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:
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:
$watch
, which is an application programming interface (API) to detect model transformations.$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.{{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:
{{name}}
is described in the HTML viewname
property of the scope retrieved previously, assignment of the result to the greeting
property of the scope, and rendering of the HTML elementEach 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:
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:
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:
$injector
, the root scope is created through the application Bootstrap. Directives create new child scopes during the process of template linking.$apply
call is required while synchronizing within the controller's $http
, $timeout
, or $interval
services. The AngularJS API implicitly calls additional $apply
methods.$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.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:
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:
ng-model
AngularJS directive is set up on the HTML <input>
element using the keydown
event$watch
to notify any name change in the HTML <input>
elementkeydown
event will be fired<input>
element and call $apply
to update the model inside the AngularJS context execution$digest
loop begins$watch
identifies the changes in the name
property of the directive and notifies the interpolationkeydown
event will also exitIn 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> Select car color </div><br /> <div enter="blueCarColor" leave> <span class="blueCarColor"></span> Select car color </div><br /> <div enter="greenCarColor" leave> <span class="greenCarColor"></span> 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:
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
.