Angular1 Scope

angular1

// Angular 1 - Scope:

The $scope object is best thought of as the glue that allows communication 
between a controller and a view. Although similar, technically scopes are not 
models in the MVW pattern.  Typically, for every controller that is created, a 
new scope is created as well.

Scopes are also arranged hierarchically in a parent-child relationship.  Scopes 
are prototypical, and therefore all children scopes inherit the variables and 
functions of their parents.

Scopes provide an API that allows us to communicate with the associated views, 
and controllers.

Scopes are dependency injected automatically by the Angular framework.  Consider:

var Person = function(firstname, lastname) {
  this.firstname = firstname;
  this.lastname = lastname;
}

function logPerson() {
  var john = new Person('John','Doe');
  console.log(john);
}

logPerson();

In the above code, the logPerson method depends on the Person object.  It 
creates an instance of the Person class, and then do whatever it is designed to 
do.  In this simple case, it just pass it onto console.log.  From the 
maintainability perspective of this logPerson function, this is not very 
flexible.  A more flexible approach is for the caller to pass the Person object 
as a parameter to the logPerson method:

function logPerson2(person) {
  console.log(person);
}

var john = new Person('John','Doe');
logPerson2(john);

This is the basic idea of dependency injection (let the caller inject, or 
provide all the necessary dependencies).

var searchPeople = function(firstName, lastName, height, age, occupation) {
  return 'Jane Doe';
}
angular.injector().annotate(searchPeople); // This returns an array:
// ["firstName", "lastName", "height", "age", "occupation"]

// We can take a function and convert it to a string using the toString() method.
// Angular get the string version of a function, and parse it to get the 
// information that it needs.  If Angular recognizes a particular name, it knows
// that it needs to create a particular object.  For example, if the name of 
// a parameter is $scope, Angular knows that it needs to create a scope object.
// This means that we can put the $scope object anywhere in the parameter list.

Scopes hold your Models (that's your data), they cooperate with your 
Controllers, and they give the Views everything they need (that's what the user 
sees and interacts with).

The first scope we'll need is the application scope, this is exactly what you'd 
expect it to be: that's the scope your Angular application can operate in. We 
set this up in our HTML using the ng-app attribute:

<html ng-app="myApp">
    <head></head>
    <body></body>
</html>

Notice how we gave our app a name of 'myApp'. This will be usable in our whole 
html file. We can use ng-app without the "myApp" to declare a default app. In 
other words, just: <html ng-app>.

The second scope is ng-controller. This will determine where our controller can 
operate. We can have multiple controllers within our application. Each 
ontroller will have its own scope. For example, we may have an inbox.html file, 
containing the below code. It will give responsibility to a controller named 
'InboxCtrl' (in our JavaScript) for this scope.

<div ng-controller="InboxCtrl">
    <!-- inside InboxCtrl scope -->
</div>

Both ng-app and ng-controller, are Angular directives. Think of an Angular 
directive as something that allows you to extend your HTML. A directive is 
essentially a function† that executes when the Angular compiler finds it in the 
DOM. The function(s) can do almost anything, which is why I think it is rather 
difficult to define what a directive is. Each directive has a name (like 
ng-repeat, tabs, make-up-your-own) and each directive determines where it can 
be used: element, attribute, class, in a comment.

A simple controller:

<div ng-controller="TestCtrl"><h1>{{title}}</h1>
   <input type="text" ng-model="title">
</div>

<script>
   function TestCtrl($scope) {
      $scope.title = 'Write a title here...';
   };
</script>

In the above code, we specify the name of the controller using the ng-controller 
directive. Angular will be looking for a function with this name in our 
JavaScript so that it can act as a Controller. Complete example:

<!doctype html>
<html ng-app>
  <head>
    <title>Sample AngularJS Controller</title>
  </head>
  <body>
    <div ng-controller="TestCtrl">
        <h1>{{title}}</h1>
        <input type="text" ng-model="title">
    </div>

    <script src="lib/jquery-v1.11.1.js"></script>
    <script src="lib/angular-v1.2.22.js"></script>

    <script>
      function TestCtrl($scope) {
        $scope.title = 'Write a title here...';
      };
    </script>
  </body>
</html>

Scopes hold your Models (that's your data), they cooperate with your Controllers, 
and they give the Views everything they need (that's what the user sees and 
interacts with).

The first scope we'll need is the application scope, this is exactly what you'd 
expect it to be: that's the scope your Angular application can operate in. We 
set this up in our HTML using the ng-app attribute.

<html ng-app="myApp">
    <head></head>
    <body></body>
</html>

Notice how we gave our app a name of 'myApp'. This will be usable in our whole 
html file. We can use ng-app without the "myApp" to declare a default app. In 
other words, just <html ng-app>.

The second scope is ng-controller; this will determine where our controller can 
operate. We can have multiple controllers within our application. Each 
controller will have its own scope. For example, we may have an inbox.html file, 
containing the below code. It will give responsibility to a controller named 
'InboxCtrl' (in our JavaScript) for this scope.

<div ng-controller="InboxCtrl">
    <!-- inside InboxCtrl scope -->
</div>

Consider:

"use strict";
angular.module('myApp',[]);
angular.module('myApp').controller('MainController',['$scope',function($scope) {
  console.log('Hello');
}]);

After the name of the controller, we have an array.  This is important because 
during the minification process, the $scope argument would be minified to 
something different, and Angular wouldn't know what to inject into that 
particular slot.  So, Angular provides this special way.  If we create an array, 
the first elements are the names of arguments in our constructor function.  That 
way, Angular will still work after minification and be able to perform dependency 
injection.  In this case, when the controller is instantiated, Angular is going 
to inject a scope into the controller.  If the name of the variable is $scope, 
Angular will create a scope object, and pass it to the function in that slot.  
What exactly Angular do to create the scope object is still a bit of a mistery 
to me.  It probably create an empty object, set up the prototypical relationship 
with the parent element / controller, and then pass it on to our function.

Now that we have the scope provided to our controller, we can create our own 
variable within the scope object.  We can also add additional functions to the 
scope object:

$scope.message = "Hello";
$scope.sayHello = function(name) {
  return $scope.message + ' ' + name + '.';
};

To use this pieces of information in our HTML, we can use the double curly brace:

<body ng-controller="MainController">
  <p>{{sayHello('...')}}</p>
</body>

So, in our controller, we can add variables and methods to the scope object, and 
in our HTML, we can access these variables and methods of the scope object 
through the scope object.  The double curly brace construct is part of Angular, 
and might not be part of our controller.

In Angular, it is important to understand the scope hierarchy and the 
relationship between parent scope and child scope.  All child scopes inherits 
from their parents via prototypical inheritance.  Therefore, any properties or 
methods created on our parent scope are automatically available to its child 
scopes.  This usually works fine.  However, we must be careful when using 
primitives on a scope because Angular may create new scope variables on the fly 
that will hide or shadow parent variables.  This confuses developers.  Often, 
the developer is expecting to bind to a parent scope variable, but two-way 
binding creates a child variable of the same name.  In this case, instead of 
binding to the parent, Angular will bind to the child and cause unexpected 
results.

Fortunately, we can get around this behavior by always storing our scope 
variables in an object rather than a primitive.  This, in effect, forces Angular 
to use the parent scope variable and stops the shadowing effects.

var app = angular.module('myApp');
app.controller('ParentController',['$scope', 
  function($scope) {
    $scope.name = "Michael Jackson";
  }
]);

app.controller('ChildController', ['$scope', 
  function($scope) {
  }
]);

<body>
 <div ng-controller="ParentController">
   <p>This is parent:{{name}}</p>
   <div ng-controller="ChildController">
     <p>This is child:{{name}}</p>
   </div>
 </div>
</body>

When the above code is run, the primitive "Michael Jackson" (or the name 
variable) is bound to both as we expected.

The above code produces:

This is parent: Michael Jackson
This is child: Michael Jackson

The problem arises when we create an input box with data binding using the 
ng-model.  So, we put two inputs in the HTML.  The first one is in the parent, 
and the second one is in the child:

<body>
  <div ng-controller="ParentController">
    <input type="text" ng-controller="name"/>
    <p>This is parent: {{name}}</p>
    <div ng-controller="ChildController">
      <input type="text" ng-model="name"/>
      <p>This is child: {{name}}</p>
    </div>
  </div>
</body>

When we run this code, if we change the value of the parent input box, both the 
parent and child works properly as we expect.  But if we go down to the child 
and make the change, that in effect breaks the data binding from the parent.

And the reason is because this second input box is in the child.  Once we make a 
change to the value, it's actually looking on the child scope and seeing that 
the value is to the data bound doesn't exist, and creates a new one, and 
therefore shadowing the parent.

To get around this issue, we use the dot notation between our controllers and 
our views.  In order to do that, we will just use an object that stores our 
values rather primitives.  So, in this case, we are going to name an object as 
model, and in it, we define a property called name, and rather than binding 
directly to $scope.name, we bind to $scope.model.name:

app.controller('ParentController', ['$scope', function($scope) {
  $scope.model = {
    name: 'Michael Jackson'
  };
}]);

app.controller('ChildController',['$scope', function($scope) {
}]);

<body>
  <div ng-controller="ParentController">
    <input type="text" ng-model="model.name"/>
    <p>This is parent:{{model.name}}</p>
    <div ng-controller="ChildController">
      <input type="text" ng-model="model.name"/>
      <p>This is child: {{model.name}}</p>
    </div>
  </div>
</body>

When we run the above code, it should work as expected.  Changing the name in 
the parent scope, should automatically change the UI for the child scope as well 
and changing the name in the child scope should automatically change the UI for 
the parent scope as well.

As mentioned previously, scopes exists in a hierarchy with the root scope at the 
top of the hierarchy.  $scope inherits all properties and methods from their 
parents.
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License