Code


<!DOCTYPE html> <html> <head> <script src="angular.js"></script> <script src="script.js"></script> </head> <body ng-app="mainModule"> <div ng-controller="mainController"> <h3>Base scope</h3> <label>First name: <input type="text" ng-model="firstName"/></label><br /> <label>Last name: <input type="text" ng-model="lastName"/></label><br /> <br /> <strong>First name:</strong> {{firstName}}<br /> <strong>Last name:</strong> {{lastName}}<br /> <br /> <h3>1. Shared scope</h3> <div ngh-shared-scope-dir></div> <br /> <h3>2. Inherited scope</h3> <div ngh-inherited-scope-dir></div> <br /> <h3>3. Isolated scope</h3> <div ngh-isolated-scope-dir dir-first-name="Michael" dir-last-name="lastName" dir-update-name-method="updateName(newFirstName, newLastName)"> </div> <br /> <h3>4. Directives on the same DOM node might share the scope</h3> <strong>Base scope ID:</strong> {{$id}}<br /> <br /> <h3>4.1. Directives with shared, inherited and isolated scope</h3> <button ngh-shared-scope-tester="1A" ngh-shared-scope-tester-copy="1B" ngh-inherited-scope-tester="1A" ngh-inherited-scope-tester-copy="1B" ngh-isolated-scope-tester="1">Check scope IDs</button><br /> <br /> <strong>[nghSharedScopeTester] Scope ID:</strong> {{sharedDirScopeId1A}}<br /> <strong>[nghSharedScopeTesterCopy] Scope ID:</strong> {{sharedDirScopeId1B}}<br /> <strong>[nghInheritedScopeTester] Scope ID:</strong> {{inheritedDirScopeId1A}}<br /> <strong>[nghInheritedScopeTesterCopy] Scope ID:</strong> {{inheritedDirScopeId1B}}<br /> <strong>[nghIsolatedScopeTester] Scope ID:</strong> {{isolatedDirScopeId1}}<br /> <br /> <h3>4.2. Directives with shared and isolated scope</h3> <button ngh-shared-scope-tester="2A" ngh-shared-scope-tester-copy="2B" ngh-isolated-scope-tester="2">Check scope IDs</button><br /> <br /> <strong>[nghSharedScopeTester] Scope ID:</strong> {{sharedDirScopeId2A}}<br /> <strong>[nghSharedScopeTesterCopy] Scope ID:</strong> {{sharedDirScopeId2B}}<br /> <strong>[nghIsolatedScopeTester] Scope ID:</strong> {{isolatedDirScopeId2}}<br /> <br /> <h3>4.3. Directives with inherited and isolated scope</h3> <button ngh-inherited-scope-tester="3A" ngh-inherited-scope-tester-copy="3B" ngh-isolated-scope-tester="3">Check scope IDs</button><br /> <br /> <strong>[nghInheritedScopeTester] Scope ID:</strong> {{inheritedDirScopeId3A}}<br /> <strong>[nghInheritedScopeTesterCopy] Scope ID:</strong> {{inheritedDirScopeId3B}}<br /> <strong>[nghIsolatedScopeTester] Scope ID:</strong> {{isolatedDirScopeId3}}<br /> <br /> <h3>4.4. Directives with shared and inherited scope</h3> <button ngh-shared-scope-tester="4A" ngh-shared-scope-tester-copy="4B" ngh-inherited-scope-tester="4A" ngh-inherited-scope-tester-copy="4B">Check scope IDs</button><br /> <br /> <strong>[nghSharedScopeTester] Scope ID:</strong> {{sharedDirScopeId4A}}<br /> <strong>[nghSharedScopeTesterCopy] Scope ID:</strong> {{sharedDirScopeId4B}}<br /> <strong>[nghInheritedScopeTester] Scope ID:</strong> {{inheritedDirScopeId4A}}<br /> <strong>[nghInheritedScopeTesterCopy] Scope ID:</strong> {{inheritedDirScopeId4B}}<br /> </div> </body> </html>
angular.module("mainModule", []) .controller("mainController", function ($scope) { $scope.firstName = "John"; $scope.lastName = "Doe"; $scope.updateName = function (firstName, lastName) { $scope.firstName = firstName; $scope.lastName = lastName; }; }) // 1. Shared scope (scope: false) .directive("nghSharedScopeDir", function () { return { scope: false, template: '<label>First name: <input type="text" ng-model="firstName"/></label><br />' + '<label>Last name: <input type="text" ng-model="lastName"/></label><br />' + '<br />' + '<strong>First name:</strong> {{firstName}}<br />' + '<strong>Last name:</strong> {{lastName}}' }; }) // 2. Inherited scope (scope: true) .directive("nghInheritedScopeDir", function () { return { scope: true, template: '<label>First name: <input type="text" ng-model="firstName"/></label><br />' + '<label>Last name: <input type="text" ng-model="lastName"/></label><br />' + '<br />' + '<strong>First name:</strong> {{firstName}}<br />' + '<strong>Last name:</strong> {{lastName}}' }; }) // 3. Isolated scope (scope: {}) .directive("nghIsolatedScopeDir", function () { return { scope: { firstName: '@dirFirstName', lastName: '=dirLastName', setNameMethod: '&dirUpdateNameMethod' }, template: '<label>First name: <input type="text" ng-model="firstName"/></label><br />' + '<label>Last name: <input type="text" ng-model="lastName"/></label><br />' + '<button ng-click="execSetNameMethod()">Set name on external scope</button><br />' + '<br />' + '<strong>First name:</strong> {{firstName}}<br />' + '<strong>Last name:</strong> {{lastName}}', link: function (scope, element, attrs) { scope.execSetNameMethod = function () { scope.setNameMethod( { newFirstName: scope.firstName, newLastName: scope.lastName }); }; } }; }) // 4. Scope tester: shared scope .directive("nghSharedScopeTester", function () { return { scope: false, link: function (scope, element, attrs) { element.on("click", function () { scope.$root.$$childHead["sharedDirScopeId" + attrs.nghSharedScopeTester] = scope.$id; scope.$root.$digest(); }); } }; }) .directive("nghSharedScopeTesterCopy", function () { return { scope: false, link: function (scope, element, attrs) { element.on("click", function () { scope.$root.$$childHead["sharedDirScopeId" + attrs.nghSharedScopeTesterCopy] = scope.$id; scope.$root.$digest(); }); } }; }) // 4. Scope tester: inherited scope .directive("nghInheritedScopeTester", function () { return { scope: true, link: function (scope, element, attrs) { element.on("click", function () { scope.$root.$$childHead["inheritedDirScopeId" + attrs.nghInheritedScopeTester] = scope.$id; scope.$root.$digest(); }); } }; }) .directive("nghInheritedScopeTesterCopy", function () { return { scope: true, link: function (scope, element, attrs) { element.on("click", function () { scope.$root.$$childHead["inheritedDirScopeId" + attrs.nghInheritedScopeTesterCopy] = scope.$id; scope.$root.$digest(); }); } }; }) // 4. Scope tester: isolated scope .directive("nghIsolatedScopeTester", function () { return { scope: { }, link: function (scope, element, attrs) { element.on("click", function () { scope.$root.$$childHead["isolatedDirScopeId" + attrs.nghIsolatedScopeTester] = scope.$id; scope.$root.$digest(); }); } }; }) .directive("nghIsolatedScopeTesterCopy", function () { return { scope: { }, link: function (scope, element, attrs) { element.on("click", function () { scope.$root.$$childHead["isolatedDirScopeId" + attrs.nghIsolatedScopeTesterCopy] = scope.$id; scope.$root.$digest(); }); } }; });

Example


Description


A directive can request different types of scope through the scope property of its definition object. We can identify 3 types:

  • shared
  • inherited
  • isolated

Shared scope

This type is requested when scope is false in the definition object or when scope is not specified (false is the default value of the property).

This case is shown in point 1 of the example. Shared scope simply means that the directive works with the same scope that is already available for the DOM node where the directive appears without creating a new one. So, in this specific case, we can see that the firstName and lastName model variables specified in the directive's template are in fact exactly the same variables defined on the scope used by mainController because that's exactly the scope that our custom directive uses. Every change on the model variables made in the text fields of the directive's template will be made on the shared scope and is visible even on the text fields declared directly in the main HTML page (they are bound to the same model variables).

Inherited scope

This type is requested when scope is true in the definition object.

This case is shown in point 2 of the example. A new scope is created for our directive, but it inherits all the properties of the parent scope through JavaScript's prototypal inheritance: when the directive accesses a property on it's new scope, the property is first searched on the current scope and if it's found then it's value is returned, otherwise, if it's not found, the same property name is searched in the parent scope and that value is returned (the search traverses all the parents until the property is found or until there are no more parents); if a value is assigned to a property on the new directive's scope and the same property already exists in the parent, accessing the property value directly in the new scope will in fact override the parent's property value and any change to the property in the parent will not be propagated anymore to the child scope.

Let's see this quickly by checking what happens in point 2 of the example. When you've just loaded the example, the scope of mainController (parent scope) has both the firstName and lastName model variables defined. nghInheritedScopeDir has a new scope (child scope) and those variables are not defined yet. The text fields of nghInheritedScopeDir read the firstName and lastName variables from the child scope and since they're not found, the values of the same properties in the parent scope are returned. If you try to change the values of firstName and lastName with the parent's text fields you'll see that the changes are reflected even in the directive's text fields. Now, if you try to change the values in the directive's text fields, you'll see that the values of the parent scope don't change anymore because you've in fact just defined the firstName and/or lastName properties on the child scope. From now on, every time you change the values in the parent's text fields, the values displayed in the directive's field don't change anymore.

Isolated scope

This type is requested when scope is an object {} in the definition object. The object of the scope property (called scope option) can be empty or can have some properties specified in the following ways:

  • One Way binding (read-only access)
    dirScopeProperty: '@dirAttributeName'

    dirScopeProperty is the name of the property we want to make available in the isolated scope, while dirAttributeName is the normalized name of an HTML attribute that appears in the same DOM node of the directive; the value of the HTML attribute can be a string or an AngularJS expression that is evaluated every time the model changes; if dirScopeProperty is assigned through the isolated scope then its new value is not reflected outside the isolated scope; if the HTML attribute's value is an expression, then every time the expression is evaluated it's new value is assigned to the specified property in the isolated scope;

  • Two Way binding (read/write access)
    dirScopeProperty: '=dirAttributeName'

    dirScopeProperty is the name of the property we want to make available in the isolated scope, while dirAttributeName is the normalized name of an HTML attribute that appears in the same DOM node of the directive; the value of the HTML attribute must be the name of an assignable model variable; if dirScopeProperty is assigned through the isolated scope then its new value is assigned also to the model variable specified in the HTML attribute; if the model variable of the HTML attribute changes its value, then the new value is assigned also to the associated property in the isolated scope;

  • Method binding
    dirScopeProperty: '&dirAttributeName'

    dirScopeProperty is the name of the method we want to make available in the isolated scope, while dirAttributeName is the normalized name of an HTML attribute that appears in the same DOM node of the directive; the value of the HTML attribute must be the name of a method with the parenthesis and the arguments names and that method must be available in the directive's parent scope; inside the directive, the external method can be called by invoking dirScopeProperty with a single parameter: dirScopeProperty's parameter is an object where each property name corresponds to an argument name defined in the HTML attribute and each value is the value we want to pass to that specific argument.

Let's take a look at point 3 of the example to see how this works. In the isolated scope, firstName gets the value from the dirFirstName HTML attribute (dirFirstName is the normalized version of dir-first-name). In this case the attribute's value is the string Michael, if it was an AngularJS expression if would've been evaluated. lastName gets the value from the dirLastName HTML attribute. The attribute's value is the lastName variable defined on the parent scope so whenever a new value is assigned to lastName in the isolated scope also the value of the lastName variable in the parent scope is changed and vice versa. setNameMethod is a method defined in the isolated scope that maps to the one specified in the dirUpdateNameMethod HTML attribute. The HTML attribute says updateName(newFirstName, newLastName) and this means that whenever we invoke setNameMethod on the isolated scope we're in fact invoking updateName on the parent scope. As you can see, whenever we call setNameMethod we pass also an object to map the values of firstName and lastName in the isolated scope to the newFirstName and newLastName arguments respectively (their names and position in the external method have been defined in the HTML attribute). The button defined in the nghIsolatedScopeDir's template has the execSetNameMethod associated to the click event to show that the method is called in the isolated scope and we've defined it in the link function of our directive.

If the name of the variable on the isolated scope is the same as the one of its bound HTML attribute, then you can use the following syntax in the scope option object:

  • myProperty: '@'
  • myProperty: '='
  • myProperty: '&'

In this case it is expected to find the my-property HTML attribute (remember that myProperty is the normalized version of my-property) on the DOM node and it will be bound to myProperty on the isolated scope.

Multiple directives on the same DOM node

In point 4 of the example we see what happens when multiple directives appear on the same DOM node and require different types of scopes. These rules are very important to keep in mind to fully understand how our custom directives could interact with the others on the same DOM node:

  • if there are only directives with a shared scope, then all of them will share the same parent scope
  • if there are only directives with an inherited scope, then a single new inherited scope is created for the DOM node and all the directives share the same scope object
  • if there are some directives with a shared scope and some with an inherited scope, then a single new inherited scope is created for the DOM node and all the directives share the same scope object (even those that requested the shared scope in fact receive the inherited scope and don't have direct access to the parent scope, they must go through prototypal inheritance)
  • only one directive with an isolated scope can appear in a DOM node otherwise AngularJS will throw an error
  • if there are some directives with a shared scope and a directive with an isolated scope, then those asking for the shared scope will receive the parent's scope, while the one requesting the isolated scope will get a new isolated scope object
  • if there are some directives with an inherited scope and a directive with an isolated scope, then those asking for the inherited scope will receive a single new inherited scope object to share among them, while the one requesting the isolated scope will get a new isolated scope object
  • if there are some directives with a shared scope, some with an inherited scope and a directive with an isolated scope, then those asking for the shared or inherited scope will receive a single new inherited scope object to share among them, while the one requesting the isolated scope will get a new isolated scope object

You can check these rules by interacting with the example. Each scope has an ID in its $id property. Whenever you press a button in the example, you can check the IDs of the scopes that each directive sees. The base scope is the one available inside mainController and the new scopes generated by our custom directives are its descendants in the scopes hierarchy. Our tester directives basically assign the scope ID directly to model variables on the base scope and call the $digest method to ask AngularJS to update the view since the model is changed (in this case the view would not be updated automatically whenever we assign a new ID variable to the base scope).