What is the difference between '@' and '=' in directive scope in AngularJS?

I've read the AngularJS documentation on the topic carefully, and then fiddled around with a directive. Here's the fiddle.

And here are some relevant snippets:

  • From the HTML:

    <pane bi-title="title" title="{{title}}">{{text}}</pane>
    
  • From the pane directive:

    scope: { biTitle: '=', title: '@', bar: '=' },
    

There are several things I don't get:

  • Why do I have to use "{{title}}" with '@' and "title" with '='?
  • Can I also access the parent scope directly, without decorating my element with an attribute?
  • The documentation says "Often it's desirable to pass data from the isolated scope via expression and to the parent scope", but that seems to work fine with bidirectional binding too. Why would the expression route be better?

I found another fiddle that shows the expression solution too: http://jsfiddle.net/maxisam/QrCXh/


Why do I have to use "{{title}}" with '@' and "title" with '='?

@ binds a local/directive scope property to the evaluated value of the DOM attribute. If you use title=title1 or title="title1", the value of DOM attribute "title" is simply the string title1. If you use title="{{title}}", the value of the DOM attribute "title" is the interpolated value of {{title}}, hence the string will be whatever parent scope property "title" is currently set to. Since attribute values are always strings, you will always end up with a string value for this property in the directive's scope when using @.

= binds a local/directive scope property to a parent scope property. So with =, you use the parent model/scope property name as the value of the DOM attribute. You can't use {{}}s with =.

With @, you can do things like title="{{title}} and then some" -- {{title}} is interpolated, then the string "and them some" is concatenated with it. The final concatenated string is what the local/directive scope property gets. (You can't do this with =, only @.)

With @, you will need to use attr.$observe('title', function(value) { ... }) if you need to use the value in your link(ing) function. E.g., if(scope.title == "...") won't work like you expect. Note that this means you can only access this attribute asynchronously. You don't need to use $observe() if you are only using the value in a template. E.g., template: '<div>{{title}}</div>'.

With =, you don't need to use $observe.

Can I also access the parent scope directly, without decorating my element with an attribute?

Yes, but only if you don't use an isolate scope. Remove this line from your directive

scope: { ... }

and then your directive will not create a new scope. It will use the parent scope. You can then access all of the parent scope properties directly.

The documentation says "Often it's desirable to pass data from the isolated scope via an expression and to the parent scope", but that seems to work fine with bidirectional binding too. Why would the expression route be better?

Yes, bidirectional binding allows the local/directive scope and the parent scope to share data. "Expression binding" allows the directive to call an expression (or function) defined by a DOM attribute -- and you can also pass data as arguments to the expression or function. So, if you don't need to share data with the parent -- you just want to call a function defined in the parent scope -- you can use the & syntax.

See also

  • Lukas's isolated scope blog post (covers @, =, &)
  • dnc253's explanation of @ and =
  • my blog-like answer about scopes -- the directives section (way at the bottom, just before the Summary section) has a picture of an isolate scope and its parent scope -- the directive scope uses @ for one property and = for another
  • What is the difference between & vs @ and = in angularJS

There are a lot of great answers here, but I would like to offer my perspective on the differences between @, =, and & binding that proved useful for me.

All three bindings are ways of passing data from your parent scope to your directive's isolated scope through the element's attributes:

  1. @ binding is for passing strings. These strings support {{}} expressions for interpolated values. For example: . The interpolated expression is evaluated against directive's parent scope.

  2. = binding is for two-way model binding. The model in parent scope is linked to the model in the directive's isolated scope. Changes to one model affects the other, and vice versa.

  3. & binding is for passing a method into your directive's scope so that it can be called within your directive. The method is pre-bound to the directive's parent scope, and supports arguments. For example if the method is hello(name) in parent scope, then in order to execute the method from inside your directive, you must call $scope.hello({name:'world'})

I find that it's easier to remember these differences by referring to the scope bindings by a shorter description:

  • @ Attribute string binding
  • = Two-way model binding
  • & Callback method binding

The symbols also make it clearer as to what the scope variable represents inside of your directive's implementation:

  • @ string
  • = model
  • & method

In order of usefulness (for me anyways):

  1. =
  2. @
  3. &

The = means bi-directional binding, so a reference to a variable to the parent scope. This means, when you change the variable in the directive, it will be changed in the parent scope as well.

@ means the variable will be copied (cloned) into the directive.

As far as I know, <pane bi-title="{{title}}" title="{{title}}">{{text}}</pane> should work too. bi-title will receive the parent scope variable value, which can be changed in the directive.

If you need to change several variables in the parent scope, you could execute a function on the parent scope from within the directive (or pass data via a service).


If you would like to see more how this work with a live example. http://jsfiddle.net/juanmendez/k6chmnch/

var app = angular.module('app', []);
app.controller("myController", function ($scope) {
    $scope.title = "binding";
});
app.directive("jmFind", function () {
    return {
        replace: true,
        restrict: 'C',
        transclude: true,
        scope: {
            title1: "=",
            title2: "@"
        },
        template: "<div><p>{{title1}} {{title2}}</p></div>"
    };
});