Frontend engineer, Qualitance
Andrei Antal
frontend engineer @ Qualitance
Contact me at:
antal.a.andrei@gmail.com
@andrei_antal
const bookTitle = "Alchemy for beginners";
const chaptersTitles = [ "Equipment" ]
let book = {
title: bookTitle,
chapters: chapterTitles
}
.....
Strings, numbers, arrays, objects etc.
Document Object Model
(DOM)
(rendering)
(Data projection)
Server
Client
request
website resources (html, js, css)
request (state change request)
new website resources (html, js, css)
request (state change request)
new website resources (html, js, css)
(re-render page)
(re-render page)
CONTROLLER
MODEL
VIEW
Update
Notify
Update
User action
app.Todo = Backbone.Model.extend({
defaults: {
title: '',
completed: false
},
toggle: function () {
this.save({
completed: !this.get('completed')
});
}
});
app.TodoView = Backbone.View.extend({
template: `<div class="view">
<input class="toggle" type="checkbox"
<%= completed?'checked':'' %>>
<label><%- title %></label>
<button class="destroy"></button>
</div>
<input class="edit" value="<%- title %>`,
events: {
'click .toggle': 'toggleCompleted',
}
initialize: function () {
this.listenTo(this.model, 'change', this.render);
},
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
toggleCompleted: function () {
this.model.toggle();
},
})
Manual re-render
<div>
Name: {{input type="text" value=name placeholder="Enter your name"}}
</div>
<div class="text">
<p>My name is, <strong>{{name}}</strong> and I want to learn Ember!</p>
</div>
<button {{action changeName}}>Change Name to Void Canvas</button>
template
Text
App = Ember.Application.create();
App.ApplicationController = Ember.Controller.extend({
actions:{
changeName:function(){
this.set('name','Andrei Antal');
}
}
});
js
Data binding
Data binding
Digest cycle (dirty checking)
<div ng-controller="app">
<input ng-model="a">
<div>{{b}}</div>
</div>
app.controller('App', function() {
$scope.a = 1;
$scope.b = 2;
$scope.c = 3;
});
Scope watch list
watch 1: a = 1
watch 2: b = 2
Digest cycle (dirty checking)
Watch: a = 8
Watch: b = 3
Watch: c = 5
Watch: d = 1
scope watch list
digest cycle
Angular context
Digest cycle (dirty checking)
Digest cycle (dirty checking)
Watch: a = 8
Watch: b = 3
Watch: c = 5
Watch: d = 1
watch list
digest cycle 1
Watch listener
Watch listener
digest cycle 2
...
Digest cycle (dirty checking)
var vm = new Vue({
data: {
// declare message with an empty value
message: ''
},
template: '<div>{{ message }}</div>'
})
// set `message` later
vm.message = 'Hello!'
@Component({
selector: 'todo-item',
template: `
<div class="todo noselect">
{{todo.owner.firstname}} - {{todo.description}} <br />
completed: {{todo.completed}}
</div>
<button (click)="onToggle()">COMPLETE</button>`
})
export class TodoItem {
@Input()
todo:Todo;
@Output()
toggle = new EventEmitter<Object>();
onToggle() {
this.toggle.emit(this.todo);
}
}
@Component()
class ContactsApp implements OnInit{
contacts:Contact[] = [];
constructor(private http: Http) {}
ngOnInit() {
this.http.get('/contacts')
.map(res => res.json())
.subscribe(contacts => this.contacts = contacts);
setTimeout(() => {/*some changes*/})
}
}
console.log('1')
console.log('2')
console.log('3')
RESULTS:
1
2
3
console.log('1')
setTimeout(() => {
console.log('2')
}, 0)
console.log('3')
STACK
console.log
setTimeout
console.log
RESULTS:
1
3
2
Synchronous
Asynchronous
function main() {
console.log('1')
setTimeout(() => {
console.log('2')
}, 0)
console.log('3')
}
var myZoneSpec = {
beforeTask: function () {
console.log('Before task');
},
afterTask: function () {
console.log('After task');
}
};
var myZone = zone.fork(myZoneSpec);
myZone.run(main);
// Logs:
// Before task
// 1
// 3
// After task
// Before task
// 2
// After task
function addEventListener(eventName, callback) {
// call the real addEventListener
callRealAddEventListener(eventName, function() {
// first call the original callback
callback(...);
// and then run Angular-specific functionality
var changed = angular2.runChangeDetection();
if (changed) {
angular2.reRenderUIPart();
}
});
}
// very simplified version of actual source
class ApplicationRef {
changeDetectorRefs:ChangeDetectorRef[] = [];
constructor(private zone: NgZone) {
this.zone.onTurnDone
.subscribe(() => this.zone.run(() => this.tick());
}
tick() {
this.changeDetectorRefs
.forEach((ref) => ref.detectChanges());
}
}
event
@Component({
selector: 'todo-item',
template: `
<div class="todo noselect">
{{todo.owner.firstname}} - {{todo.description}}
completed: {{todo.completed}}
</div>
<button (click)="onToggle()">COMPLETE</button>`
})
export class TodoItem {
@Input()
todo:Todo;
@Output()
toggle = new EventEmitter<Object>();
onToggle() {
this.toggle.emit(this.todo);
}
}
export class Todo {
constructor(public id: number,
public description: string,
public completed: boolean,
public owner: Owner) {
}
}
export class Owner {
constructor(
public firstName: string,
public lastName: string) {
}
}
// AppComponent
@Component({...})
export class AppComponent {
todo: Todo = {...};
toggle() {
this.todo.completed = !this.todo.completed;
}
}
Angular triggers change detection on its top-most ViewRef, which after running change detection for itself runs change detection for its child views.
Reducing the number of checks - OnPush change detection
@Component({..})
export class App {
data = { counter: 0 };
}
<my-counter [data]="data"></my-counter>
import {..., ChangeDetectionStrategy } from '@angular/core';
@Component({
...
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
@Input() data;
}
// won't trigger change detection
this.data.counter++;
// will trigger change detection
this.data = { counter: this.data.counter + 1 };
OnPush change detection - excluding parts of the component tree from running change detection
OnPush is more than just reference comparrison:
Using Observables as Inputs with OnPush change detection
@Component({
...
template: `
...
<my-counter [data]="data$"></my-counter>
`
})
export class App {
data$ = new BehaviorSubject({ counter: 0 });
...
}
this.data$.next({ counter: ++this._counter });
@Component({
...
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
@Input() data: Observable<any>;
...
ngOnInit() {
this.data.subscribe((value) => {
// local variable bound to our template
this._data = value;
});
}
}
event
import {..., ChangeDetectorRef } from '@angular/core';
export class CounterComponent {
constructor(private cd: ChangeDetectorRef) {}
...
ngOnInit() {
this.data.subscribe((value) => {
this._data = value;
// tell CD to verify this subtree
this.cd.markForCheck();
});
}
}
Turning change detection off and triggering change detection manually
import {..., ChangeDetectorRef } from '@angular/core';
export class CounterComponent {
constructor(private cd: ChangeDetectorRef) {}
...
ngOnInit() {
cd.detach();
// get some stream of data
setInterval(() => {
this.cd.detectChanges();
}, 5000);
}
}
export declare abstract class ChangeDetectorRef {
abstract checkNoChanges(): void;
abstract detach(): void;
abstract detectChanges(): void;
abstract markForCheck(): void;
abstract reattach(): void;
}
export abstract class ViewRef extends ChangeDetectorRef {
...
}
export class AppComponent {
constructor(cd: ChangeDetectorRef) { ... }