Scopes (Part 2 of the AngularJS - from beginner to expert in 7 steps series)

This is the second post of AngularJS - from beginner to expert in 7 steps.

We started our first post by showing you how to start out building an AngularJS app. In this post, we’ll discuss a fundamental concept in understanding how AngularJS works and how you can use it to your advantage.

Throughout this tutorial series, we are going to be building an NPR audio player that will show us the current stories on the show Morning Edition and play them in our browser. To see the fully finished demo, head over here.

Part 2. Scopes

A $scope is an object that ties a view (a DOM element) to the controller. In the Model-View-Controller structure, this $scope object becomes the model. It provides an execution context that is bound to the DOM element (and its children).

Although it sounds complex, the $scope is just a JavaScript object. Both the controller and the view have access to the $scope so it can be used for communication between the two. This $scope object will house both the data and the functions that we’ll want to run in the view, as we’ll see.

All AngularJS apps have a $rootScope. The $rootScope is the top-most scope that is created on the DOM element that contains the ng-app directive.

This is where AngularJS binds data and functions when explicit $scopes are not set in the page. This is why the example in part 1 works.

In this example, we’re working with the $rootScope. We add an attribute on the scope of name in our main.js file. By putting this function in the app.run function, we’ll guarantee that it will run prior to the rest of the app. You can think of the run function being the main method of the angular app.

1
2
3
app.run(function($rootScope) { $rootScope.name = "Ari Lerner"; });

Now, anywhere in our view, we can reference this attribute using the expression/template: {{ }}, like so:

1
{{ name }}

We’ll go more in-depth into how the expression template syntax works later in this series.

See it:

{{ aPersonsname }}

In summary:

To really see the power of scopes, let’s attach a controller to a DOM element, which will create a $scope for the element and allow us to interact with it.

ng-controller

To explicitly create a $scope object, we’ll attach a controller object to a DOM element using the ng-controller directive on an object, like so:

1
2
3
<div ng-controller="MyController"> {{ person.name }} </div>

The ng-controller directive creates a new $scope object for the DOM element and nests it in the containing $scope. In this example, the parent $scope of the div with ng-controller is the $rootScope object. The scope chain looks like this:

Prototypal scope inheritance

Now, MyController sets up a $scope that we can access from inside the DOM element. In this case, we’re going to create a person object on the $scope of MyController, in main.js:

1
2
3
4
5
app.controller('MyController', function($scope) { $scope.person = { name: "Ari Lerner" }; });

Now we can access this person object in any child element of the div where ng-controller='MyController' is written because it is on the $scope.

See it

{{ aPersonsname }}


With one exception, all scopes are created with prototypal inheritance, meaning that they have access to their parent scopes. By default, for any property that AngularJS cannot find on a local scope, AngularJS will crawl up to the containing (parent) scope and look for the property or method there. If AngularJS can’t find the property there, it will walk to that scope’s parent and so on and so forth until it reaches the $rootScope.

The one exception: Some directives can optionally create an isolate scope and do not inherit from their parents.

For instance, say we have a ParentController that contains the person object and a ChildController that wants to reference that object:

1
2
3
4
5
6
7
8
9
app.controller('ParentController', function($scope) { $scope.person = {greeted: false}; }); app.controller('ChildController', function($scope) { $scope.sayHello = function() { $scope.person.greeted = true; } });

When we bind the ChildController under the ParentController in our view, we can reference the property on the parent scope just as if it were on the ChildController:

1
2
3
4
5
6
7
<div ng-controller="ParentController"> <div ng-controller="ChildController"> <input type="text" ng-model="person.name" placeholder="Name"></input> <a ng-click="sayHello()">Say hello</a> </div> {{ person }} </div>

Nested prototypal scope inheritance

See it

Say hello Reset
{{ person }}

Integrating in myApp

Now let’s use the power of the $scope to manage our NPR app. We left off last article having just defined the app module. Now, let’s start breaking up our DOM structure and build our fundamental functionality.

Just like we saw in the example above, we’ll create a root controller called PlayerController. Our RelatedController will be responsible for keeping track of our audio element and will handle fetching our listing of NPR programs.

Let’s create both of our controllers, back in main.js:

1
2
3
4
5
6
7
var app = angular.module('myApp', []); app.controller('PlayerController', ['$scope', function($scope) { }]); app.controller('RelatedController', ['$scope', function($scope) { }]);

Audio

These controllers don’t do much yet, so let’s get some audio going on in the app. In this tutorial, we’ll be using the HTML5 audio element, so make sure you have an HTML5-compliant browser (we prefer Google Chrome).

To add an audio element, we can either add it in the HTML or in our controller. Since we’ll be interacting with the audio element primarily in our controller, it makes most sense to create it here.

In our PlayerController, let’s create an audio element. We’ll store it on our scope, which, as you know by now, means that we’ll connect the view to the controller through the $scope object.

1
2
3
app.controller('PlayerController', ['$scope', function($scope) { $scope.audio = document.createElement('audio'); }]);

Now, this setup is kind of boring, as it doesn’t do very much yet. We’ll cover ‘fetching’ the data in the next part of our series, so for now we’ll hardcode a .mp4 url.

In the same PlayerController, set the src of the audio file to an .mp4 URL to which you have access. For the purpose of convenience, we’re using an NPR audio file that we are hosting, but we can point to any URL. Simply set your audio source as the URL of the audio file.

1
2
3
4
5
app.controller('PlayerController', ['$scope', function($scope) { $scope.playing = false; $scope.audio = document.createElement('audio'); $scope.audio.src = '/media/npr.mp4'; }]);

Try it

Playing audio: {{ playing }}

The audio won’t play unless we tell it to play. To do that, we can simply call $scope.audio.play(), and the HTML5 audio element will take over and start playing from the mp4 stream.

We can provide an interactive element to the user by creating a button that binds to an action on the $scope. We’ll discuss this more in-depth in the next section, however the HTML for the view above looks like:

1
2
3
4
5
<div ng-controller="PlayerController"> <button ng-click="play()" class="button" ng-show="!playing">Play</button> <button ng-click="stop()" class="button alert" ng-show="playing">Stop</button> Playing audio: <b>{{ playing }}</b> </div>

Notice that we don’t have to reference the audio element we create on the scope. This is because we are creating it in the controller when the controller is loaded using document.createElement("audio"). Note that we are going to refactor this component later in the tutorial series. It’s generally a bad idea to manipulate DOM components inside of a controller (thanks Brad Green for pointing this out as a note). We’re keeping this component here to maintain simplicity.

We’ve added a few variables in the view that we are keeping track of in on the $scope. We’re using a few advanced concepts that we’ll discuss in detail throughout the series, so do not worry if it doesn’t all make sense immediately:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app.controller('PlayerController', ['$scope', function($scope) { $scope.playing = false; $scope.audio = document.createElement('audio'); $scope.audio.src = '/media/npr.mp4'; $scope.play = function() { $scope.audio.play(); $scope.playing = true; }; $scope.stop = function() { $scope.audio.pause(); $scope.playing = false; }; $scope.audio.addEventListener('ended', function() { $scope.$apply(function() { $scope.stop() }); }); }]);

That’s a solid introduction into AngularJS’s $scope service. In the next section, we’ll cover bi-directional data binding in AngularJS.

The official repository for the beginner series is available as a git repo here: https://github.com/auser/ng-newsletter-beginner-series.

To get this repo locally, ensure that you have git installed, clone the repo, and check out the part2 branch:

1
2
git clone https://github.com/auser/ng-newsletter-beginner-series.git git checkout part2
Get the weekly email all focused on AngularJS. Sign up below to receive the weekly email and exclusive content.
We will never send you spam and it's a cinch to unsubscribe.

Download a free sample of the ng-book: The Complete Book on AngularJS

ng-book: The Complete Book on AngularJS is the canonical AngularJS book available today.

It's free, so just enter your email address and the PDF will be sent directly to your inbox. Mailchimp can take up to an hour to deliver the free sample chapter, but if you don't receive it within the hour, send us an email and we'll manually send them to you!

We'll send you updates about the book, when it updates and other free content.

We will never send you spam and it's a cinch to unsubscribe.

Comments

comments powered by Disqus