Angular - pass $scope function to directive - javascript

I'm passing a controller $scope function to a directive through an html attribute, but for some reason the directive thinks that the function is a string. Any hints?
HTML
<modal show='createCustomer' create-new-customer='createNewCustomer()'></modal>
Directive
function modalDialog() {
return {
restrict: 'AE',
scope:{
createNewCustomer: '&'
},
replace: true,
transclude: true,
link: function(scope, element, attrs) {
scope.createNewCustomer = attrs.createNewCustomer;
console.log(typeof scope.createNewCustomer)
},
templateUrl: "./views/directive_templates/modal.html"
};
}
$scope function
$scope.createNewCustomer = function(){
alert('yo')
}
Best,
Austin

Your code should be:
<modal show='createCustomer' create-new-customer='createNewCustomer'></modal>
It is most likely invoking the function immediately and passing in the result

In your directive, you already have scope.createNewCustomer bound to the function, through the use of the & parameter in your isolate scope. However, you actually overwrite the function with the string value when you re-assign it through the attrs. attrs is simply an array of key value pairs that are the string representation of each attribute in the element.

AngularJs directive lets you to use '&' method in scope with 2 ways.First way lets you to pass argument inside your isolated directive.Second one lets you to pass arguments outside of directive but to call INSIDE ISOLATED DIRECTIVE.Like
<div my-dir func="myFunc(arg1,arg2,..)"></div>
<div my-dir func="myFunc(value1,value2,..)"></div>
If you want more here is a cool article with examples:
http://www.w3docs.com/snippets/angularjs/angularjs-directive-scope-method.html

Related

angularjs directive: how communicate between link and controller?

I have a directive whose 'config' attribute value I need to access inside my directive controller.
Since the controller constructor get executed first,communication from controller to link is possible but not vice versa.
What should be the best way to achieve this?
I have considered the following approaches
1)Add the variable to scope-
That would in my opinion pollute the scope,making the variable accessible every where else where the scope is being shared.
2)Use $broadcast
Again the same issue as above
3) Pass a callback function on controller's this and call it from the link function with config as its argument
4)Pass the value through a service- In my case I have multiple such directives that would need to pass date through this service
Or is there some better approach that I am missing out for doing this?
module.directive('myDirective',function(){
return{
restrict:'E',
templateUrl:'path/to/html',
link:function(scope,iElement,iAttrs,controller){
var config=iAttrs.config;
//How to access this value inside the directive controller?
},
controller:function($scope){
//the directive attribute 'config' is required here for some larger computations which are not
//manipulating the DOM and hence should be seperated from the link function
})
There you can use isolated scope concept where you create isolated scope inside your controller & that would not be prototypically inherited from its parent scope. For that you need to use scope: { ... } inside your directive option. There are three options to pass scope value inside a directive through attribute
# : One way binding
= : Two way binding
& : Expression
In your case first two cases would be fine that are depends which one you need to use. If you just want to pass the value of scope variable to the directive in that case you could use 1st approach which would be # one way binding.
If you want to update the variable in both directive as well as controller from where it come i.e. nothing but two way binding, then you need to use =
I think = suits in your case so you should go for =
Markup
<my-directive config="config"></my-directive>
Directive
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
config: '='
},
templateUrl: 'path/to/abc.html',
link: function(scope, iElement, iAttrs, controller) {
//here it will be access as scope.config
console.log(scope.config);
},
controller: function($scope) {
console.log($scope.config); //here also it would be available inisde scope
//you could put a watch to detect a changes on config
}
}
});
Demo Plunkr
Update
As config value has been provide from the attribute with expression like {{}} so we could get those changes inside controller by putting [**$observe**][2] on $attrs. For that you need to inject $attrs dependency on your controller that will give you all the attributes collection which are available on directive element. And on the same $attrs object we gonna put $observe which work same as that of $watch which does dirty checking & if value gets change it fires that watch.
Directive
app.directive('myDirective', function() {
return {
restrict: 'E',
templateUrl: 'path/to/abc.html',
link: function(scope, iElement, iAttrs, controller) {
//here it will be access as scope.config
console.log(scope.config);
},
controller: function($scope,$attrs) {
//you could put a watch to detect a changes on config
$attrs.$observe('config', function(newV){
console.log(newV);
})
}
}
});
Updated Demo

Confusion on Angular Attributes on Custom Directive

I'm confused as to why this isn't working...
here is my directive:
app.directive('dateDropDowns', function () {
function link($scope, elem, attr) {
console.log($scope.startYear);
};
function controller($scope, $attrs, $location) {
};
return {
link: link,
scope: {
startYear: "&",
endYear: "&",
date: "=",
required: "&"
},
restrict: "EA",
templateUrl: "/scripts/templates/datedropdowns.html",
controller: controller
}
});
Here is my directive html:
<div date-drop-downs start-year="startYear" end-year="endYear" required="true" date="testDate"></div>
startYear, endYear, testDate are all defined in the scope of the controller on which the above html exists.
The console.log in the link function is returning:
function (locals) {
return parentGet(scope, locals);
}
However, the startYear should be returning 1910 as that is what is set to in the controller on which the "date-drop-downs" directive is being called.
I've tried console.log($scope.$eval($scope.startYear)); but it returns the same thing above
I've tried console.log($scope.$eval(attr.startYear)); but it returns the same thing above
I've tried console.log(attr.startYear); but that just returns the text "startYear"
I'm not sure what to do to actually get the startYear value.
Any help is greatly appreciated.
Since you are using & binding they get wrapped in as function if they are already not function. This is generally used to invoke a method on the parent using isolate scope.
So you should actually do:-
$scope.startYear()
to get the value of startYear, if you are looking for 2 waybinding you should go with = only, if you just need it as text binding then use # and set the value as start-year="{{startYear}}" on the directive
& or &attr - provides a way to execute an expression in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given and widget definition of scope: { localFn:'&myAttr' }, then isolate scope property localFn will point to a function wrapper for the count = count + value expression. Often it's desirable to pass data from the isolated scope via an expression to the parent scope, this can be done by passing a map of local variable names and values into the expression wrapper fn. For example, if the expression is increment(amount) then we can specify the amount value by calling the localFn as localFn({amount: 22}).
Inorder to achieve one-time binding you could use bindonce or with angular 1.3beta you could use one-time binding syntax:-
.. start-year="{{::startYear}}" ...
and
startYear: "#",
Plnkr

Access ng-model from inside directive link function

I'm attempting to validate inputs inside a directive. Consider the following:
.directive('test', function ($parse, $http, $sce, $timeout) {
return {
restrict: 'EA',
scope: {
},
template: '<div class="holder">
<input id="A" name="inputA" ng-model="modelA" />
<input id="B" name="inputB" ng-model="modelB" />
</div>',
link: function($scope, elem, attrs) {
}
}
If I wanted to do my own custom validation inside the link function, how would I reference the "modelA" model? $scope.modelA appears to only reference the value inside the input, not the model itself (which is what I need for validation, as I understand it). All the other references I could find seem to deal with binding referencing models from the parent scope, which I don't need.
What am I missing?
I see you are using an isolated scope, I can't tell if that's part of your problem, but with an isolated scope and no properties, the parent scope outside the directive will have no way to access the values inside and vice-versa. I created a plunk to explain isolated scopes: (plnkr). If you want to access values on your parent scope you probably want something like this for your directive scope:
scope: {
modelA: "=modelA",
modelB: "=modelB"
},
And then calling your directive would work like this:
<test model-a="firstName", model-b="lastName"/>
Another issue might be that the link function only executes once for each time you use your directive as it is processing it and linking it. For instance if the value of modelA changes then your link function will not be called again. If you want to do something when modelA changes you need to set up a watcher for the change in your link function. Note that it will be be called once initially without changing the value:
link: function($scope, elem, attrs) {
$scope.$watch('modelA', function(newValue, oldValue) {
console.log('modelA changed from', oldValue, 'to', 'newValue');
});
}

Can an angular directive pass arguments to functions in expressions specified in the directive's attributes?

I have a form directive that uses a specified callback attribute with an isolate scope:
scope: { callback: '&' }
It sits inside an ng-repeat so the expression I pass in includes the id of the object as an argument to the callback function:
<directive ng-repeat = "item in stuff" callback = "callback(item.id)"/>
When I've finished with the directive, it calls $scope.callback() from its controller function. For most cases this is fine, and it's all I want to do, but sometimes I'd like to add another argument from inside the directive itself.
Is there an angular expression that would allow this: $scope.callback(arg2), resulting in callback being called with arguments = [item.id, arg2]?
If not, what is the neatest way to do this?
I've found that this works:
<directive
ng-repeat = "item in stuff"
callback = "callback"
callback-arg="item.id"/>
With
scope { callback: '=', callbackArg: '=' }
and the directive calling
$scope.callback.apply(null, [$scope.callbackArg].concat([arg2, arg3]) );
But I don't think it's particularly neat and it involves puting extra stuff in the isolate scope.
Is there a better way?
Plunker playground here (have the console open).
If you declare your callback as mentioned by #lex82 like
callback = "callback(item.id, arg2)"
You can call the callback method in the directive scope with object map and it would do the binding correctly. Like
scope.callback({arg2:"some value"});
without requiring for $parse. See my fiddle(console log) http://jsfiddle.net/k7czc/2/
Update: There is a small example of this in the documentation:
& or &attr - provides a way to execute an expression in the context of
the parent scope. If no attr name is specified then the attribute name
is assumed to be the same as the local name. Given and widget definition of scope: {
localFn:'&myAttr' }, then isolate scope property localFn will point to
a function wrapper for the count = count + value expression. Often
it's desirable to pass data from the isolated scope via an expression
and to the parent scope, this can be done by passing a map of local
variable names and values into the expression wrapper fn. For example,
if the expression is increment(amount) then we can specify the amount
value by calling the localFn as localFn({amount: 22}).
Nothing wrong with the other answers, but I use the following technique when passing functions in a directive attribute.
Leave off the parenthesis when including the directive in your html:
<my-directive callback="someFunction" />
Then "unwrap" the function in your directive's link or controller. here is an example:
app.directive("myDirective", function() {
return {
restrict: "E",
scope: {
callback: "&"
},
template: "<div ng-click='callback(data)'></div>", // call function this way...
link: function(scope, element, attrs) {
// unwrap the function
scope.callback = scope.callback();
scope.data = "data from somewhere";
element.bind("click",function() {
scope.$apply(function() {
callback(data); // ...or this way
});
});
}
}
}]);
The "unwrapping" step allows the function to be called using a more natural syntax. It also ensures that the directive works properly even when nested within other directives that may pass the function. If you did not do the unwrapping, then if you have a scenario like this:
<outer-directive callback="someFunction" >
<middle-directive callback="callback" >
<inner-directive callback="callback" />
</middle-directive>
</outer-directive>
Then you would end up with something like this in your inner-directive:
callback()()()(data);
Which would fail in other nesting scenarios.
I adapted this technique from an excellent article by Dan Wahlin at http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters
I added the unwrapping step to make calling the function more natural and to solve for the nesting issue which I had encountered in a project.
In directive (myDirective):
...
directive.scope = {
boundFunction: '&',
model: '=',
};
...
return directive;
In directive template:
<div
data-ng-repeat="item in model"
data-ng-click='boundFunction({param: item})'>
{{item.myValue}}
</div>
In source:
<my-directive
model='myData'
bound-function='myFunction(param)'>
</my-directive>
...where myFunction is defined in the controller.
Note that param in the directive template binds neatly to param in the source, and is set to item.
To call from within the link property of a directive ("inside" of it), use a very similar approach:
...
directive.link = function(isolatedScope) {
isolatedScope.boundFunction({param: "foo"});
};
...
return directive;
Yes, there is a better way: You can use the $parse service in your directive to evaluate an expression in the context of the parent scope while binding certain identifiers in the expression to values visible only inside your directive:
$parse(attributes.callback)(scope.$parent, { arg2: yourSecondArgument });
Add this line to the link function of the directive where you can access the directive's attributes.
Your callback attribute may then be set like callback = "callback(item.id, arg2)" because arg2 is bound to yourSecondArgument by the $parse service inside the directive. Directives like ng-click let you access the click event via the $event identifier inside the expression passed to the directive by using exactly this mechanism.
Note that you do not have to make callback a member of your isolated scope with this solution.
For me following worked:
in directive declare it like this:
.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
scope: {
myFunction: '=',
},
templateUrl: 'myDirective.html'
};
})
In directive template use it in following way:
<select ng-change="myFunction(selectedAmount)">
And then when you use the directive, pass the function like this:
<data-my-directive
data-my-function="setSelectedAmount">
</data-my-directive>
You pass the function by its declaration and it is called from directive and parameters are populated.

AngularJs: create directive

I've a question about a directive I want to write. Here is the jsfiddle
This is the HTML
<div ng-controller="TestCtrl">
<mydir base="{{absolute}}">
<div>{{path1}}</div>
<div>{{path2}}</div>
</mydir>
</div>
The directive, called 'mydir', will need the value of the base attribute and all the path values (there can be any number of paths defined). I don't want to inject values from the controller directly into the directive scope (they need to be independent). I have 3 questions about this (checkout the jsfiddle too!)
1) although the code works as it is now, there is an error: "TypeError: Object # has no method 'setAttribute'". Any suggestions what is wrong?
2) in the directive scope.base is 'undefined' instead of '/base/'. Can this be fixed.
3) How can I make a list of all the path values in the directive ?
You need to set an isolate scope that declares the base attribute for this directive:
scope: {
base: "#"
}
This solves (2).
Problem (1) is caused from the replace: true in cooperation with the scope above, because it creates a comment node to contain the repeated <div>s.
For (3), add paths: "=" to scope and, of ocurse, pass them from the parent controller as an array.
See forked fiddle: http://jsfiddle.net/swYAZ/1/
You're creating an new scope in your directive by setting scope: true what you want to do is:
app.directive('mydir', function ($compile, $rootScope) {
return {
replace: true,
restrict: 'E',
template: '<div ng-repeat="p in paths"> {{base}}{{p}}/{{$index}}</div>',
scope: {
base : '='
},
link: function (scope, iElement, iAttrs) {
scope.paths = ['p1', 'p2'] ;
}
}
});
This will setup a bi-directional binding between your directive's local scope and the parent's scope. If the value changes in the parent controller it will change in the directive as well.
http://jsfiddle.net/4LAjf/8/
If you want to make a robust directive to be able to replace the data to its content (3)
You can pass a reference as an attribute and display the data accordingly.
See this article, look for wrapper widget.

Categories

Resources