Angular Model intro

Introduction to Angular data model vs view update.

In any web application we have the "model" - our data that keeps changing, and the "view" - the DOM or HTML that the user sees in the browser. It is hard to update the "view" ourselves, and the AngularJS framework takes the update pain away. Whenever one designs an Angular application, one should think about the data first, and then the view will be an easy part.

Let us start with an empty angular page

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-app="AngularModelExampleApp" ng-controller="AngularModelController">
</body>
</html>
app.js
1
2
3
4
angular.module('AngularModelExampleApp', [])
.controller('AngularModelController', function ($scope) {
// model data goes into $scope
});

We have an empty page. The body of the page will be controlled by the Angular framework. In particular the body will be updated by the controller called AngularModelController. The controller function is nothing special, except that it injects as its argument an special object $scope. This object is created by the Angular framework behind the scenes and should store all our model data - the data we want or might to want to somehow show in our HTML (view).

Let us add a header to the empty page.

app.js
1
2
3
4
angular.module('AngularModelExampleApp', [])
.controller('AngularModelController', function ($scope) {
$scope.header = 'Angular Model vs View';
});

The model object $scope is now { header: 'Angular Model vs View' }. How does the page (the DOM = HTML) reflect this? We can reflect the model by using a template and writing almost any expression that refers to the header property. For example we can simply use the text

index.html
1
2
3
<body ng-app="AngularModelExampleApp" ng-controller="AngularModelController">
<h2>{{ header }} example</h2>
</body>

If we reload the page we will see the following HTML if we inspect the page's "Elements" tab

1
2
3
<body ng-app="AngularModelExampleApp" ng-controller="AngularModelController" class="ng-scope">
<h2 class="ng-binding">Angular Model vs View example</h2>
</body>

Notice that the Angular framework automatically updated or synchronized the DOM with our template by using the data from the model object called $scope. What if the model changes? Let us change the header 5 seconds after the application starts.

app.js
1
2
3
4
5
6
7
angular.module('AngularModelExampleApp', [])
.controller('AngularModelController', function ($scope) {
$scope.header = 'Angular Model vs View';
setTimeout(function setNewHeader() {
$scope.header = 'Angular sync';
}, 5000);
});

Hmm, nothing is happening. Well, something is happening, trust me. The $scope.header property has been changed 5 seconds after the application starts. You can even inspect the scope if you do not believe me. Open the browser console and grab the $scope object to inspet - it is just a plain object after all!

1
2
3
// in the browser console
angular.element(document.body).scope()
// shows an object with lots of properties including { header: 'Angular sync' }

Why is the changed model data not reflected in the view? Because Angular has no idea that the model data has changed. The scope object is a plain object that does not tell the view or anyone when the data has been changed. In our case, the Angular framework does not see / override / listen to setTimeout(function () { }, 5000) call. Usually, things like setTimeout, setInterval, event listeners, jQuery callbacks, websocket messages need to manually tell Angular framework - the model data has been changed, please propagate the changes and update the DOM. It is simple to do.

app.js
1
2
3
4
5
6
7
8
angular.module('AngularModelExampleApp', [])
.controller('AngularModelController', function ($scope) {
$scope.header = 'Angular Model vs View';
setTimeout(function setNewHeader() {
$scope.header = 'Angular sync';
$scope.$apply();
}, 5000);
});

All we had to do was to call $scope.$apply() after changing the data.

Bonus 1 - avoiding $apply

In most cases we do not have to call $scope.$apply() - any own Angular code does it for us automatically at the end. If you handle click events using ng-click, then the $scope.$apply() will be called for you. AngularJS even comes with its own $timeout service you can use instead of the plain setTimeout.

app.js
1
2
3
4
5
6
7
angular.module('AngularModelExampleApp', [])
.controller('AngularModelController', function ($scope, $timeout) {
$scope.header = 'Angular Model vs View';
$timeout(function setNewHeader() {
$scope.header = 'Angular sync';
}, 5000);
});

The HTML is updated automatically 5 seconds after the application starts. We used $timeout which is part of the Angular framework. The $timeout suspects that something in our data model has changed, thus it will always call $scope.$apply() for us. If nothing has changed, the framework will see this, and nothing will be updated. If something has changed, like in our example, every place in the HTML page that refers to any $scope property will be updated.

Bonus 2 - multiple view templates

We have separated the model object from the view so we do not have to worry how to show or update the view's HTML when the data changes. For example we can have several visually different places that put the $scope.header data on the page.

index.html
1
2
3
4
<body ng-app="AngularModelExampleApp" ng-controller="AngularModelController">
<h2>{{ header }} example</h2>
This is a small example, showing {{ header | uppercase }} feature.
</body>
// renders
Angular sync example
This is a small example, showing ANGULAR SYNC feature.

The h2 element will have the header value as is, while the paragraph below will have the header value, but the string will be rendered in the HTML in upper case letters. Whenever the header property in the model is changed, both places are updated correctly.

This is a very common approach in AngularJS. Rather than having a separate precomputed value for the upper cased header in the model, and try to keep it in sync with the lower cased header, we have one model, and only its view representation is generated differently in the DOM by the framework.