Directive Isolated Scope: @ vs. =
Thu Aug 13, 2015
Shall I use @ or = to bind scope variables? Their difference is very well explained in Angular’s documentation. However, this question still often pops up when I create directives. In this post, I listed some of the major differences between these two.
@: Interpolation Binding
”@” symbols can be used to bind interpolations. Interpolations are {{ }} expressions.
Let’s say we have a @-bound scope variable – name
in a directive:
// A directive
function awesomeWidget() {
return {
...
scope: {
name: '@' // @-bound variable
}
}
}
We now can assign interpolation {{ person.name }}
to the @-bound variable name
:
<!-- Use interpolation {{ person.name }} for @-bound variables -->
<awesome-widget name="{{ person.name }}"></awesome-widget>
However, what will happen if we replace {{ person.name }}
with person.name
? Angular will not throw any error, but the value of scope.name
will be a 'person.name'
string instead of the real value of person.name.
We can also assign primitives (string, number, boolean, and etc.) directly to @-bound values. For example:
<awesome-widget name="David" age="26.5" single="false"></awesome-widget>
<!-- Note: "David" here is a string primitive not a variable name -->
Angular is smart enough to figure out their types:
function awesomeWidget($log) {
return {
...
scope: {
name: '@', age: '@', single: '@'
},
link: function(scope) {
$log.log(scope.name, typeof scope.name);
// --> "David", string
$log.log(scope.age, typeof scope.age);
// --> 26.5, number
$log.log(scope.single, typeof scope.single);
// --> false, boolean
}
}
}
=: Variable Name Binding
”=” symbols can be used to bind variable names.
Here, we have a =-bound variable – name
:
function awesomeWidget() {
return {
...
scope: {
name: '=' // =-bound scope variable
}
}
}
Unlike @-bound variables which require interpolations such as {{ person.name }}
, =-bound variables can directly refer to variable names, e.g. person.name
.
<awesome-widget name="person.name"></awesome-widget>
<!-- Not {{ person.name }} -->
If we replace person.name
with {{ person.name }}
, Angular will throw an error this time:
Error: [$parse:syntax] Syntax Error:
Token '{' invalid key at column 2 of the expression [{{ person.name }}]
starting at [{ person.name }}].
Primitives can also be assigned to =-bound variables. For instance:
<awesome-widget age="26.5" single="false"></awesome-widget>
However, for strings, single quotes are needed. This is different from @-bound values:
<awesome-widget name="'David'"></awesome-widget>
<!-- Note: David is wrapped in single quotes -->
The single quotes are needed because without the single quotes, Angular will treat “David” as a variable name instead of a string primitive, which might not be what you wanted.
@: One-way Binding
@ is for one-way binding - the bound value is passed into a directive’s scope, however, any change made to the value inside the directive will not be passed back from the directive. To help myself remember this syntax, I’d like to think @ as ‘point at’ - a one-way direction - from the external scope to a directive’s internal scope.
For example:
<div ng-controller="MainController">
<awesome-widget name="{{ person.name }}"></awesome-widget>
</div>
We bind {{ person.name }}
to the directive through the name
attribute. The value of person.name
comes from a person object that is attached to the MainController
’s scope:
// A controller
function MainController($scope) {
$scope.person = { name: 'David' };
}
In the directive, the @ symbol is to indicate that name
will be one-way bound to the directive’s scope variable scope.name
.
// A directive
function awesomeWidget() {
return {
...
scope: {
name: '@' // One-way binding
},
link: function(scope) {
// Here we change the name variable on the directive scope
scope.name = 'Serena';
}
}
}
If we change the name variable inside the directive (e.g., in the link function), the changed name will not be passed back from the directive’s scope to the controller’s scope.
<div ng-controller="MainController">
<awesome-widget name="{{ person.name }}"></awesome-widget>
<!-- In the controller's scope, person.name will still be "David" instead of "Serena" -->
{{ person.name }}
</div>
Inside awesome-widget’s link
function, name is changed from “David” to “Sereana”. However, outside of the directive, person.name
is still “David”. This is because the scope variable name
is one-way bound. The change will not affect the scope outside of its directive.
=: Two-way Binding
= is for two-way binding, meaning the change made to the bound value will be “sync’d” both inside and outside of a directive. I’d like to think the “=” symbol as “bi-directional”.
Using the above example, we change “@” to “=”:
// A directive
function awesomeWidget() {
return {
...
scope: {
name: '=' // Changed to two-way binding
},
link: function(scope) {
scope.name = 'Serena';
}
}
}
Note that in the HTML template, we also changed {{ person.name }}
to person.name
:
<div ng-controller="MainController">
<awesome-widget name="person.name"></awesome-widget> <!-- Removed {{ }} -->
<!-- Now, person.name is changed to "Serena" -->
{{ person.name }}
</div>
The scope variable name
is changed in directive’s link
function. Because name
is two-way bound, the change is also reflected in person.name
on MainControler
’s scope.
Conclusion
Type | Direction | Value type | String primitive |
---|---|---|---|
@ | One-way binding | Interpolations and primitives | No single quote: “David” |
= | Two-way binding | Variable names and primitives | Single quotes: “‘David’” |