access app controller scope inside custom directive in ng-repeat - angularjs-directive

I'm new to this so sorry if I'm asking something obvious. I have an app with controller holding some config variables in scope. I have a custom directive used inside ng-repeat that will have to make use of this config. For debugging purposes I need the changes to config bee reflected inside the directive. How can I achieve this.What I have so far is not working my pointsmap is undefined
angular.module('demo', [])
.directive('demoDir', function () {
var dirController = ['$scope', function ($scope) {
$scope.totalPoints = 0;
$scope.$watch('person', function(newVal, oldVal){
resetPoints(newVal);
}, true);
function resetPoints(pPerson){
$scope.totalPoints = $pointsMap['VIP'] * pPerson.points ;
}
return {
restrict: 'E',
scope: {
person: '=' ,
pointsMap : '='
},
controller: dirController,
template: '<span> {{totalPoints}}</span>'
}
})
.controller('mainAppController', function ($compile, $scope, $q ) {
/*CONFIG */
$scope.points = {
'VIP': 8.50,
'Standard': 7.50,
};
});
<demoDir person='myobject' pointsMap='points' />

Any particular reason why you want your directive to have isolated scope? If there is no restriction to use isolated scope, you can simply make the scope of the directive as false(now directive will use parent scope)
angular.module('demo', [])
.directive('demoDir', function () {
var dirController = ['$scope', function ($scope) {
$scope.totalPoints = 0;
$scope.$watch('person', function(newVal, oldVal){
resetPoints(newVal);
}, true);
function resetPoints(pPerson){
$scope.totalPoints = $pointsMap['VIP'] * pPerson.points ;
}
return {
restrict: 'E',
scope: false,
controller: dirController,
link: function(scope, element, attrs){
//scope.points should be available
}
template: '<span> {{totalPoints}}</span>'
}
})
.controller('mainAppController', function ($compile, $scope, $q ) {
/*CONFIG */
$scope.points = {
'VIP': 8.50,
'Standard': 7.50,
};
});
//assuming that your directive is housed inside the mainAppController
<div ng-controller="mainAppController">
<demoDir/>
</div>
You will also have access to the parent scope properties in the directive's link function if you want to use.

Related

AngularJs #how to pass scope variable in on change event in directive in input file

I am not able to pass data to controller's function through angular directive, directive has one change event. In which i want to pass my dynamic id.
In controller i have myArray
$scope.myArray = [1,2,3,4,5];
I have following html.
<div ng-repeat="data in myArray track by $index">
<input type="file" ng-upload-change="uploadFile($event, $id)" my-id="$index">
<div>
In Controller:
$scope.uploadFile = function($event, $id){
var files = $event.target.files;
console.log("id:"+$id);
};
In directive:
app.directive('ngUploadChange', function() {
return{
scope:{
ngUploadChange:"&",
myId:"="
},
link:function($scope, $element, $attrs){
$element.on("change",function(event){
$scope.ngUploadChange({$event: event, $id : $scope.myId});
})
$scope.$on("$destroy",function(){
$element.off();
});
}
}
});
As you can see that when i pass uploadFile function to ngUploadChange directive, it always pass first id (in this case it is 1) to controllers function.
I am not getting updated id every time.
Thanks in advance.
When you want to pass parameters through the function, you can use "=" instead of "&" for that attr binding, and in your HTML, you can specify like this:
<input type="file" ng-upload-change="uploadFile" ... />
And, I changed the way you were passing an object of params since there is no need to create that object.
Now, if you see the code snippet below, it correctly logs id: x (0-based) on each file upload.
var myApp = angular.module('myApp', []);
//myApp.directive('myDirective', function() {});
myApp.controller('MyCtrl', function MyCtrl($scope) {
$scope.myArray = [1, 2, 3, 4, 5];
$scope.uploadFile = function($event, $id) {
var files = $event.target.files;
console.log("id: " + $id);
};
});
myApp.directive('ngUploadChange', function() {
return {
scope: {
ngUploadChange: "=",
myId: "="
},
link: function($scope, $element, $attrs) {
$element.on("change", function(event) {
$scope.ngUploadChange(event, $scope.myId);
})
$scope.$on("$destroy", function() {
$element.off();
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
<div ng-repeat="data in myArray">
<input type="file" ng-upload-change="uploadFile" my-id="$index">
</div>
</div>
It is better to write the directive without isolate scope.
app.directive('ngUploadChange', function() {
return{
/*
scope:{
ngUploadChange:"&",
myId:"="
},*/
link:function($scope, $element, $attrs){
$element.on("change",function(event){
var locals = { $event: event,
$id : $scope.$eval($attrs.myId)
};
$scope.$eval($attrs.ngUploadChange, locals);
});
/*
$scope.$on("$destroy",function(){
$element.off();
});*/
}
}
});
Use $scope.$eval instead of isolate scope bindings.
Isolate scope adds a scope and additional watchers which have been known to cause digest cycle delays that fight the ngModelController.

Angular: Directive watch model defined in own controller

I try to update my chart when data is pushed to a websocket. I want to do this by defining my own directive in angular, but the binding of data does not work. The Code:
angular.directive("myChart", ['service', function (service) {
function ChartController ($scope) {
var ws = new WebSocket("url/to/websocket/");
var data = service.initData(); // initialize data table
ws.onmessage = function (event) { // listen and update data
data = service.updateData(event.data);
$scope.recentData = data;
}
$scope.recentData = data;
}
function link(scope, element, attr) {
function drawChart (data) {}
scope.$watch('data', function (newD, old) {
drawChart(newD);
}, true);
}
return {link: link, controller: ['$scope', ChartController], restrict: 'EA'}
}
Thats a simplistic example, of what i want to do. The service and data changes work well, i can log the current values. However drawChart() gets called only on startup and not on every mutation.
The Controller has to be part of the directive and not wrapped around
You need to notify angular of the changes by using a method that calls apply() after the change occurs, which is preferably done by using $timeout.
angular.directive("myChart", ['service', '$timeout', function (service, $timeout) {
function ChartController ($scope) {
var ws = new WebSocket("url/to/websocket/");
var data = service.initData(); // initialize data table
ws.onmessage = function (event) { // listen and update data
data = service.updateData(event.data);
$scope.recentData = data;
}
$scope.recentData = data;
}
function link(scope, element, attr) {
function drawChart (data) {}
scope.$watch('data', function (newD, old) {
$timeout(function(){
drawChart(newD);
});
}, true);
}
return {link: link, controller: ['$scope', ChartController], restrict: 'EA'}
}

Set angular directive attribute by calling function

I'm trying to set the value of a directive's attribute by calling a function on the containing page's controller, but it doesn't work as expected. In the code below, the "make" object does not have a "modelList" property, so I must place a separate call to the server to get it for each make.
<div ng-repeat="make in makeList">
<model-list-directive model-list="getModelList(make)" />
</div>
app.controller("myController",function($scope) {
$scope.getModelList = function(make) {
return null;
//return myService.getModelList(make);
};
})
app.directive("modelListDirective",function() {
restrict:'E',
scope: {
modelList: '='
},
template: '<ul><li ng-repeat="model in modelList">{{model.modelName}}</li></ul>',
controller: ['$scope', function ($scope) {
}]
If the getModelList() function is set to return null (not commented out in the code), no error is given, but the function is called multiple times (randomly varies between 3 and 5 usually).
The real problem comes when I invoke myService.getModelList(make) (commented out in the code). This results in an endless loop of calls to the service, which crashes the browser.
I'm guessing this is because of two-way binding, but I'm not sure.
Is there a better way to get dynamic data to the directive?
I think part of the problem is that your directive definition isn't returning an object. It should look like this:
app.directive('modelListDirective',function() {
return { // <-- need to return an object
restrict:'E',
scope: {
modelList: '='
},
template: '<ul><li ng-repeat="model in modelList">{{model.modelName}}</li></ul>',
controller: ['$scope', function ($scope) {
}]
};
});
However, you're passing a function as a 2-way binding into the directive, which you shouldn't do. See this answer to a similar issue.
What you can do instead is inject myService directly into your directive, then have your directive call myService.getModelList() in its link function.
So your markup would look like this:
<div ng-repeat="make in makeList">
<model-list-directive make="{{make}}" />
</div>
Each directive instance would just need the make.
And your directive definition would look like this:
app.directive('modelListDirective', ['myService', function(myService) {
return {
restrict:'E',
scope: {
make: '#'
},
link: function (scope, element, attrs) {
scope.modelList = myService.getModelList(scope.make);
},
template: '<ul><li ng-repeat="model in modelList">{{model.modelName}}</li></ul>',
controller: ['$scope', function ($scope) {
}]
};
}]);
setting scope.modelList in its link function.
Here's a fiddle.

Directive: Link function children not interpolating values

I am trying to output child element id in a directive, but it keeps printing non-interpolated values. I don't know how to achieve that....please help.
I am trying to learn angular...
//////////////////////
//Directive example
app.directive('simpleNumber', ['$http', '$compile', function($http, $compile) {
return {
restrict: 'A', /*Example: <span simple-number></span>*/
terminal: true,
transclude: true,
replace: true, /*Replace <simple-number-ctrl> tag with below template*/
template: "<div><div id=\"{{$id}}\"></div></div> ",
scope: { /*data-binding to parent scope*/
ctrlWidth: "#", /*one way binding*/
strNumber: "=", /*two way binding*/
onWidthChanged: "&" /*Event function fired*/
},
link: function($scope, elm, attrs) {
console.log(elm.children()[0].id); //This is printing {{$id}} !!! I AM CONFUSED
}
};
}]);
<span simple-number ctrl-width="100px" str-number="numberText" on-width-changed="onWidthChanged(width);"><font color=green>Transcluded text</font></span>
When the linking function is called, the {{ }} bindings are not evaluated yet, so you still have them as raw text. You will get the correct value once the $digest cycle is finished.
Use this in your link function if you want to better understand what is going on.
link: function($scope, elm, attrs) {
console.log("bindings not evaluated yet:",element.html());
var unwatch = scope.$watch(function(){
console.log("after $digest, bindings are evaluated",element.html());
unwatch();
});
}
I have solved this by using $timeout (see code below). I don't know why it's working this way. Can someone provide an explanation?
//////////////////////
//Directive example
app.directive('simpleNumber', ['$http', '$compile', function($http, $compile) {
return {
restrict: 'A', /*Example: <span simple-number></span>*/
transclude: true,
replace: true, /*Replace <simple-number-ctrl> tag with below template*/
template: "<div><div id=\"{{$id}}\"></div></div> ",
link: function($scope, elm, attrs) {
//Print children ID's
var __debugOutputChildrenInfo = function(children)
{
for(var i = 0; i < children.length; i++)
console.log(children[i].id);
}
var children = elm.children();
__debugOutputChildrenInfo(children); //Prints {{$id}} [Shouldn't binding have been resolved by now?]
//Don't understand why this is working...need explanation?
$timeout(function() {
__debugOutputChildrenInfo(children); //Prints 002 [THIS IS WHAT I WANTED..NEED EXPLANATION]
});
}
};
}]);
<span simple-number ctrl-width="100px" str-number="numberText" on-width-changed="onWidthChanged(width);"><font color=green>Transcluded text</font></span>

angularjs directive call function specified in attribute and pass an argument to it

I want to create a directive that links to an attribute. The attribute specifies the function that should be called on the scope. But I also want to pass an argument to the function that is determined inside the link function.
<div my-method='theMethodToBeCalled'></div>
In the link function I bind to a jQuery event, which passes an argument I need to pass to the function:
app.directive("myMethod",function($parse) {
restrict:'A',
link:function(scope,element,attrs) {
var expressionHandler = $parse(attrs.myMethod);
$(element).on('theEvent',function( e, rowid ) {
id = // some function called to determine id based on rowid
scope.$apply(function() {expressionHandler(id);});
}
}
}
app.controller("myController",function($scope) {
$scope.theMethodToBeCalled = function(id) { alert(id); };
}
Without passing the id I can get it working, but as soon as I try to pass an argument, the function is not called anymore
Marko's solution works well.
To contrast with recommended Angular way (as shown by treeface's plunkr) is to use a callback expression which does not require defining the expressionHandler. In marko's example change:
In template
<div my-method="theMethodToBeCalled(myParam)"></div>
In directive link function
$(element).click(function( e, rowid ) {
scope.method({myParam: id});
});
This does have one disadvantage compared to marko's solution - on first load theMethodToBeCalled function will be invoked with myParam === undefined.
A working exampe can be found at #treeface Plunker
Just to add some info to the other answers - using & is a good way if you need an isolated scope.
The main downside of marko's solution is that it forces you to create an isolated scope on an element, but you can only have one of those on an element (otherwise you'll run into an angular error: Multiple directives [directive1, directive2] asking for isolated scope)
This means you :
can't use it on an element hat has an isolated scope itself
can't use two directives with this solution on the same element
Since the original question uses a directive with restrict:'A' both situations might arise quite often in bigger applications, and using an isolated scope here is not a good practice and also unnecessary. In fact rekna had a good intuition in this case, and almost had it working, the only thing he was doing wrong was calling the $parsed function wrong (see what it returns here: https://docs.angularjs.org/api/ng/service/$parse ).
TL;DR; Fixed question code
<div my-method='theMethodToBeCalled(id)'></div>
and the code
app.directive("myMethod",function($parse) {
restrict:'A',
link:function(scope,element,attrs) {
// here you can parse any attribute (so this could as well be,
// myDirectiveCallback or multiple ones if you need them )
var expressionHandler = $parse(attrs.myMethod);
$(element).on('theEvent',function( e, rowid ) {
calculatedId = // some function called to determine id based on rowid
// HERE: call the parsed function correctly (with scope AND params object)
expressionHandler(scope, {id:calculatedId});
}
}
}
app.controller("myController",function($scope) {
$scope.theMethodToBeCalled = function(id) { alert(id); };
}
Not knowing exactly what you want to do... but still here's a possible solution.
Create a scope with a '&'-property in the local scope.
It "provides a way to execute an expression in the context of the parent scope" (see the directive documentation for details).
I also noticed that you used a shorthand linking function and shoved in object attributes in there. You can't do that. It is more clear (imho) to just return the directive-definition object. See my code below.
Here's a code sample and a fiddle.
<div ng-app="myApp">
<div ng-controller="myController">
<div my-method='theMethodToBeCalled'>Click me</div>
</div>
</div>
<script>
var app = angular.module('myApp',[]);
app.directive("myMethod",function($parse) {
var directiveDefinitionObject = {
restrict: 'A',
scope: { method:'&myMethod' },
link: function(scope,element,attrs) {
var expressionHandler = scope.method();
var id = "123";
$(element).click(function( e, rowid ) {
expressionHandler(id);
});
}
};
return directiveDefinitionObject;
});
app.controller("myController",function($scope) {
$scope.theMethodToBeCalled = function(id) {
alert(id);
};
});
</script>
You can create a directive that executes a function call with params by using the attrName: "&" to reference the expression in the outer scope.
We want to replace the ng-click directive with ng-click-x:
<button ng-click-x="add(a,b)">Add</button>
If we had this scope:
$scope.a = 2;
$scope.b = 2;
$scope.add = function (a, b) {
$scope.result = parseFloat(a) + parseFloat(b);
}
We could write our directive like so:
angular.module("ng-click-x", [])
.directive('ngClickX', [function () {
return {
scope: {
// Reference the outer scope
fn: "&ngClickX",
},
restrict: "A",
link: function(scope, elem) {
function callFn () {
scope.$apply(scope.fn());
}
elem[0].addEventListener('click', callFn);
}
};
}]);
Here is a live demo:
http://plnkr.co/edit/4QOGLD?p=info
Here's what worked for me.
Html using the directive
<tr orderitemdirective remove="vm.removeOrderItem(orderItem)" order-item="orderitem"></tr>
Html of the directive: orderitem.directive.html
<md-button type="submit" ng-click="remove({orderItem:orderItem})">
(...)
</md-button>
Directive's scope:
scope: {
orderItem: '=',
remove: "&",
My solution:
on polymer raise an event (eg. complete)
define a directive linking the event to control function
Directive
/*global define */
define(['angular', './my-module'], function(angular, directives) {
'use strict';
directives.directive('polimerBinding', ['$compile', function($compile) {
return {
restrict: 'A',
scope: {
method:'&polimerBinding'
},
link : function(scope, element, attrs) {
var el = element[0];
var expressionHandler = scope.method();
var siemEvent = attrs['polimerEvent'];
if (!siemEvent) {
siemEvent = 'complete';
}
el.addEventListener(siemEvent, function (e, options) {
expressionHandler(e.detail);
})
}
};
}]);
});
Polymer component
<dom-module id="search">
<template>
<h3>Search</h3>
<div class="input-group">
<textarea placeholder="search by expression (eg. temperature>100)"
rows="10" cols="100" value="{{text::input}}"></textarea>
<p>
<button id="button" class="btn input-group__addon">Search</button>
</p>
</div>
</template>
<script>
Polymer({
is: 'search',
properties: {
text: {
type: String,
notify: true
},
},
regularSearch: function(e) {
console.log(this.range);
this.fire('complete', {'text': this.text});
},
listeners: {
'button.click': 'regularSearch',
}
});
</script>
</dom-module>
Page
<search id="search" polimer-binding="searchData"
siem-event="complete" range="{{range}}"></siem-search>
searchData is the control function
$scope.searchData = function(searchObject) {
alert('searchData '+ searchObject.text + ' ' + searchObject.range);
}
This should work.
<div my-method='theMethodToBeCalled'></div>
app.directive("myMethod",function($parse) {
restrict:'A',
scope: {theMethodToBeCalled: "="}
link:function(scope,element,attrs) {
$(element).on('theEvent',function( e, rowid ) {
id = // some function called to determine id based on rowid
scope.theMethodToBeCalled(id);
}
}
}
app.controller("myController",function($scope) {
$scope.theMethodToBeCalled = function(id) { alert(id); };
}