Chapter 3. AngularJS Scopes, Controllers, and Filters

The goal of this chapter is to understand AngularJS's scope object as well as controller and its use in AngularJS. The chapter starts with a brief description of the purpose of the scope object in AngularJS and its use. We will also cover AngularJS's built-in filters and how to develop a custom filter.

This chapter is organized as follows:

  • Understanding AngularJS's scope object
  • AngularJS's scope object's lifecycle
  • Understanding AngularJS controllers and its use:
    • Controller inheritance
  • Understanding the AngularJS built-in filter and learning how to develop a custom filter

Understanding the scope of AngularJS

In AngularJS, scope is an object that refers to AngularJS applying the model. Scope is an execution context for an expression. AngularJS's scope objects are organized in the data structures that mimic the DOM structure of the applying model. Scopes will watch expressions and propagate events.

Scope is the glue between an application's controller and the view. Throughout the model's linking section, the directives start with the $watch expression on the scope. The $watch expression permits the directives to be notified of the property changes and then permits the directives to render the updated value to the DOM.

AngularJS's scope objects have the following characteristics:

  • AngularJS scopes offer $watch, which is an application programming interface (API) to detect model transformations.
  • AngularJS scopes deliver $apply, which is an API used to proliferate any changes made in the model via the system's inside view from the outside of the AngularJS framework. For example, controller, services, and AngularJS event handlers.
  • Scopes are often nested to limit access to the properties of application elements by providing access to shared model properties. Nested scopes are either child scopes or isolated scopes. A child scope inherits properties from its parent scope. The local isolated scope does not see isolated scopes for the additional data.
  • AngularJS expressions are evaluated against the context provided by the scope. For example, the {{name}} AngularJS expression has no meaning until it is evaluated against the AngularJS scope, which defines the name property.

In AngularJS, both controllers and directives have reference to the scope, however, not to one another. This arrangement isolates the controller from the directive in addition to the DOM. This can be a crucial purpose since it makes the controllers view agnostic, which greatly improves the testing story of the application.

The following code shows an example of scope rendering:

var app = angular.module('myApp', []);

app.controller('ctrlScopeExample', function($scope) { 

$scope.name = 'my name';

$scope.sayHello = function() { 

$scope.greeting = 'Hello ' + $scope.name + '!';

}; 

});

We will render HTML, as shown in the following code:

<div ng-app="myApp" ng-controller="ctrlScopeExample">
Your name:
<input type="text" ng-model="name">
<button ng-click='sayHello()'>Say Hello</button>
<hr>
{{greeting}}
</div>

As seen in the preceding example, in the crtlScopeExample controller, we assigned the "my name" value to the name scope property. The AngularJS scope object will assign this value to the <input> element of HTML using the ng-model AngularJS built-in directive, such as <input type="text" ng-model="name">. This exhibits how controllers write data to HTML tags using scope.

In a similar way, the controller can assign data from the HTML element to the sayHello function declared inside the controller using the scope. The sayHello function will get invoked when the user clicks on the button shown in the preceding code, <button ng-click='sayHello()'>Say Hello</button>. The sayHello function will get the value from the DOM element and assign it to the new property, greeting.

Sensibly, the rendering of {{greeting}} contains:

  • Retrieval of the scope associated with the DOM element where {{name}} is described in the HTML view
  • Evaluation of the name property of the scope retrieved previously, assignment of the result to the greeting property of the scope, and rendering of the HTML element

AngularJS scope's chain of command

Each application in AngularJS has one root scope, but may have several child scopes. The AngularJS application can have multiple child scopes because some AngularJS directives create a child scope, which we discussed in Chapter 2, Learning AngularJS Binding and Directives. When a new scope is created, it will be added as a child scope to its parent scope. This will create a tree structure of the HTML element they are attached to. The following code snippet shows the scope hierarchies:

var app = angular.module('myApp', []);

app.controller('ctrlParentScope', function ($scope, $rootScope) {

    $rootScope.car = 'Toyota';

});

app.controller('ctrlChildScope1', function ($scope) {

    $scope.car = 'Ford';

});

app.controller('ctrlChildScope2', function ($scope, $rootScope) {

    $scope.car = $rootScope.car;

});

We can render the HTML elements, as follows:

<div ng-app="myApp" ng-controller="ctrlParentScope">
<p> Car name from parent scope is {{car}}</p>
</div>

<div ng-controller="ctrlChildScope1">
<p> Car name from child scope is {{car}}</p>
</div>

<div ng-controller="ctrlChildScope2">
<p> Car from parent scope is {{car}}</p>
</div>

This code can be written as shown in the following screenshot:

AngularJS scope's chain of command

As shown in the preceding code, there are three controllers that are declared: ctrlParentScope, ctrlChildScope1, and ctrlChildScope2. In the ctrlParentScope controller, the $rootScope provider is set when the module initializes and all the controllers that are inherited using the $rootScope provider get their instance of the parent scope and can be used independently. We can read and overwrite the parent scope values. When the preceding code snippet is executed, it creates the markup shown in the preceding figure. It is noticeable that the AngularJS framework automatically adds the ng-scope class on the elements. In the ctrlParentScope controller, a car property is created and consigned to $rootScope, and a value of Toyota is assigned to this property. The ctrlChildScope1 controller does not inherit from the ctrlParentScope controller and the property car has the Ford value. The ctrlChildScope2 controller is inherited from the ctrlParentScope controller by injecting $rootScope and assigning its local property car to the $ctrlParentScope property car.

The output of all the controllers is shown in the following figure:

AngularJS scope's chain of command

The AngularJS scope lifecycle

A browser's standard stream of receiving an event is that it executes a corresponding JavaScript callback. When the browser completes the callback, it rerenders the DOM elements and waits for more events. When the browser calls the JavaScript functions, the functions get executed outside the AngularJS framework execution environment, which suggests that AngularJS is unaware of model modifications. In order to properly execute the model modifications, the execution should start the AngularJS framework execution context using the $apply method. Model modifications that get executed within the $apply methodology are properly accounted for by the AngularJS framework.

For instance, if the ng-controller attribute is implemented in HTML, the ng-controller attribute should evaluate the expression within the $apply methodology. The $apply method will execute a $digest method after assessing the attributes in HTML. In the $digest part, the scope examines all of the $watch expressions and relates them with the prior value. The comparison gets completed asynchronously, which suggests that assigning a value to $scope, such as $scope.car ="Ford", will not instantly notify a $watch method, instead the $watch notification is delayed until the $digest phase. This delay is fascinating since it merges multiple model updates in one $watch notification guaranteeing that in the $watch notification, no alternative $watches are running. If $watch changes the value of the model, it will force further $digest cycles. The following are the steps that describe the lifecycle of AngularJS's scope object:

  • Scope creation: By $injector, the root scope is created through the application Bootstrap. Directives create new child scopes during the process of template linking.
  • Watch registration: The directive registers watches on the scope during template linking. To broadcast the model values to HTML, these watches will be used.
  • Model transformation: No additional $apply call is required while synchronizing within the controller's $http, $timeout, or $interval services. The AngularJS API implicitly calls additional $apply methods.
  • Transformation observation: The AngularJS framework implements a $digest method on the root scope at the end of $apply, which can then be transmitted during all the child scopes. Through the $digest cycle, all the $watch expressions or functions will be tested for model transformation. If any transformations are identified, the $watch listener will be called.
  • Destruction of scope: The child scope creator will destroy the child scopes using the scope.$destroy() API when they are not in use. This will stop the $digest call into the child scope, and the transmission and memory used by the child scope model will be retrieved by the garbage collector.

The following figure shows how the browser events interact with AngularJS's scope object:

The AngularJS scope lifecycle

To explain the preceding figure, we will use the hello world example. In this example, data binding is achieved when the user enters text in the text field. The compilation runs as follows:

  • Compilation part:
    • The ng-model AngularJS directive is set up on the HTML <input> element using the keydown event
    • The interpolation executes $watch to notify any name change in the HTML <input> element
  • Runtime part:
    • When the user presses any key, the keydown event will be fired
    • The directive will then get the text change in the <input> element and call $apply to update the model inside the AngularJS context execution
    • The $digest loop begins
    • $watch identifies the changes in the name property of the directive and notifies the interpolation
    • AngularJS exits the execution context and within the JavaScript context execution, the keydown event will also exit
    • The browser will render the page and update the text

Adding behavior to the AngularJS scope

In the AngularJS framework, usually, the child scope inherits from its parent scope. However, the one exception to this rule is an AngularJS directive that uses the scope construct. This construct is regularly used while developing a reusable directive. In the directives, the parent scope is used by default. Using the parent scope in the directive, we can add behavior to the scope. The following code shows how to add behavior to the scope:

var app = angular.module('myApp', []);

app.directive("enter", function () {

return function (scope, element, attr) {

element.bind("mouseenter", function () {

$('#divMessage').empty();

$('#messageHeading').empty();          

$('#messageHeading').append('Message'),

$('#divMessage').html('My favorite car color is <span class=' + attr.enter + ' ></span></br></br>'),

$('#divMessageDialog').dialog({
model: true,
width:'auto',
…
});

app.directive("leave", function () {

    return function (scope, element, attr) {

        element.bind("mouseleave", function () {

            $('#divMessageDialog').dialog('close'),

        …
});

We can render the HTML elements as follows:

<body ng-app="myApp">
…
<div enter="redCarColor" leave>
<span class="redCarColor"></span>&nbsp;&nbsp;
  Select car color
</div><br />

<div enter="blueCarColor" leave>
<span class="blueCarColor"></span>&nbsp;&nbsp;
  Select car color
</div><br />

<div enter="greenCarColor" leave>
<span class="greenCarColor"></span>&nbsp;&nbsp;
  Select car color
</div>
<div id="divMessageDialog"></div>
…
</body>

The Cascade Style Sheet (CSS) used is as shown:

.redCarColor {
    background-color:darkred;
    width:20px; 
    height:20px; 
    display:inline-block; 
    margin-top:10px
}

.blueCarColor {
    background-color:lightblue;
    width:20px; 
    height:20px; 
    display:inline-block; 
    margin-top:10px
}

.greenCarColor {
    background-color:lightgreen;
    width:20px; 
    height:20px; 
    display:inline-block; 
    margin-top:10px
}

In the preceding code, two directives enter and leave are created. Inside each directive, there are linking functions; enter is bound with mouseenter and leave is bound with mouseleave. As the page is loaded and we move our mouse over the Select car color element, the jQuery dialog box shows the My favorite color is message. When we leave the Select car color element, the jQuery dialog box is closed, as shown in the following screenshot:

Adding behavior to the AngularJS scope

Different CSS classes are used to display the selected color. We can add the selected CSS class when the mouseenter event is fired and remove it when the mouseleave event gets fired. The element parameter for any AngularJS element means that jQLite methods will be called when the event gets fired. We use a string parameter to pass from the view into the attrs directive and pass that as a parameter to the jQLite API method call using attrs.enter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset