How can I pass either a model or a string to a directive in angularjs?
I am working on a directive, where one of the parameters to it can be either a model (dynamic value), or sometimes a string.
The only way to do this is to use @
. Is there a better way to do this?
<my-directive foo="{{foomodel}}">
<my-directive foo="foostring">
In my directive, I am using a marquee:
scope: {
foo: '@'
}
I've tried doing this with just strings and it didn't work.
source to share
Reading these answers, it seems like there is a lot of confusion about the differences between '=', '&' and "@" when using isolated scope. For your purposes, I would use, and if you want the value to be a string or an object, as @pankajparkar does and puts strings in single quotes.
Unless you need the changes to your directive to be reflected in your parent scope, don't use '='. '=' implicitly sets the clock for the isolated scope property and changes the parent scope property if it sees a change. Clock is a relatively expensive operation in Angular and you want to minimize it.
& is not a clock-creating implication, and it will override the expression when you call the generated scope function. This is perfect for your case because you don't need two-way binding. Think back and create a function that returns the value generated by the expression assigned to this attribute.
app.directive("foo", function(){
return {
scope: {
foo: "&"
},
template: "<span>{{foo()}}</span> | <span>{{typeof foo()}}</span>",
link: function(scope){
//so we don't have to set up a watch, I've just inlined the evaluation of the type
}
}
})
<div foo="model"></div> <!-- test | string -->
<div foo="'text'"></div> <!-- text | string -->
<div foo="5"></div> <!-- 5 | number -->
<div foo="{a: 5}"></div>
@ is reserved for cases where you want to limit the evaluation of an expression prior to interpolation. Interpolation in angular ALWAYS results in a string. If you use "@" and your attribute is not an interpolation expression ({{}}), the value of the scope property will be the literal value of the attribute. If your attribute value is an interpolation expression, the property value will be the interpolated expression value (which will always be a string).
Clean like dirt, right?
source to share
If you are using the selection, you can use two-way binding: "="
. This will take a model or literal like this (assuming $scope.model = "test"
:
<div foo="model"></div> <!-- test | string -->
<div foo="'text'"></div> <!-- text | string -->
<div foo="5"></div> <!-- 5 | number -->
<div foo="{a: 5}"></div> <!-- {"a":4} | object -->
and this is the directive definition:
app.directive("foo", function(){
return {
scope: {
foo: "="
},
template: "<span>{{foo}}</span> | <span>{{type}}</span>",
link: function(scope){
scope.type = typeof scope.foo;
}
}
})
EDIT:
According to the feedback from @JoeEnzminger, in "="
is an expensive way to get the real object as it sets 2 observers (for two way binding).
For one-way binding (with one observer)
You can bind foo: "@"
. This will cause all values to be interpreted as string
. In particular, to convey meaning $scope.model
, you will need to use the expression: {{model}}
.
Alternatively, as suggested by @JoeEnzminger answer below , you can use binding foo: "&"
. This will bring the actual model / object back. The disadvantage is the more awkward function syntax {{foo()}}
for expressions.
For a one-time binding:
If you can opt out of the one-time binding, you can use the service $parse
to get the actual object.
For selection area:
scope: {},
link: function(scope, element, attrs){
scope.foo = $parse(attrs.foo)(scope.$parent);
}
In the legacy area:
scope: true,
link: function(scope, element, attrs){
scope.foo = $parse(attrs.foo)(scope);
}
source to share