Continues to the right ...
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 uses prototypical inheritance, we can simulate it with plain JS
Deconstruction of a directive
JavaScript Prototypal Inheritance
scope inheritance in different angular directives
Prepare to Angular2
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.
index.html
app.js
list-directive.tmpl.html
app.js
list-directive.tmpl.html
index.html
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.
Every application has a single root scope. All other scopes are descendant scopes of the root scope.
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
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'
}
}
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
First button is active,
but not the others.
Names are shown.
As expected!
button is active as soon it is clicked.
But different selectId?
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.
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.
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
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();
}
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;
}
... 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
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
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>
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>
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;
be careful with arrays on primitives!
$scope.myArrayOfPrimitives = [ 11, 22 ]
// vs.
$scope.myArrayOfObjects = [
{value: 'value 1'},
{value: 'value 2'},
{value: 'value 3'}
]
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: ...
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: ...
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'
}
Performance
Simplify the framework
...
Performance
Web Workers
No digest cycle (dirty checking)
Simplicity
Moving towards components
Removing $scope
Removing controllers
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>
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.
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.”