Scopes - m-kostira/angularjs-notes GitHub Wiki

AngularJS version: 1.7.7

The scope is a JavaScript object with properties and methods, which are available to both the view and the controller. You can

  • define scope properties and methods in the controller
  • assign values to scope properties in the view (as html tag attributes of directives, or via text inputs with ng-model)
  • reference scope properties in the view with {{}} expressions
  • call scope methods from the view, e.g. on click

Scopes within an application are hierarchical, with child scopes inheriting properties and methods from the parent scope via JavaScript prototypical inheritance. The scope hierarchy is related to the DOM hierarchy; certain directives(ng-controller and ng-repeat) create a new scope, which is associated with the directive's tag and all its children tags. [see https://docs.angularjs.org/tutorial/step_06 for hierarchy diagram]
Custom directives do not create a new scope by default, but they may. Directives may be declared with 'isolate' scope, meaning that it does not inherit from the parent scope. Components (an opinionated directive introduced in later versions) create an isolate scope by default. Using isolate scope is a best practice to eliminate tight coupling between components.

Isolate scope

Note: There are a number of ways to pass properties into an isolate scope and the mechanisms are unintuitive, with an inconsistent and bizarre syntax. Angular magically syncs properties in the background making the logic hard to reason about. We will try to clear things up below. To keep things simple, we use components as opposed to directives.

There are a number of ways to pass data into a component that has isolate scope. One way is through services injected into the component. The other way is to receive properties from the parent scope. To do this, you need to specify the passed property in two places:

  • the component tag
  • the component definition object, in the bindings property

In the component tag:

<child-component movie="$ctrl.parentMovie" />

In the component definition object:

angular.module('heroApp').component('childComponent', {
  templateUrl: 'childComponent.html',
  controller: ChildController,
  bindings: {
    childMovie: '=movie', 
  }
});

function ChildController() {
   console.log(childMovie); // outputs value of movie in parent scope
}

In the above example, the childMovie property in the child scope will be bound to the parentMovie property in the parent scope. Note that the property is available immediately to the ChildController function.

Note: There is a shorthand syntax if the property name in the child scope matches the tag attribute:

bindings: {
    movie: '=', 
}

The = symbol is one of several prefixes that define the type of binding to establish between the child scope property and the parent scope property. The others are <, @, and &. [Official docs here]

  • = establishes a two way binding. Changing the property in the parent will result in change in the child, and vice versa.

  • < establishes a one way binding, parent to child. Changing the property in the parent will result in change in the child, but changing the child property will not affect the parent property.

  • @ will assign to the child property the string value of the tag attribute. If the attribute contains an expression, the child property updates whenever the expression evaluates to a different string. For example:

<child-component description="The movie title is {{$ctrl.movie.title}}" />
bindings: {
    description: '@', 
}

Here, the description property in the child scope will be the current value of the expression "The movie title is {{$ctrl.movie.title}}", where movie is an object in the parent scope.

  • & is a bit tricky, and in fact there seems to be no compelling reason to ever use it. It allows you to evaluate an expression in the parent scope, substituting parameters with variables from the child scope. An example (plunk):
<child-component 
  foo = "myVar + $ctrl.parentVar + myOtherVar"
</child-component>
angular.module('heroApp').component('childComponent', {
  template: "<div>{{  $ctrl.parentFoo({myVar:5, myOtherVar:'xyz'})  }}</div>",
  bindings: {
    parentFoo: '&foo'
  }
});

Given parentVar=10, the expression parentFoo({myVar:5, myOtherVar:'xyz'}) will evaluate to 5 + 10 + 'xyz' and the component will render as:

<div>15xyz</div>

When would you ever want to use this convoluted functionality? & is often used by people to pass to the child scope a callback function in the parent scope. In reality, however, the same effect can be achieved by using '<' to pass the function, which is more straightforward and avoids the awkward curly braces syntax to pass parameters ({myVar:5, myOtherVar:'xyz'}). Consider:

Callback using &:

<child-component parent-foo="$ctrl.foo(bar)"/>
angular.module('heroApp').component('childComponent', {
  template: '<button ng-click="$ctrl.parentFoo({bar:'xyz'})">Call foo in parent</button>',
  bindings: {
    parentFoo: '&'
  }
});

Callback using <:

<child-component parent-foo="$ctrl.foo"/>
angular.module('heroApp').component('childComponent', {
  template: '<button ng-click="$ctrl.parentFoo('xyz')">Call foo in parent</button>',
  bindings: {
    parentFoo: '<'
  }
});

Note that objects (and arrays) are passed by reference to the child scope, not copied. What this means is that even if it's a one-way binding, you are working with the same object in both the parent and the child scope.


Hands on example

To see the different prefixes in action, open this plunk.

One-time binding(initialization) using ::

[Official docs]
Later versions of AngularJS introduce the option to have a one-time binding, where the child scope property is updated only once. This improves performance by eliminating the need to watch the parent property. The syntax is different from above; to declare a one-time binding, you add :: in front of the expression in the component tag:

<child-component 
  tagline = "::$ctrl.tagline">
</child-component>

This will propagate the value of tagline to the child scope without establishing a one-way or two-way binding. Note: if tagline is initially undefined in the parent scope, angular will watch it until it changes and then make a one-time update of the corresponding property in the child scope.

Summary

The table below shows how the prefixes work depending on whether the property is an object, array, string, etc. How the various isolate scope bindings work

Note: The syntax for directives is a bit different than that for components. If dealing with legacy code or you need to use directives, see https://www.3pillarglobal.com/insights/angularjs-understanding-directive-scope for a discussion of prefixes in the context of directives.

Scope hierarchies

https://docs.angularjs.org/guide/scope#scope-hierarchies

Further resources:

https://stackoverflow.com/a/13033249/9226401

⚠️ **GitHub.com Fallback** ⚠️