Mastering AngularJS scope

 

Gif Screen capture:

http://www.cockos.com/licecap/

 

Some images were generated by 

https://github.com/mrajcok

Credits

Continues to the right ...

Backup slides


function Scope() {
  this.id = nextUid(),
  // and some rootScope properties
}

function CreateChildScope(parent) {
  function ChildScope() {
    this.id = nextUid()
    // ...
  }
  ChildScope.prototype = parent;

  return new ChildScope();
}


var rootScope = new Scope();
var mainCtrlScope = CreateChildScope(rootScope)

mainCtrlScope.selectId = 1;
// ...

var directiveScope = CreateChildScope(mainCtrlScope)

Angular in plain JS 

Angular uses prototypical inheritance, we can simulate it with plain JS

index

  • Deconstruction of a directive    

  • JavaScript Prototypal Inheritance

  • scope inheritance in different angular directives

  • Prepare to Angular2 

What are Scopes?

Scope refers to the visibility of variables. 

 

Scope is an execution context for expressions.  

 

Scope is an object that refers to the application model.

Scopes can watch expressions and propagate events.

 

Scopes are arranged in hierarchical structure which mimic the DOM structure of the application.

 

...

scope

$scope

Let's focus only on scope

aka. visibility, context, etc.

what does this directive?

index.html
app.js
list-directive.tmpl.html

?

why does it fail?

app.js
list-directive.tmpl.html
index.html

Access to different scopes

Scope inheritance is normally straightforward, and you often don't even need to know it is happening...

until you try 2-way data binding to a primitive (e.g., number, string, boolean) defined on the parent scope from inside the child scope. 

scope life cycle without DOM

Console MODE

rootScope

Every application has a single root scope. All other scopes are descendant scopes of the root scope. 

ngController

When a Controller is attached to the DOM via the ng-controller directive, Angular will instantiate a new Controller object.

A new child scope will be created and made available as an injectable parameter 

directives

scope: false

The directive does not create a new scope, so there is no inheritance, it's the same scope.





function listDirective(){
  return {
    scope: false,
    templateUrl: 'list-directive.tmpl.html'
  }
}

ngRepeat

The ngRepeat directive instantiates a template once per item from a collection. Each template instance gets its own scope, where the given loop variable is set to the current collection item

view

First button is active,

    but not the others.

Names are shown.

 As expected!

ngClick 

button is active as soon it is clicked.

But different selectId?

Inheritance and the prototype chain

JavaScript? Inheritance?

In JavaScript, classes can't (being that they don't exist!) describe what an object can do. 

When it comes to inheritance, JavaScript only has one construct: objects.

Then, how it is done?

JavaScript is a class-free, object-oriented language, it uses prototypal inheritance

... instead of classical inheritance.

Prototype chain

Each object has an internal link to another object called its prototype.

That prototype object has a prototype of its own, and so on until an object is reached with null as its prototype. null, by definition, has no prototype, and acts as the final link in this prototype chain.

JS prototype chain

JavaScript objects have a link to a prototype object.

When getting a property,

the first step is to check if the object itself has the property

if not, it follows the prototype chain

When setting a property, 

primitives are shadowed,

objects are accessed

function ParentScope() {
  this.aNumber = 101,
  this.aString = 'Hello World',
  this.aFunction = function() { 
    console.log( this.aString ) 
  },
  this.anArray = [100, 101, 102],
  this.anObject = { a: 1, b: 2 }
}

var parent = new ParentScope()

function ChildScope() {}
ChildScope.prototype = parent

var child = new ChildScope()

child.aFunction()
child.aNumber
child.aNumber = 1001

child

child.anObject.a = 101

child

child.anObject = { c: 3, d: 4 }

child

Prototype chain

Prototypal lnheritance

Operator that implements true prototypal inheritance: 

Take an old object as the parameter and return a new object that inherits from the old one.

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

.prototype = parent

AngularJS relies on JavaScript 

 Prototype Inheritance 

// angular.js/src/ng/rootScope.js

function createChildScopeClass(parent) {

    function ChildScope() {
      this.$$watchers = null;
      this.$$nextSibling = null;
      this.$$childHead = null;
      this.$$childTail = null;
      this.$$listeners = {};
      this.$$listenerCount = {};
      this.$$watchersCount = 0;
      this.$id = nextUid();
      this.$$ChildScope = null;
    }

    ChildScope.prototype = parent;

    return ChildScope;
}

Start fixing 

... if you use ng-model there has to be a dot somewhere. If you don't have a dot, you're doing it wrong ...


var rootScope = new Scope();
var mainCtrlScope = CreateChildScope(rootScope)

mainCtrlScope.view = {}

mainCtrlScope.view.artists = [
  {id: 1001, name: 'Pablo'}, 
  {id: 1002, name: 'Salvador'} 
]
mainCtrlScope.view.selectId = 1001;


var childScope = CreateChildScope(mainCtrlScope)

childScope.view.selectId = 1002

childScope.view.selectId
mainCtrlScope.view.selectId

well, that is true for ng-model, but also for all inherited scopes

no more primitives in $scope

Fixed!

Why using prototypal inheritance?

 The design decisions behind the scope are heavily favored for speed and memory consumption.


 The typical use of scope is to watch the expressions, which most of the time return the same value as last time so we optimize the operation.
 

Closures construction is expensive in terms of speed as well as memory

  • No closures, instead use prototypical inheritance for API
  • Internal state needs to be stored on scope directly, which means that private state is exposed as $$____ properties
     

Child scopes are created and removed often

  • Using an array would be slow since inserts in middle are expensive so we use linked list

Design notes

Directives using

Scope Inheritance

... and inherit prototypically

  • ng-include
  • ng-if
  • ng-controller
  • ng-switch
  • ng-view 
  • ng-repeat
  • directive with "scope: true"

... and don't inherit

  • directive with "scope: { ... }"

creating new scopes

ngInclude

Fetches, compiles and includes an external HTML fragment, creating a new scope.




 <div class="slide-animate-container">
    <div ng-include="template.url"></div>
  </div>
</div>

ngIf

When an element is removed using ngIf its scope is destroyed and a new scope is created when the element is restored




<input type="checkbox" 
    ng-model="checked" 
    ng-init="checked=true" />


<div ng-if="checked">
   Inside ng-if 
   <button ng-click="selectId = 7777">
        Write selectId
    </button>
</div>

same behavior

  • ng-controller
  • ng-switch
  • ng-view 

ngRepeat

creates a new scope, which prototypically inherits from the parent scope, 

but it also assigns the item's value to a new property on the new child scope





// child scope prototypically inherits from parent scope ...     
childScope = scope.$new(); 

// creates a new childScope property
childScope[valueIdent] = value; 

ngRepeat (2)

be careful with arrays on primitives!

$scope.myArrayOfPrimitives = [ 11, 22 ]

// vs.

$scope.myArrayOfObjects = [
    {value: 'value 1'}, 
    {value: 'value 2'}, 
    {value: 'value 3'}
]

directives

scope: false

Default

The directive does not create a new scope, so there is no inheritance, it's the same scope.

But careful! Directives are intended as reusable components





function listDirective(){
  return {
    scope: false,
    template:  ...

directives

scope: true

The directive creates a new child scope that prototypically inherits from the parent scope

Similar to ng-include, etc.





function listDirective(){
  return {
    scope: true,
    template:  ...

directives

scope: { .. }

The directive creates a new isolate/isolated scope. It does not prototypically inherit.

Good isolation

 

@  one-way model binding from parent to isolated scope

=   two-way model binding

&   bind to parent expressions





<my-directive 
    interpolated="{{parentProp1}}" 
    twowayBinding="parentProp2"> 

// ... 

scope: { 
    interpolatedProp: '@interpolated', 
    twowayBindingProp: '=twowayBinding' 
}

Prepare for Angular2

Core goals on Angular2

Performance
Simplify the framework

... 

How?

Performance

         Web Workers

         No digest cycle (dirty checking)

Simplicity

    Moving towards components

Removing $scope

Removing controllers

death of a $scope

Remove $scope

Use "controller as"

Attach the view properties on the controller instead of the $scope object.

 

Introduced in Angular 1.2

app.controller('TodoCtrl', TodoCtrl);

// Instead of doing this:
function ($scope) {
  $scope.input = 'ex. buy milk';
}

// Do this:
function () {
  this.input = 'ex. buy milk';
}



<!-- Then instead of this: -->
<div ng-controller="TodoCtrl">
  <input type="text" ng-model="input" />
</div>

<!-- Do this: -->
<div ng-controller="TodoCtrl as todo">
  <input type="text" ng-model="todo.input" />
</div>

Remove $scope (2)

Use capture variable for this

 when using "controllerAs" syntax

 



app.controller('TodoCtrl', TodoCtrl);

function TodoCtrl() {
    var vm = this;
    vm.input= {};
}



<div ng-controller="TodoCtrl as vm">
  <input type="text" ng-model="vm.input" />
</div>

The this keyword is contextual and when used within a function inside a controller may change its context.

Remove controllers

Match controllers with directives

 

Rely on isolated scope, which means cleaner code

 

Use bindToController, so the component’s properties are bound to the controller rather than to the scope

// Angular 1.3
app.directive('todo', function () {
  return {
    scope: {
       max: '='
    },
    controller: todoCtrl,
    controllerAs: 'TodoCtrl',
    bindToController: true,
    template: ...
  };
});


// Angular 1.4
app.directive('todo', function () {
  return {
    scope: {},
    controller: todoCtrl,
    controllerAs: 'TodoCtrl',
    bindToController:  {
       max: '='
    },
    template: ...
  };
});

function TodoCtrl() {
    console.log(todoCtrl.max);
    ...
}

 “The $scope is not the model, the $scope refers to the model” and “Whenever you have ng-model there’s gotta be a dot in there somewhere. If you don’t have a dot, you’re doing it wrong.”

Conclusions

Questions?

Gracias!

Mastering AngularJS scope

By Carlos Morales

Mastering AngularJS scope

Slides presented at Zürich AngularJS Meetup (http://www.meetup.com/AngularJS-ZRH/events/224146961/)

  • 1,028