angular2 + rxjs
TRANSCRIPT
Angular 2 with rxjschris noring
Frontend Developer OVO Energy, LondonGoogle Developer Expert
Who am I@chris_noring
Why angular 2
Backed by google
It’s released !!
It’s fast
It uses typescript
It uses components
compile time type checkinggood when teams and codebase grows
Fewer concepts to learn than angular 1
Mobile first Designed to be memory efficient and less CPU cycles
Faster than React, less fast than Vue.js
Tooling like angular-cli
1
2
3
4
5
6
7
Bootstrapping ng-1index.htmlapp.js
ng-app =“app”
angular.module(‘app’, [ dep1, dep2, dep3]).controller().service().factory().filter()
1
2
Bootstrapping ng 2main.t
s
app.module.ts
platform.bootstrapModule(AppModule);
app.component component1 component n..
module1 module1
1
2define dependant modulesdefine components our module consist ofdefine services
bootstrap app
Filter Constant/ Value
@Pipe
Service, factory, provider
vanilla javascript
ng1
ng2
Less conceptsfilter is faster, more js vanilla
more performant
.constant.value
.factory,
.service, .provider
Module Router
ngModuleMuch more
powerful
ng1
ng2
Less conceptsmodules + better router
lazy loadingbuilt for > 100k routes
better separation, itsnot all global like ng1
Controller Directiveelement
@Component @Directive
. Directive
Attrib
Less concepts
. DirectiveStructural
Bindings
Bindings - one way
<div [attr] =“prop” > </div>export class SomeComponent{ prop:string;}
{{ prop }}
[ someAttribute ]
Event binding<div (click)=“method()”>
export class SomeComponent{ method(){ // do something } }
(event)=“method()”
Banana in a box, 2-way
<input [(ngModel)]=“user.name”>
export class SomeComponent{ user:User; }
shorthand for
class User{ name:string;}
banana in a box
[(ngModel)]=“dataProperty”
<input [value]=“user.name" (input)=“user.name = $event.target.value">
Directives
3 directive typesStructural
Attribute
Component
@Directive({})export class SomeClass{
}
@Component({})export class SomeComponent{}
Structural directiveschanges the DOM layout
by adding and removing DOM elements
<div *ngIf=“condition”> some text..<div/>
add or remove from DOM tree
<div *ngFor=“let item of items”>{{ item.prop }}</div><div [ngSwitch]="status"> <template [ngSwitchCase]=“'beginner'">Young padawan</template> <template [ngSwitchCase]="'expert'">Jedi</template> <template ngSwitchDefault>Unknown</template></div>
create one tow for each items
select one matching case
Attribute directivechanges the appearance
or behavior of a DOM element
import { Directive, ElementRef, Input, Renderer } from '@angular/core';
@Directive({ selector: '[myHighlight]' })export class HighlightDirective { constructor(el: ElementRef, renderer: Renderer) { renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'yellow'); }}
2 Add to module
3 use in component template
@NgModule({ declarations : [ HighLight ]})
<div myHighLight > highlight me!! </div>
1 Define directive
Component- Replaces controller and element directive from ng1
- Can be routed to, is the page
- Uses a decorator on a class to turn the class into a component
myDirective
my-directive
component-name
component-name:D
ng1 naming is gone
- Can be used as component on a page, part of the page
First component@Component({ selector : ‘component-name’, template : ` <div> content </div> `, otherprops..})export class SomeComponent{}
<component-name></component-name>
Config object
Class is decorated
Use component
Componentbind value/methods to itInput
Output<my-component [user]=“user” (save)=“save( $event )” ></my-component>
export class ParentComponent{ save(ev){ console.log( ‘child called save..’ ,ev.value ); }}
export class ChildComponent{ @Input() user:User; @Output() save = new EventEmitter();
persist(){ this.save.emit({ value : ’data’ }); } }
<div> Here is the user {{ user.name }} <input [(ngModel)]=“user.name” > <button (click)=“persist()” >Save</button></div>
Emit data
Receive data
Send in event1
2
3
Pipes
Pipes<div *ngFor=“let item of items | pipe:arg”></div>
export class SomePipe implements PipeTransform { value: Array<Type> = []; transform(items: Array<Type>, arg: boolean) { if (items && items.length) { this.value = items.filter((item: Type) => { return item.property == arg; }); } return this.value; }}
@Pipe({ name: "pipe"}) 1
3 return matching item
implement interface
2
implement transform
Services
Service - transientWe DONT care about state
Ex utility service
export class Service { static calcSomeThing(){ return 1+1; }}
import { Service } from ‘utilities/service’;
export class SomeComponent{ doStuff(){ let answer = Service.calcSomething(); }}
Consume service, in componentDeclare service
Service - singleton
export class Service { getData(){ }}
@injectable()
import { Service } from ‘services/service’;
export class SomeComponent{
construcor( private srv:Service, private other:OtherService){
}
doStuff(){ var data = this.srv.getData(); }}
We care about stateDeclare Consume
Moduleswiring up the applications
ngModule
What modules am I dependent onWhat Components do I containBootstrap / Export if root module or feature moduleServices I can inject
123
@NgModule({ imports: [ BrowserModule, HttpModule ], declarations: [ AppComponent, DataComponent ], bootstrap/exports: [ AppComponent ], providers : [ srv1, srv2, srv3 ]})export class AppModule { }
1
2
3
4
4
What is a module?Logical division of code
Consist of
Other modules Directives ServicesOne root component = entry point for the module
You can and you should write your own module to make your app easier to work with
Remember
OROne/ many exported component/s = that other modules may use
Applicationmodule overview
Application module
Feature module
Commonmodule
Dependant on
Dependant on
Containsfeauture directives
feauture service
feature pipe
application wide
service
common component
common service
common pipe
bootstrap
exports
exports
Application module overviewRoot
module
Feature
module
Common
module
@NgModule({ imports : [ CommonModule ], exports : [ FeatureComponent ], declarations : [ FeatureComponent, Comp1, Comp2.. ], providers : [ moduleService ]})export class FeatureModule {}
@NgModule({ imports: [ FeatureModule BrowserModule ], declarations: [ AppComponent, Pipe, Directive ], bootstrap: [ AppComponent ], providers : [ globalService ]})export class AppModule {
}
Dependant on
Start app with
Consists of
Injectable srv
1
2
Framework modules
NgIf NgFor
imports
Browser module
Common
modulethat defines
Formsmodule
ReactiveForms module
OR
Lifecycle hooksComponent lifecycle
constructor
ngOnChanges
ngOnInit
ngDoCheck
ngAfterContentInit
ngAfterContentChecked
ngAfterViewInit
ngAfterViewChecked
ngOnDestroy
There are interfaces definedthat lets you hook into these events
Lifecycle events for a component
ngOnInit, ngOnDestroyclass SomeComponent implements OnInit {
ngOnInit(){ // setup things like fetch init data, // listen to events etc.. }}
class SomeComponent implements OnDestroy { ngOnDestroy(){ // cleanup, remove event listeners etc. }}
ngOnChangeswhen values are bound to a component
class Component implements OnChanges{ @Input() data;
ngOnChanges(changes: {[propertyName: string]: SimpleChange}) { for (let propName in changes) { let chng = changes[propName]; let cur = JSON.stringify(chng.currentValue); let prev = JSON.stringify(chng.previousValue); }
}}
First projectapp with a few components and a service
show a list of things
App component
List component
main.ts
app.module.ts
index.html
@NgModule({ imports : [ BrowserModule ], bootstrap : [ AppComponent], declarations : [ AppComponent, ListComponent ], providers : [ ]})export class AppModule {}
Put our components in declarations so they know of each other
NO services yet
AppComponents is our entry point
app.component.ts
@Component({ selector : ‘app’, template : ‘<list-component></list-component>’ })export class AppComponent{
}
App component
@Component({ selector : ‘list-component’ template : `
<div *ngFor=“let person of persons”> {{ person.name }} <button (click)=“select( person )” >select</button></div><div *ngIf=“selectedPerson” > Selected: {{ selectedPerson.name }}</div>`})export class ListComponent { persons:Array<Person>; selectedPerson:Person; constructor(){ this.persons = [{
name : ‘Darth Vader’},{ name : ‘Luke Skywalker’}, { name : ‘Yoda ’}]
}
select( person:Person ){ this.selectedPerson = person; }
}
List componentrepeater
show if exist
Data fetchingFetch API or Rxjs
ProductServic
e
Http Servic
eWebServerGET GET
Promise or Observable
Fetching data with fetch api and promise
fetch(‘data.json',init).then(function(response) { return response.blob();})
var init = { method: 'GET', headers: myHeaders, mode: 'cors', cache: 'default' };
There is always this one if you don’t want to use Observables
import { Http } from ‘@angular/http’
@Injectable()class Service{ constructor( private http:Http ) {
}
getData(){ return this.http.get(url); } }
Http client in angular 2export class Component{ data;
constructor( private service:Service ){ }
ngOnInit(){ this.service .getData() .map( res:Response => res.json() ) .subscribe( json => this.data = json, err => console.log(err) ) }}
import ‘rxjs/add/operator/map’
ImportsSo when dealing with fetching data ensure you import:
The needed operator import, one for each import
import ‘rxjs/add/operator/operator’ operator = do, map, catch etc..
import { Observable } ‘rxjs/Observable’
import { Http, Response } ‘@angular/http‘
Common classes
From promise to Observable
Problems with promise
Not cancellable
We need to write a lot of code to retry our logic, becomes messy
Only returns one value
Not easy to compose with other things like callbacks and event etc, although them being async in their nature
Cumbersome to retry
A better way, observables
stream of values over time
1 2 3 4 5 6
Observablebreakdown
var stream = new Rx.Observable.create( fnSuccess,fnError,fnCompleted )
stream.subscribe((data) => { console.log( “Data”,data );})
nothing happens till someone subscribes
Observableunder the hood
var stream = Rx.Observable.create(function(observer){ observer.onNext(1); observer.onNext(2); observer.onNext(3); observer.onError( ‘there is an error’ )})
stream.subscribe( function(data){ console.log( data ); }, function(error) { console.error( error ); })
emit values
report error
1
1
2
2
Observablecleaning up
var homemadeStream = Rx.Observable.create((observer) => { var i=0;
var handle = setInterval(function() { observer.onNext( i++ ); }, 500);
return function(){ console.log('Disposing timeout'); clearTimeout( handle ); }});
var subscription2 = homemadeStream.subscribe((val) => { console.log('Homemade val',val);});
setTimeout(function() { console.log('Cancelling homemadeStream'); subscription2.dispose();}, 1500);
define dispose behaviour
Call dispose
Observableturning something into an
observablevar stream = Rx.Observable.create(function(observer){ var request = new XMLHttpRequest();
request.open( ‘GET’, ‘url’ ); request.onload = function(){ if(request.status === 200) { observer.onNext( request.response ); observer.onCompleted(); } else { observer.onError( new Error( request.statusText ) ) } } request.onerror = function(){ observer.onError( new Error(‘unknown error’) ); } request.send();})
1
2
3
3
stream.subscribe( (result) => { console.log( result );
} err => {}() => {})
132
Rich compositionCreate an observable from different types of sourcesUse operators to format the observable output
Everything is a stream
Everything can be turned into a a streamalmost
click events
key events
data from
server
callbacks
socket
DONT create observables by hand
Different ways to create an observable
Rx.Observable.fromArray([ 1,2,3,4 ])
Rx.Observable.fromEvent(element, ‘event’);Rx.Observable.fromArray(eventEmitter, ‘data’, function(){})
Rx.Observable.fromNodeCallback(fs.createFile)
Rx.Observable.fromCallback(obj.callback)
Rx.Observable.range(1,3)Rx.Observable.interval(miliseconds)
There is an operator for that
var stream = Rx.Observable.interval(500).take(5).filter((val) = > { return val % 2 === 0;}).map((val) => { return “val” + 1;})
Operators
stream.subscribe(() => {})
operator is a function that returns an observable
emit value every 500 ms
limit number of values
operator: change the value
operator: only emit certain values
Operatorsoverview
120+ operators ( 60+ Rxjs5 )
Combination Conditional
Creational
Error handling
Filtering
Transformation
Utility
Categories
How am I gonna keep track of that?
Start with these operators
map
filter
flatMap
switchMap
do
merge
of
interval
take
Marble diagramsconcat
Stream
Other stream
Resulting stream
1
Most operators are covered at rxmarbles.com
2 3
4 5
1 2 3 4 5
stream/s + operation = new stream
concat
Marble diagramsconcat
Stream
Resulting stream
1
Most operators are covered at rxmarbles.com
2 3
1 2 3
stream/s + operation = new stream
delay
flatMapUnderstand
flatMapRx.Observable.of(1,2,3).map( (val) => {
} ) return Rx.DOM.getJSON( ‘data’ + val +‘.json' )
Becomes a list of observables, hard to work with
Rx.Observable.of(1,2,3).flatMap( (val) => {
} )return Rx.DOM.getJSON( ‘data’ + val +‘.json' )
Flatten all observables to a meta stream
flatMapin conclusion
Is for scenarios when you come from one type of streams and wants it to become something completely else like a stream of clicks becomes a stream of ajax calls
clicks ajax persons
Also avoid thisajax
clicks
ajax
ajax
Subscribe
Subscribe
Subscribe
Instead becomeajax
clicks
ajax
ajax
ajax personsflat mapmap
one subscribe
Delay,the whole sequence
stream.delay(1000).subscribe( (val) => { var newTime = new Date().getTime(); console.log('Delayed', val + " " + (newTime - time));})
Rx.Observable.merge( Rx.Observable.of('Marco').delay(1000), Rx.Observable.of('Polo').delay(2000)).subscribe((val) => { var newTime = new Date().getTime(); console.log('Merged Delay', val + " " + (newTime - time));})
Delay….. 1 second
123
….. 1 second Marco
….. 2 secondPolo
Subjectthe double nature
Observer Observable
Subject
the subject can act as a proxy for a group of subscribers and a source
Subject
var subject = new Rx.Subject();
subject.subscribe((val) => { console.log( 'Produced by subject', val );});
subject.onNext(1);subject.onCompleted();
Acts like an observer
Acts like an observable
Subjectvar subject = new Rx.Subject();
var source = Rx.Observable.interval(500);
source.subscribe(subject);
subject.subscribe( (val) => { console.log('Sub', val); }, (err) => console.log(err), () => console.log('completed'));
setTimeout(function() { subject.onCompleted();}, 3000);
Pass subject as an observer
Receives all the values pushed out by the source
Able to stop receiving values
2
1
3
var subject = new Rx.Subject();
var source = Rx.Observable.interval(500).take(3);
source.subscribe( subject );
subject.subscribe((val) => { console.log('Subject', val);});
subject.onNext('Mess1');subject.onNext('Mess2');
setTimeout(function() { subject.onCompleted();}, 1600);
Listens to all values from source
Add to stream
Order important
SubjectonNext() before subscribe is lost
subject.onNext(1);
var subject = Rx.ReplaySubject();subject.onNext(1);
subject.subscribe((val) = > { console.log('Replay', val);})
subject.onNext(2);subject.onNext(3);
var subject = Rx.Subject();subject.onNext(1);
subject.subscribe((val) = > { console.log('Replay', val);})
subject.onNext(2);subject.onNext(3);
Normal subject, everything before subscribe is lost
Replay subject, nothing is lost
Subject - typesdont do this
1 2
BehaviourSubject
Good for default values
/* Initialize with initial value of 42 */var subject = new Rx.BehaviorSubject(42);
var subscription = subject.subscribe( function (x) { console.log('Next: ' + x.toString()); }, function (err) { console.log('Error: ' + err); }, function () { console.log('Completed'); });
subject.onNext(56);
subject.onCompleted();// => Completed
Init/Default value
Next: 42
Next: 56
Schedulersbending time
Because scheduler has its own virtual clockAnything scheduled on that scheduler will adhere to time denoted on the clock
I.e we can bend time for ex unit testing
var onNext = Rx.ReactiveTest.onNext;var scheduler = new Rx.TestScheduler();var subject = scheduler.createColdObservable( onNext(100,'first'), onNext(200,'second'));
var result;
subject.subscribe((val) => { result = val;});
scheduler.advanceBy( 100 );
console.log('Should equal', result === 'first');
scheduler.advanceBy( 100 );
console.log('Should equal', result === 'second');
Advance timeAssert
Advance timeAssert
create observable from scheduler
emit values
var testScheduler = new Rx.TestScheduler();
var stream = Rx.Observable.interval(1000, testScheduler).take(5).map((val) => { return val + 1}).filter((i) => { return i % 2 === 0});
var result;stream.subscribe((val) => result = val );
console.log('testing function’);
testScheduler.advanceBy(1000);testScheduler.advanceBy(1000);testScheduler.advanceBy(1000);
console.log('Should equal', result === 2);
testScheduler.advanceBy(1000);testScheduler.advanceBy(1000);console.log('Should equal', result === 4);
replace default scheduler
0 1 2
Further reading
https://xgrommx.github.io/rx-book
http://www.learnrxjs.io/
bacon.js
Back to angular 2and some recipes
flatMap auto complete
flatmapExample = Rx.Observable.fromEvent(input,'keyup').map( function(ev){ return ev.target.value;}).filter(function(text){ return text.length >=3;}).distinctUntilChanged().flatMap( function(val){ return Rx.DOM.getJSON( 'data3.json' );})
flatmapExample.subscribe( function(result){ console.log('Flatmap', result);})
Transform event to char
Wait until we have 3 chars
Only perform search if this ‘search’ is unique
Debouncestop clicking the save button
var debounceTime = Rx.Observable.fromEvent(button,'click').debounce(2000);
debounceTime.subscribe( function(){ console.log('mouse pressed');})
Ignores all generated mouse click events
for 2 seconds
click click click click click
ignore ignore ignore ignore use
…. 2 seconds passed
Retryfor those shaky connections
var stream = Rx.DOM.get(‘/products.json’).delay( 5000 ).retry(5);
stream.subscribe((val) => { console.log('Data', val);}, err => console.log(err));
5 failed attempts then we hit error callback
Its so easy to retry, imagine how messy this code would be with a promise
Component to ComponentYou want something that can produce value,
that you can listen to
this.subject = new Rx.Subject();
function sendData(data){ this.subject.next( data )}
function getSubject(){ return this.subject;}
//service implComponent
1
Component2
service.getSubject().subscribe(()=>{})
service.sendData( data )
BUT, can be made nicer with a uniform data flow
Routingsetup
NgModule({ imports : [ BrowserModule … other modules RouterModule ], declarations : [ AppComponent, … other components ], bootstrap : [ AppComponent ]})export class AppModule {}
import { RouterModule } from ‘@angular/router’
Router serviceRouter directivesand our configured routes
contains
Routingcomponent based
[ { path : ‘jedis’, component : JediListComponent, }, { path : ‘jedis/:id’, component : JediComponent, }, { path : ‘’, redirect : ‘jedis’, pathMatch: ‘full’ }, { path : ‘**’, component : PageNotFoundComponent, }
]
domain/jedis
domain/jedis/1
domain/anything
Order matters, first match wins
Routingsetting up configured routes
NgModule({ imports : [ BrowserModule … other modules RouterModule.forRoot([ [{ path: ‘jedis’, component : ‘JediList’ }] .. and so on ]) ], declarations : [ AppComponent, … other components ], bootstrap : [ AppComponent ]})export class AppModule {}
Routingdirectives
<a [routerLink]=“[ ‘/jedis’ ]” >Jedis</a>
{ path: ‘jedis’, component : ‘JediList’}
<router-outlet></router-outlet>Where to render content, think ng-view
Creates a link using routerLink attribute directive
Corresponding configured route
Routing with parameters
{ path: ‘jedis/:id’, component : ‘JediDetail’}
<a [routerLink]=“[ ‘/jedis’, jedi.id ]” >{{ jedi.name }}</a><div *ngFor=“let jedi of jedis”>
</div>import { ActivatedRoute } from ‘@angular/route’;
export class JediDetail{ constructor(private route:ActivatedRoute){ this.route.snapshot.params[‘id’]; // get data by id }}
Routingrouting service
import { Router } from ‘@angular/router’export class JediDetailComponent{ constructor( router:Router ) { } onSave(){ this.service .save( someData ) .subscribe((result) => { this.router.navigate([‘/jedis’]); }) }}
this.router.navigate([‘url’,<parameter>]);
Routing guardsCanActivate
CanDeactivate
Resolve
CanLoad
Is it allowed to navigate to this route
Is it allowed to navigate away from this route
Prefetch data before going to the route
Prevent async routing
CanActivateexample
export class AuthenticateGuard implements CanActivate { constructor( private user:User ){
}
canActivate():boolean { return this.user.roles.contains(‘Administrator’); } }
RouterModule.forRoot([ { path: ’adminpage’, canActivate : [ AuthenticateGuard ], component : AdminPageComponent }])
angular-cli
forms
augury
Further reading
angular-universal
AOT compilation
scaffolding tool
debug tool for browser
very competent way of dealing with forms and validation
serverside rendering
precompilation of code, faster, smaller bundle, no JIT, no compiler in bundle
Thank you