AngularJS is a JavaScript framework. It is a library written in JavaScript. AngularJS extends HTML attributes with Directives. AngularJS expressions can be written inside HTML
AngularJS Extends HTML
AngularJS extends HTML with ng-directives (AngularJS directives are HTML attributes with an ng prefix. ) AngularJS lets you extend HTML with new attributes called Directives.
- The ng-app directive defines the root element of an AngularJS application(defines an AngularJS application).
It tells AngularJS that the <div> element is the "owner" of an AngularJS application.
Technically possible to have several applications per page, only one ng-app directive will be automatically instantiated and initialized by the Angular framework. we need to bootstrap the modules to have multiple ng-app within the same page.
- The ng-model directive binds the value of HTML controls (input, select, textarea) to application data.
It binds the value of the input field to the application variable name.
The ng-model directive can also:
Provide type validation for application data (number, email, required).
Provide status for application data (invalid, dirty, touched, error).
Provide CSS classes for HTML elements.
Bind HTML elements to HTML forms.
Provide status for application data (invalid, dirty, touched, error).
Provide CSS classes for HTML elements.
Bind HTML elements to HTML forms.
- The ng-bind directive binds application data to the HTML view.
It binds the innerHTML of the <p> element to the application variable name.
- The ng-init directive initialize AngularJS application variables. It initializes application data and defines initial values for an AngularJS application. Normally, we'll not use ng-init. We'll use a controller or module instead.
- The ng-controller directive defines the application controller.
AngularJS applications are controlled by controllers. It control the data of AngularJS applications.
A controller is a JavaScript Object, created by a standard JavaScript object constructor.
<div ng-app="myApp" ng-controller="myCtrl">
First Name: <input type="text" ng-model="firstName"><br>
Last Name: <input type="text" ng-model="lastName"><br>
<br>
Full Name: {{firstName + " " + lastName}}
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.firstName = "John";
$scope.lastName = "Doe";
});
</script>
First Name: <input type="text" ng-model="firstName"><br>
Last Name: <input type="text" ng-model="lastName"><br>
<br>
Full Name: {{firstName + " " + lastName}}
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.firstName = "John";
$scope.lastName = "Doe";
});
</script>
In the above example, ng-app="myApp" creates AngularJs application and the application runs inside the <div>. The ng-controller="myCtrl" defines a controller and myCtrl function is a JavaScript function. It invoke the controller with a $scope object. $scope is the application object. The controller creates two properties (variables) in the scope (firstName and lastName). The ng-model directives bind the input fields to the controller properties (firstName and lastName).
It is common to store controllers in external files:
<script src="{YourControllerFileName}.js"></script>
Controller Methods
The example above demonstrated a controller object with two properties: lastName and firstName.
A controller can also have methods (variables as functions):
<div ng-app="myApp" ng-controller="personCtrl">
First Name: <input type="text" ng-model="firstName"><br>
Last Name: <input type="text" ng-model="lastName"><br>
<br>
Full Name: {{fullName()}}
</div>
<script>
var app = angular.module('myApp', []);
app.controller('personCtrl', function($scope) {
$scope.firstName = "John";
$scope.lastName = "Doe";
$scope.fullName = function() {
return $scope.firstName + " " + $scope.lastName;
}
});
</script>
First Name: <input type="text" ng-model="firstName"><br>
Last Name: <input type="text" ng-model="lastName"><br>
<br>
Full Name: {{fullName()}}
</div>
<script>
var app = angular.module('myApp', []);
app.controller('personCtrl', function($scope) {
$scope.firstName = "John";
$scope.lastName = "Doe";
$scope.fullName = function() {
return $scope.firstName + " " + $scope.lastName;
}
});
</script>
- The ng-repeat directive clones HTML elements once for each item in a collection (in an array).
Example:
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<body>
<div ng-app="" ng-init="firstName='John'">
<p>Name: <input type="text" ng-model="name"></p>
<p ng-bind="name"></p>
OR
<p>{{name}}</p>
<p>The name is <span ng-bind="firstName"></span></p>
OR
<p>The name is {{firstName}}</p>
</div>
</body>
</html>
AngularJS modules define applications:
var app = angular.module('myApp', []);
AngularJS controllers control applications:
app.controller('myCtrl', function($scope) {
$scope.firstName= "John";
$scope.lastName= "Doe";
});
AngularJS expressions are written inside double braces: {{ expression }}.
AngularJS expressions binds data to HTML the same way as the ng-bind directive.
Example:
<p>The name is <span ng-bind="firstName"></span></p>
OR
<p>The name is {{firstName}}</p>
If we remove the ng-app directive, HTML will display the expression as it is, without solving it.
we can bind data to HTML in two ways:
<div ng-app="" ng-init="firstName='John';lastName='Doe'">
<p>The name is {{ firstName + " " + lastName }}</p>
</div>
OR
<div ng-app="" ng-init="firstName='John';lastName='Doe'">
<p>The name is <span ng-bind="firstName + ' ' + lastName"></span></p>
</div>
AngularJS Objects
AngularJS objects are like JavaScript objects:
<div ng-app="" ng-init="person={firstName:'John',lastName:'Doe'}">
<p>The name is {{ person.lastName }}</p>
</div>
<p>The name is {{ person.lastName }}</p>
</div>
AngularJS Arrays
AngularJS arrays are like JavaScript arrays:<div ng-app="" ng-init="points=[1,15,19,2,40]">
<p>The third result is {{ points[2] }}</p>
</div>
In the above example, the {{ firstName }} expression is an AngularJS data binding expression.
{{ firstName }} is synchronized with ng-model="firstName", binds the value of the firstName input field to the application firstName variable name.
In the below example two text fields are synchronized with two ng-model directives:
<div ng-app="" ng-init="quantity=1;price=5">
Quantity: <input type="number" ng-model="quantity">
Costs: <input type="number" ng-model="price">
Total in dollar: {{ quantity * price }}
</div>
The ng-repeat directive repeats an HTML element and it used on an array of objects.
<div ng-app="" ng-init="names=[
{name:'Jani',country:'Norway'},
{name:'Hege',country:'Sweden'},
{name:'Kai',country:'Denmark'}]">
<ul>
<li ng-repeat="x in names">
{{ x.name + ', ' + x.country }}
</li>
</ul>
</div>
AngularJS Filters
Filters can be added to expressions and directives using a pipe character.
AngularJS filters can be used to transform data. A filter can be added to an expression with a pipe character (|).
Filter in expression
The uppercase filter format strings to upper case:
<div ng-app="myApp" ng-init="lastName ='John'">
<p>The name is {{ lastName | uppercase }}</p>
</div>
<p>The name is {{ lastName | uppercase }}</p>
</div>
<div ng-app="myApp" ng-init="lastName ='John'">
<p>The name is {{ lastName | lowercase }}</p>
</div>
The currency filter formats a number as currency:
<div ng-app="myApp" ng-controller="costCtrl">
<input type="number" ng-model="quantity">
<input type="number" ng-model="price">
<p>Total = {{ (quantity * price) | currency }}</p>
</div>
Filter in directives
A filter can be added to a directive with a pipe character (|).
The orderBy filter orders an array by an expression:
<div ng-app="myApp" ng-controller="namesCtrl">
<ul>
<li ng-repeat="x in names | orderBy:'country'">
{{ x.name + ', ' + x.country }}
</li>
</ul>
<div>
The filter filter selects a subset of an array:
<div ng-app="myApp" ng-controller="namesCtrl">
<p><input type="text" ng-model="test"></p>
<ul>
<li ng-repeat="x in names | filter:test | orderBy:'country'">
{{ (x.name | uppercase) + ', ' + x.country }}
</li>
</ul>
</div>
AngularJS AJAX - $http
AngularJS $http is a core service for reading data from web servers.
$http.get(url) is the function to use for reading server data.
<div ng-app="myApp" ng-controller="customersCtrl">
<ul>
<li ng-repeat="x in names">
{{ x.Name + ', ' + x.Country }}
</li>
</ul>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('customersCtrl', function($scope, $http) {
$http.get("http://www.w3schools.com/angular/customers.php")
.success(function(response) {$scope.names = response.records;});
});
</script>
<ul>
<li ng-repeat="x in names">
{{ x.Name + ', ' + x.Country }}
</li>
</ul>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('customersCtrl', function($scope, $http) {
$http.get("http://www.w3schools.com/angular/customers.php")
.success(function(response) {$scope.names = response.records;});
});
</script>
In the above example, $http is an XMLHttpRequest object for requesting external data.
$http.get() reads JSON data from http://www.w3schools.com/angular/customers.php. If success, the controller creates a property (names) in the scope, with JSON data from the server.
Display the Table Index ($index)
To display the table index, add a <td> with $index:
<table>
<tr ng-repeat="x in names">
<td>{{ $index + 1 }}</td>
<td>{{ x.Name }}</td>
<td>{{ x.Country }}</td>
</tr>
</table>
<tr ng-repeat="x in names">
<td>{{ $index + 1 }}</td>
<td>{{ x.Name }}</td>
<td>{{ x.Country }}</td>
</tr>
</table>
Using $even and $odd
To identify the even and odd rows in a table.
<table>
<tr ng-repeat="x in names">
<td ng-if="$odd" style="background-color:#f1f1f1">{{ x.Name }}</td>
<td ng-if="$even">{{ x.Name }}</td>
<td ng-if="$odd" style="background-color:#f1f1f1">{{ x.Country }}</td>
<td ng-if="$even">{{ x.Country }}</td>
</tr>
</table>
<tr ng-repeat="x in names">
<td ng-if="$odd" style="background-color:#f1f1f1">{{ x.Name }}</td>
<td ng-if="$even">{{ x.Name }}</td>
<td ng-if="$odd" style="background-color:#f1f1f1">{{ x.Country }}</td>
<td ng-if="$even">{{ x.Country }}</td>
</tr>
</table>
AngularJS HTML DOM
The ng-disabled directive binds AngularJS application data to the disabled attribute of HTML elements.
<div ng-app="myApp" ng-init="mySwitch=true">
<p>
<button ng-disabled="mySwitch">Click Me!</button><p>
</p>
<p>
<input type="checkbox" ng-model="mySwitch">Button
</p>
<input type="checkbox" ng-model="mySwitch">Button
</p>
<p>
{{ mySwitch }}
</p>
</div>
It binds the application data mySwitch to the HTML button's disabled attribute. The ng-model directive binds the value of the HTML checkbox element to the value of mySwitch.
If the value of mySwitch evaluates to true, the button will be disabled.
The ng-show directive shows or hides an HTML element. It shows (or hides) an HTML element based on the value of ng-show. It can also be used to set the visibility of a part of an application.
</p>
We can use any expression that evaluates to true or false:
<div ng-app="myApp" ng-init="hour=13">
<p ng-show="hour > 12">I am visible.</p>
</div>
The ng-hide directive hides or shows an HTML element. It can be used to set the visibility of a part of an application.
<div ng-app="myApp">
<p ng-hide="true">I am not visible.</p>
<p ng-hide="false">I am visible.</p>
</div>
<div ng-app="myApp" ng-controller="personCtrl">
<button ng-click="toggle()">Toggle</button>
<p ng-hide="myVar">
First Name: <input type="text" ng-model="firstName"><br>
Last Name: <input type="text" ng-model="lastName"><br>
<br>
Full Name: {{firstName + " " + lastName}}
</p>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('personCtrl', function($scope) {
$scope.firstName = "John",
$scope.lastName = "Doe"
$scope.myVar = false;
$scope.toggle = function() {
$scope.myVar = !$scope.myVar;
};
});
</script>
The ng-if directive removes an HTML element. Instead of changing its visibility via the display css property, it completely removes element. When an element is removed using ng-If its scope is destroyed and a new scope is created when the element is restored.
If the value of mySwitch evaluates to true, the button will be disabled.
The ng-show directive shows or hides an HTML element. It shows (or hides) an HTML element based on the value of ng-show. It can also be used to set the visibility of a part of an application.
<div ng-app="myApp" ng-init="mySwitch=true">
<p>
<button ng-show="mySwitch">Click Me!</button><p>
</p>
<p>
<input type="checkbox" ng-model="mySwitch">Button
</p>
<input type="checkbox" ng-model="mySwitch">Button
</p>
<p>
{{ mySwitch }}
</p>
</div>
We can use any expression that evaluates to true or false:
<div ng-app="myApp" ng-init="hour=13">
<p ng-show="hour > 12">I am visible.</p>
</div>
The ng-hide directive hides or shows an HTML element. It can be used to set the visibility of a part of an application.
<div ng-app="myApp">
<p ng-hide="true">I am not visible.</p>
<p ng-hide="false">I am visible.</p>
</div>
<div ng-app="myApp" ng-controller="personCtrl">
<button ng-click="toggle()">Toggle</button>
<p ng-hide="myVar">
First Name: <input type="text" ng-model="firstName"><br>
Last Name: <input type="text" ng-model="lastName"><br>
<br>
Full Name: {{firstName + " " + lastName}}
</p>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('personCtrl', function($scope) {
$scope.firstName = "John",
$scope.lastName = "Doe"
$scope.myVar = false;
$scope.toggle = function() {
$scope.myVar = !$scope.myVar;
};
});
</script>
The ng-if directive removes an HTML element. Instead of changing its visibility via the display css property, it completely removes element. When an element is removed using ng-If its scope is destroyed and a new scope is created when the element is restored.
<label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/>
Show when checked:
<span ng-if="checked" class="animate-if">
This is removed when the checkbox is unchecked.
</span>
By using ng-show/ng-hide, The element is shown or hidden by removing or adding the .ng-hide CSS class onto the element. The .ng-hide CSS class is predefined in AngularJS and sets the display style to none (using an !important flag).
Don't confuse with ng-if, ng-show/ng-hide, ng-if will remove elements from DOM. I.e anything else attached to those elements will be lost. For example, if we bound a click handler to one of child elements, when ng-if evaluates to false, that element will be removed from DOM and our click handler will not work any more, even after ng-if later evaluates to true and displays the element. You will need to reattach the handler.
ng-show/ng-hide does not remove the elements from DOM. It uses CSS styles to hide/show elements. This way our handlers that were attached to children will not be lost.
The ng-click directive defines an AngularJS click event.
<div ng-app="myApp" ng-controller="myCtrl">
<button ng-click="count = count + 1">Click me!</button>
<p>{{ count }}</p>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.count = 0;
});
</script>
app.controller('MainCtrl', function() {
$scope.name = "Foo";
$scope.changeFoo = function() {
$scope.name = "Bar";
}
});
{{ name }}
<button ng-click="changeFoo()">Change the name</button>
Here we have only one $watch because ng-click doesn’t create any watches (the function is not going to change :P).
1. We press the button.
2. The browser receives an event which will enter the angular context (I will explain why, later in this article).
3. The $digest loop will run and will ask every $watch for changes.
4. Since the $watch which was watching for changes in $scope.name reports a change, if will force another $digest loop.
5. The new loop reports nothing.
6. The browser gets the control back and it will update the DOM reflecting the new value of $scope.name
The important thing here is that EVERY event that enters the angular context will run a $digest loop. That means that every time we write a letter in an input, the loop will run checking every $watch in this page.
Example -1:
When you will click on second button, the data binding is not updated. Since $scope.$digest() is not called after the second button's event listener is executed. In this way on clicking the second button the time will be updated in the $scope.data.time variable, but the new time will never displayed.
<script type="text/javascript">
document.getElementById("updateTimeButton").addEventListener('click', function () {
console.log("update time clicked");
$scope.datetime = new Date();
//to update $scope
$scope.$digest();
console.log($scope.datetime);
});</script>
To fix this issue you need to add a $scope.$digest() call to the second button event listener
Don't confuse with ng-if, ng-show/ng-hide, ng-if will remove elements from DOM. I.e anything else attached to those elements will be lost. For example, if we bound a click handler to one of child elements, when ng-if evaluates to false, that element will be removed from DOM and our click handler will not work any more, even after ng-if later evaluates to true and displays the element. You will need to reattach the handler.
ng-show/ng-hide does not remove the elements from DOM. It uses CSS styles to hide/show elements. This way our handlers that were attached to children will not be lost.
The ng-click directive defines an AngularJS click event.
<div ng-app="myApp" ng-controller="myCtrl">
<button ng-click="count = count + 1">Click me!</button>
<p>{{ count }}</p>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.count = 0;
});
</script>
AngularJS Forms
An AngularJS form is a collection of input controls.
<html>
<body>
<div ng-app="myApp" ng-controller="formCtrl">
<form novalidate>
First Name:<br>
<input type="text" ng-model="user.firstName"><br>
Last Name:<br>
<input type="text" ng-model="user.lastName">
<br><br>
<button ng-click="reset()">RESET</button>
</form>
<p>form = {{user}}</p>
<p>master = {{master}}</p>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('formCtrl', function($scope) {
<form novalidate>
First Name:<br>
<input type="text" ng-model="user.firstName"><br>
Last Name:<br>
<input type="text" ng-model="user.lastName">
<br><br>
<button ng-click="reset()">RESET</button>
</form>
<p>form = {{user}}</p>
<p>master = {{master}}</p>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('formCtrl', function($scope) {
//Assign initial values to the master object
$scope.master = {firstName: "John", lastName: "Doe"};
$scope.master = {firstName: "John", lastName: "Doe"};
//Create reset().
$scope.reset = function() {
$scope.user = angular.copy($scope.master);
};
$scope.reset(); //Calling reset(), to initialize the user data
});
</script>
$scope.reset = function() {
$scope.user = angular.copy($scope.master);
};
$scope.reset(); //Calling reset(), to initialize the user data
});
</script>
</body>
</html>
In the above example, The ng-model directive binds two input elements to the user object in the model.
The formCtrl function sets initial values to the master object, and defines the reset() method. The reset() method sets the user object equal to the master object. The ng-click directive invokes the reset() method, only if the button is clicked The novalidate attribute is new in HTML5. It disables any default browser validation.
$rootScope
The $rootScope is the top-most scope. An app can have only one $rootScope which will be shared among all the components of an app. Hence it acts like a global variable. All other $scopes are children of the $rootScope.
<html>
<body ng-app="myApp">
<div ng-controller="Ctrl1" style="border:2px solid blue; padding:5px">
Hello {{msg}}!
<br />
Hello {{name}}!
(rootScope)
</div>
<br />
<div ng-controller="Ctrl2" style="border:2px solid green; padding:5px">
Hello {{msg}}!
<br />
Hey {{myName}}!
<br />
Hi {{name}}! (rootScope)
<br/>
<button ng-click="change()">Change root variable</button>
</div>
<br/>
<script src= "angular.min.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('Ctrl1', function ($scope, $rootScope) {
$scope.msg = 'World';
$rootScope.name = 'AngularJS';
});
app.controller('Ctrl2', function ($scope, $rootScope) {
$scope.msg = 'Dot Net Tricks';
$scope.myName = $rootScope.name;
$scope.change = function() {
$rootScope.name = "(root variable value modified)";
};
});
</script>
</body>
</html>
Angular Context
When the browser receives an event that can be managed by the angular context and the $digest loop will be fired.The $watch list
Every time we bind something in the UI angular insert a $watch in a $watch list. Imagine the $watch as something that is able to detect changes in the model it is watching
Exaple-1:
User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />
Here, we have $scope.user, which is bound to the first input, and we have $scope.pass, which is bound to the second one. Doing this we add two $watch to the $watch list.
Example-2:
app.controller('MainCtrl', function($scope) {
$scope.foo = "Foo";
$scope.world = "World";
});
Hello, {{ World }}
Here, even though we have two things attached to the $scope, only one is bound. So in this case we only created one $watch.
Example-3:
app.controller('MainCtrl', function($scope) {
$scope.people = [...];
});
<ul>
<li ng-repeat="person in people">
{{person.name}} - {{person.age}}
</li>
</ul>
How many $watch are created here? Two for each person (for name and age) in people plus one for the ng-repeat. If we have 10 people in the list it will be (2 * 10) + 1, I.e 21 $watch's.
Everything that is bound in our UI using directives creates a $watch.When our template is loaded, Also known as in the linking phase, the compiler will look for every directive and creates all the $watch that are needed.
we already know that every binding we set has its own $watch to update the DOM when is needed
The $scope.watch() function is used to observe changes in a variable on the $scope. It accepts three parameters : expression, listener and equality object
Example -4:
app.controller('MainCtrl', function($scope) {
$scope.name = "Angular";
$scope.updated = -1;
$scope.$watch('name', function() {
$scope.updated++;
});
});
<body ng-controller="MainCtrl">
<input ng-model="name" />
Name updated: {{updated}} times.
</body>
Here the first parameter can be a string or a function. In this case it is just a string with the name of what we want to $watch, in this case, $scope.name. The second parameter is what is going to happen when $watch says that our watched expression has changed. The first thing we have to know is that when the controller is executed and finds the $watch, it will immediately fire.
we have initialized the $scope.updated to -1, the $watch will run once when it is processed and it will put the $scope.updated to 0.
Example-5:
app.controller('MainCtrl', function($scope) {
$scope.name = "Angular";
$scope.updated = 0;
$scope.$watch('name', function(newValue, oldValue) {
if (newValue === oldValue) { return; } // AKA first run
$scope.updated++;
});
});
<body ng-controller="MainCtrl">
<input ng-model="name" />
Name updated: {{updated}} times.
</body>
The second parameter of $watch receives two parameters. The new value and the old value. We can use them to skip the first run that every $watch does. Normally you don’t need to skip the first run,
Example-6:
app.controller('MainCtrl', function($scope) {
$scope.user = { name: "Fox" };
$scope.updated = 0;
$scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
});
});
<body ng-controller="MainCtrl">
<input ng-model="user.name" />
Name updated: {{updated}} times.
</body>
We want to $watch any changes in our $scope.user object. Same as before but using an object instead of a primitive.
Here It doesn’t work. Because the $watch by default compares the reference of the objects. In example 4 and 5, every time we modify $scope.name it will create a new primitive, so the $watch will fire because the reference of the object is new and that is our change. In this new case, since we are watching $scope.user and then we are changing $scope.user.name, the reference of $scope.user is never changing because we are creating a new $scope.user.name every time we change the input, but the $scope.user will be always the same.
Example-7:
app.controller('MainCtrl', function($scope) {
$scope.user = { name: "Fox" };
$scope.updated = 0;
$scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
}, true);
});
<body ng-controller="MainCtrl">
<input ng-model="user.name" />
Name updated: {{updated}} times.
</body>
Now it'll work, we added a third parameter to the $watch which is a bool to indicate that we want to compare the value of the objects instead of the reference. And since the value of $scope.user is changing when we update the $scope.user.name the $watch will fire appropriately.
When the $digest loop finishes, the DOM makes the changes.
Example-4:
Exaple-1:
User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />
Here, we have $scope.user, which is bound to the first input, and we have $scope.pass, which is bound to the second one. Doing this we add two $watch to the $watch list.
Example-2:
app.controller('MainCtrl', function($scope) {
$scope.foo = "Foo";
$scope.world = "World";
});
Hello, {{ World }}
Here, even though we have two things attached to the $scope, only one is bound. So in this case we only created one $watch.
Example-3:
app.controller('MainCtrl', function($scope) {
$scope.people = [...];
});
<ul>
<li ng-repeat="person in people">
{{person.name}} - {{person.age}}
</li>
</ul>
How many $watch are created here? Two for each person (for name and age) in people plus one for the ng-repeat. If we have 10 people in the list it will be (2 * 10) + 1, I.e 21 $watch's.
Everything that is bound in our UI using directives creates a $watch.When our template is loaded, Also known as in the linking phase, the compiler will look for every directive and creates all the $watch that are needed.
we already know that every binding we set has its own $watch to update the DOM when is needed
The $scope.watch() function is used to observe changes in a variable on the $scope. It accepts three parameters : expression, listener and equality object
Example -4:
app.controller('MainCtrl', function($scope) {
$scope.name = "Angular";
$scope.updated = -1;
$scope.$watch('name', function() {
$scope.updated++;
});
});
<body ng-controller="MainCtrl">
<input ng-model="name" />
Name updated: {{updated}} times.
</body>
Here the first parameter can be a string or a function. In this case it is just a string with the name of what we want to $watch, in this case, $scope.name. The second parameter is what is going to happen when $watch says that our watched expression has changed. The first thing we have to know is that when the controller is executed and finds the $watch, it will immediately fire.
we have initialized the $scope.updated to -1, the $watch will run once when it is processed and it will put the $scope.updated to 0.
Example-5:
app.controller('MainCtrl', function($scope) {
$scope.name = "Angular";
$scope.updated = 0;
$scope.$watch('name', function(newValue, oldValue) {
if (newValue === oldValue) { return; } // AKA first run
$scope.updated++;
});
});
<body ng-controller="MainCtrl">
<input ng-model="name" />
Name updated: {{updated}} times.
</body>
The second parameter of $watch receives two parameters. The new value and the old value. We can use them to skip the first run that every $watch does. Normally you don’t need to skip the first run,
Example-6:
app.controller('MainCtrl', function($scope) {
$scope.user = { name: "Fox" };
$scope.updated = 0;
$scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
});
});
<body ng-controller="MainCtrl">
<input ng-model="user.name" />
Name updated: {{updated}} times.
</body>
We want to $watch any changes in our $scope.user object. Same as before but using an object instead of a primitive.
Here It doesn’t work. Because the $watch by default compares the reference of the objects. In example 4 and 5, every time we modify $scope.name it will create a new primitive, so the $watch will fire because the reference of the object is new and that is our change. In this new case, since we are watching $scope.user and then we are changing $scope.user.name, the reference of $scope.user is never changing because we are creating a new $scope.user.name every time we change the input, but the $scope.user will be always the same.
Example-7:
app.controller('MainCtrl', function($scope) {
$scope.user = { name: "Fox" };
$scope.updated = 0;
$scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
}, true);
});
<body ng-controller="MainCtrl">
<input ng-model="user.name" />
Name updated: {{updated}} times.
</body>
Now it'll work, we added a third parameter to the $watch which is a bool to indicate that we want to compare the value of the objects instead of the reference. And since the value of $scope.user is changing when we update the $scope.user.name the $watch will fire appropriately.
$digest loop
When the browser receives an event that can be managed by the angular context and the $digest loop will be fired.
This loop is made from two smaller loops. One processes the $evalAsync queue and the other one processes the $watch list,
The $scope.$digest() function iterates through all the watches in the $scope object, and its child $scope objects (if it has any - isolated scopes).
Hey $watch, what is your value? //Watch-1
It is 9
Alright, has it changed? ///Watch-1
No, sir.
(nothing happens with this one, so it moves to the next)
You, what is your value? //Watch-2
It is Foo.
Has it changed? //Watch-2
Yes, it was Bar.
(good, we have a DOM to be updated)
This continues until every $watch has been checked.
The above checking is called dirty-checking. Now that all the $watch have been checked there is something else to ask: Is there any $watch that has been updated? If there is at least one of them that has changed, the loop will fire again until all of the $watch report no changes. This is to ensure that every model is clean. Have in mind that if the loop runs more than 10 times, it will throw an exception to prevent infinite loops.
When the $digest loop finishes, the DOM makes the changes.
Example-4:
$scope.name = "Foo";
$scope.changeFoo = function() {
$scope.name = "Bar";
}
});
{{ name }}
<button ng-click="changeFoo()">Change the name</button>
Here we have only one $watch because ng-click doesn’t create any watches (the function is not going to change :P).
1. We press the button.
2. The browser receives an event which will enter the angular context (I will explain why, later in this article).
3. The $digest loop will run and will ask every $watch for changes.
4. Since the $watch which was watching for changes in $scope.name reports a change, if will force another $digest loop.
5. The new loop reports nothing.
6. The browser gets the control back and it will update the DOM reflecting the new value of $scope.name
The important thing here is that EVERY event that enters the angular context will run a $digest loop. That means that every time we write a letter in an input, the loop will run checking every $watch in this page.
Example -1:
<html>
<head>
<title>AngularJS Digest</title>
<script src="lib/angular.js"></script>
<script>
var myapp = angular.module("myapp", []);
var myController = myapp.controller("myController", function ($scope) {
$scope.datetime = new Date();
$scope.updateTime = function () {
$scope.datetime = new Date();
}
// This block of code updating model outside of the Angular. so, we are using $scope.$digest() to refresh.
document.getElementById("updateTimeButton").addEventListener('click', function () {
console.log("update time clicked");
$scope.datetime = new Date();
console.log($scope.datetime);
});
});
</script>
</head>
<body ng-app="myapp" ng-controller="myController">
<button ng-click="updateTime()">Update time - ng-click</button>
<button id="updateTimeButton">Update time</button>
<br />
{{datetime | date:'yyyy-MM-dd HH:mm:ss'}}
</body>
</html>
When you will click on second button, the data binding is not updated. Since $scope.$digest() is not called after the second button's event listener is executed. In this way on clicking the second button the time will be updated in the $scope.data.time variable, but the new time will never displayed.
<script type="text/javascript">
document.getElementById("updateTimeButton").addEventListener('click', function () {
console.log("update time clicked");
$scope.datetime = new Date();
//to update $scope
$scope.$digest();
console.log($scope.datetime);
});</script>
To fix this issue you need to add a $scope.$digest() call to the second button event listener
$apply
When we do change in any model outside of the Angular context (like browser DOM events, setTimeout, XHR or third party libraries), then we need to inform Angular of the changes by calling $apply() manually. When the $apply() function call finishes AngularJS calls $digest() internally, so all data bindings are updated.
<script>
document.getElementById("updateTimeButton").addEventListener('click', function () {
$scope.$apply(function () {
console.log("update time clicked");
$scope.datetime = new Date();
console.log($scope.datetime);
});
});
</script>
In the above $digest example, instead of calling $digest() function inside the button listener function we can used the $apply() function like this
The main differance between the $digest and $apply:
1. $digest() is faster than $apply(), since $apply() triggers watchers on the entire scope chain while $digest() triggers watchers on the current scope and its children(if it has).
2. When error occurs in one of the watchers, $digest() can not handled errors via $exceptionHandler service, In this case you have to handle exception yourself. While $apply() uses try catch block internally to handle errors and if error occurs in one of the watchers then it passes errors to $exceptionHandler service.
No comments:
Post a Comment