(as opposed to AngularJS which refers to Angular1)
Ben Kinsey @bkinsey808
Github Demo Project: github.com/bkinsey808/bk-ng2-ultra
ng2 | vs | react |
---|---|---|
typescript | written in | es5 |
beta.2 | version | 0.15 |
megacorp | ||
framework | considered as | library |
more opinionated | less opinionated | |
more corporate | more hipster |
ng2 | vs | react |
---|---|---|
dirty checking | change detection | virtual dom diffing |
more complex | simpler | |
more extensible | change detection strategy | less extensible |
ng2 change detection
super optimized
different strategies for different scenarios
jit change detection
inline caching
only updates changed bindings
can be further optimized with immutables and observables
ng2 uses Zone.js
tells ng2 when to run change detection
How it works:
intercepts all asynchronous events so they map to the same thread local context
MAGIC!
ng2 is engineered to be Opinionated
Even more opinionated than ng1, and ng1 was considered a relatively opinionated framework
Why is Opionated a Good Thing?
Angular community includes 1.1 million active developers
Massive scale community consensus and synergy
Learn TypeScript!
(Don't worry, it's awesome)
import angular, {bootstrap}
from 'angular';
angular.module('app', [])
.component('app', {
restrict: 'E',
template: `
<div>
Hello World
</div>
`,
controller: class App {}
});
bootstrap(document, ['app']);
import {bootstrap} from
'angular2/platform/browser';
import {Component} from
'angular2/core';
@Component({
selector: 'app',
template: `
<div>
Hello World
</div>
`
})
class App {}
bootstrap('App', []);
Slide credit: Patrick Stapleton
What the @ are decorators?
simply functions that modify:
• methods
• method parameters
• properties
• classes
Decorators are typically used to add metadata, but could also add mixin-like functionalty
@Component - tells ng2 that the class defines a component, and also how to process it
An ng2 app is a tree of components
A component is self-describing:
Scope is not inherited down the DOM tree. There is no scope.
What's up with Dependency Injection?
"Don't call me, I'll call you"
The technical name for this is "Inversion of Control"
ng2, like ng1, controls how dependencies get injected into your custom code
This is a design pattern that increases modularity and extensibility
The evolution of Dependency Injection
['a', 'b', function(a, b) {..}]
This is necessary, because under the hood, a simple object in ng1 is used to store the
name-value (token-object) pairs of each dependency:
di = {'a': a, 'b': b}
di = new Map()
The end result is that in ng2 dependencies can be modeled as a simple array:
providers: [a, b]
What is a Provider?
an instruction that describes how an object for a certain token is created
In ng2 there are two ways to inject an object (typically a service) so that it can be used by a component:
// at bootstrap
bootstrap(AppComponent, [DataService]);
// in a component
@Component({
...
providers: [DataService]
})
class AppComponent {
constructor(dataService: DataService) {
// dataService instanceof DataService === true
}
}
Multi Providers: ng2 pluggable hooks
can provide multiple tokens
can also be used to extend...
class Engine { }
class TurboEngine { }
var injector = Injector.resolveAndCreate([
provide(Engine, {useClass: Engine}),
provide(Engine, {useClass: TurboEngine})
]);
var engine = injector.get(Engine);
// engine instanceof TurboEngine
ng2 encourages "composition over inheritance"
Last token WINS
...a very nice feature to implement a pluggable interface that can be extended from the outside world
...including ng2 platform directives
Wait... did you say Directives?
Directives are classes which get instantiated as a response to a particular DOM structure
By controlling the DOM structure, what directives are imported, and their selectors, the developer can use the "composition pattern"
using simple objects to build complex ones.
Directives are the cornerstone of an Angular application.
Directives allow the developer to turn HTML into a DSL and then control the application assembly process.
ng2 directives
Directives are instantiated whenever the CSS selector matches the DOM structure.
@Component is a special kind of @Directive that matches the tag selector
@Directive is used when matching any other css selector (e.g. custom tooltip attribute)
Let's talk Templates
ng2 templates are compiled at compile time instead of runtime for optimization
Key Point: same syntax for custom and native components
friendly for tooling (though not much ng2 template tooling has been built yet)
Explicit Template Syntax
makes template easier to refactor without understanding underlying components
for example, *ngIf and *ngFor cannot be combined in same element, therefore there is no need for the concept of "directive priority"
What the #*{{|}}*[()] did you do to my HTML?
calm down, it actually makes a lot of sense[] property binding
() event binding
[()] two way binding
{{}} interpolation
* template tag (ngIf, ngFor, etc)
# local variable
| pipe
The evolution of forms
ng1 forms rely on the ng-model directive
instantaneous two-way data binding keeps a form control in sync with a view model.
However, the approach has some disadvantages:
What's an Observable and why is it a big deal?
Function types in es7/es2016
Synchronous | Asynchronous | |
---|---|---|
function | T | Promise |
function* | Iterator | Observable? |
Ready for the Rx Revolution?
in ng1 promises were prevalent, in ng2 it will be observables.
observables have advantage over promises:
Template-driven Form Example Template
<form #f="form"
(ng-submit)="onSubmitTemplateBased()">
<label>First Name:</label>
<input type="text"
ng-control="firstName"
[(ng-model)]="vm.firstName" required>
<label>Password:</label>
<input type="password"
ng-control="password"
[(ng-model)]="vm.password" required>
<button type="submit"
[disabled]="!f.valid">
Submit
</button>
</form>
Model-based Form Example Template
<form [ng-form-model]="form"
(ng-submit)="onSubmitModelBased()">
<label>First Name:</label>
<input type="text"
ng-control="firstName">
<label>Password:</label>
<input type="password"
ng-control="password">
<button type="submit"
[disabled]="!form.valid">
Submit
</button>
</form>
Template-driven Form Example Component
@Component({
selector: "template-driven-form",
templateUrl: 'template-driven-form.html',
directives: [FORM_DIRECTIVES]
})
export class TemplateDrivenFormComponent {
vm: Object = {};
onSubmitTemplateBased() {
console.log(this.vm);
}
}
Template-driven Form Example Component
@Component({
selector: "model-driven-form",
templateUrl: 'model-driven-form.html',
directives: [FORM_DIRECTIVES]
})
export class ModelDrivenFormComponent {
form: ControlGroup;
firstName: Control = new Control("", Validators.required);
constructor(fb: FormBuilder) {
this.form = fb.group({
"firstName": this.firstName,
"password": ["", Validators.required]
});
}
onSubmitModelBased() {
console.log(this.form);
}
}
Now comes the fun part.
This is FRP (Functional Reactive Programming)
this.form.valueChanges
.map((value) => {
value.firstName = value.firstName.toUpperCase();
return value;
})
.filter((value) => this.form.valid)
.subscribe((value) => {
alert("View Model = " + JSON.stringify(value));
});
getTasks() {
return this.http
.get('/api/v1/tasks.json')
.map( (responseData) => {
return responseData.json();
})
.map((tasks: Array) => {
let result:Array = [];
if (tasks) {
tasks.forEach((task) => {
result.push(
new Task(
task.id,
task.description,
task.dueDate,
task.complete));
});
}
return result;
});
}
}
This is a service that wraps an observable
@Injectable()
export class WikipediaService {
constructor(
private jsonp: Jsonp) {}
search (term: string) {
var search = new URLSearchParams()
search.set('action', 'opensearch');
search.set('search', term);
search.set('format', 'json');
return this.jsonp
.get('http://en.wikipedia.org/w/api.php?callback=JSONP_CALLBACK', {
search
})
.map((response) =>
response.json()[1]);
}
}
Search-as-you-type powered by observables
@Component({
selector: 'wikipedia-search',
providers: [WikipediaService],
template: `
<div>
<h2>Wikipedia Search</h2>
<input
type="text"
[ngFormControl]="term">
<ul>
<li *ngFor="#item of items
| async">
</li>
</ul>
</div>
`
})
export class WikipediaSearchComponent {
items: Observable<Array<string>>;
term = new Control();
constructor(
private wikipediaService:
WikipediaService) {
this.items =
this.term.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(term =>
this.wikipediaService
.search(term));
}
}
A taste of learning about Rx instance operators
With flatMap, the search results could be stale, because search responses may come back out of order. To fix this, switchMap should be used, since it ensures that an old observable is unsubscribed once a newer one is provided.
So, in summary, flatMap should be used when all results matter, regardless of their timing, and switchMap should be used when only results from the last Observable matter.
The stateful AsyncPipe
The Async pipe can receive a Promise or Observable as input and subscribe to the input automatically, eventually returning the emitted value(s).
It is stateful because the pipe maintains a subscription to the input and its returned values depend on that subscription.
Bottom Line:
ng2 plus Rx pushes the state-of-the-art dramatically.
It's never been easier to write powerful, responsive UI.
Hopefully, this presentation has given you a little glimpse into emerging patterns that I predict will become well-used in this field in the future.