Contenu connexe Similaire à Reactive frontends with RxJS and Angular (20) Plus de VMware Tanzu (20) Reactive frontends with RxJS and Angular2. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Reactive efforts in Spring
2
Spring
framework
Spring
Boot
Project
Reactor
Spring
Security
Spring
Data
Spring
Cloud
Spring
Integration
…
3. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Agenda
3
• RxJS
• Obervables
• Operators
• Combining observables
• Error handling
• Angular
• Managing subscriptions
• HttpClient
• Reactive Forms
• WebSockets /SSE
4. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ 4
5. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Language support
5
6. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Async in web apps
6
• DOM Event (0-N values)
• AJAX (1 value)
• WebSocket (0-N values)
• Server Sent Event (0-N values)
• Animation (0-N values)
7. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Reactive programming
7
Pros
• Clean and understandable code
• Focus on the result
• Complex operations out of the box
• Abort processing of data when you don’t need it (cancel)
Cons
• More difficult to debug
• Learning curve
8. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ 8
Observable.of(...)
Factory Methods
Observable.from(...)
Observable.fromEvent(...)
Observable.interval(...)
...
9. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ 9
Creating an Observable
const welcome$ = Observable.create((subscriber) => {
subscriber.next('Welcome');
subscriber.next('to');
subscriber.next('S1P');
subscriber.next('!');
subscriber.complete();
});
10. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ 10
Observer
subscribe
notification
11. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Observer
11
export interface Observer<T> {
closed?: boolean;
next: (value: T) => void;
error: (err: any) => void;
complete: () => void;
}
var observer = {
next: x => console.log('Next value: ' + x),
error: err => console.error('Error: ' + err),
complete: () => console.log('Complete notification')
};
12. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Observer
12
Observable.interval(1000) Producer
Consumer.subscribe(val => console.log(val))
.operator(...)
.operator(...)
.operator(...)
Pipeline
13. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Unsubscribe
13
const mouseMove$ = Observable.fromEvent(document, 'mousemove');
const subscription = mouseMove$.subscribe(console.log);
…
subscription.unsubscribe();
- Release resources allocated by the observable
- No more events sent to the registered observer
14. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Operators
14
create
fromEvent
range
…
Creation
map
scan
switchMap
…
Transformation
debounce
filter
take
…
Filtering
concat
merge
startWith
…
Combination
catch
retry
retryWhen
Error Handling
share
publish
multicast
Multicast
do / tap
delay
toPromise
…
Utility
every
defaultIfEmpty
Conditional
15. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Lettable Operators
15
<T, R>(source: Observable<T>) => Observable<R>
Observable.interval(1000)
.pipe(
filter(x => x % 2 === 0),
map(x => x * x),
take(5)
)
.subscribe(x => console.log(x))
16. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ 16
17. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Combining Observables: merge
17
const spring$ =
Observable.of('spring', 'spring-boot', ‘spring-data');
const cloud$ =
Observable.of('cloud-native', 'cloudfoundry', 'bosh');
Observable.merge(spring$, cloud$).subscribe(console.log);
18. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ 1818
Spring
Spring
spring$
cloud$
Spring
Boot
Spring
Boot
Cloud
Native
Cloud
Native
Spring
Data
Spring
Data
Cloud
Foundry
Cloud
Foundry
Bosh
Bosh
merge(spring$, cloud$)
19. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
switchMap
19
Observable.of(1, 2, 3)
.pipe(
switchMap(num => Observable.of(num, num * num))
).subscribe(console.log);
1
switchMap(num => Observable.of(num, num * num)
1 1
2
2 4
3
3 9
20. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
forkJoin
20
1
const users$ = Observable.forkJoin(
findUser(1),
findUser(2),
findUser(3)
);
...
users$.subscribe(console.log);
2
3
fork
join
21. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
fork
forkJoin
21
1
const users$ = Observable.forkJoin(
findUser(1),
findUser(2),
findUser(3)
);
users$.subscribe(console.log);
2 3
join
22. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
join
forkJoin
22
1
const users$ = Observable.forkJoin(
findUser(1),
findUser(2),
findUser(3)
);
users$.subscribe(console.log);
fork 1 2 3
2 3
23. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
join
forkJoin
23
1
const users$ = Observable.forkJoin(
findUser(1),
findUser(2),
findUser(3)
);
users$.subscribe(console.log);
fork
2 3[ ], ,
24. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Errors
24
• When an error occurs the observable is cancelled and no data passes though
• Errors are propagated to the downstream observers
Observable.range(1, 20)
.pipe(
map(num => {
if (num === 13) {
throw new Error('I don't like 13');
}
return num;
})
)
25. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Remember the Observer?
25
export interface Observer<T> {
closed?: boolean;
next: (value: T) => void;
error: (err: any) => void;
complete: () => void;
}
26. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Errors
26
• When an error occurs the observable is cancelled and no data passes though
• Errors are propagated to the downstream observers
Observable.range(1, 20)
.pipe(
map(num => {
if (num === 13) {
throw new Error('I don't like 13');
}
return num;
})
).subscribe(
num => console.log('Got num ' + num),
err => console.log('Upssss...' + err));
map
1
1
13
X
…
…
27. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
catchError
27
Observable.range(1, 20)
.pipe(
map(num => {
if (num === 13) {
throw new Error('I don't like 13');
}
return num;
}),
catchError(err => Observable.range(100, 3))
)
.subscribe(console.log);
Output:
1
2
3
4
5
6
7
8
9
10
11
12
100
111
112
28. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Angular
28
RxJS can be used with any framework
Angular has reactivity at its core, leveraging RxJS
- HttpClient
- Reactive Forms
- Router
- Component communication
29. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Unsubscribing
29
@Component({
selector: 'app-mouse-position',
template: '<span>{{mousePosition}}</span>'
})
export class MousePositionComponent implements OnInit, OnDestroy {
ngOnInit() {
}
ngOnDestroy() {
}
}
30. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Unsubscribing
30
@Component({
selector: 'app-mouse-position',
template: '<span>{{mousePosition}}</span>'
})
export class MousePositionComponent implements OnInit, OnDestroy {
ngOnInit() {
}
ngOnDestroy() {
}
}
const mouseDown$ = Observable.fromEvent(document, 'mousemove')
.pipe(
map(val => `${val.offsetX} ${val.offsetY}`)
);
Define Observable
31. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Unsubscribing
31
@Component({
selector: 'app-mouse-position',
template: '<span>{{mousePosition}}</span>'
})
export class MousePositionComponent implements OnInit, OnDestroy {
mousePosition: String;
mouseSubscription: Subscription;
ngOnInit() {
}
ngOnDestroy() {
}
}
this.mouseSubscription = mouseDown$
.subscribe((pos: String) => this.mousePosition = pos);
const mouseDown$ = Observable.fromEvent(document, 'mousemove')
.pipe(
map(val => `${val.offsetX} ${val.offsetY}`)
);
Subscribe
32. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Unsubscribing
32
@Component({
selector: 'app-mouse-position',
template: '<span>{{mousePosition}}</span>'
})
export class MousePositionComponent implements OnInit, OnDestroy {
mousePosition: String;
mouseSubscription: Subscription;
ngOnInit() {
}
ngOnDestroy() {
}
}
this.mouseSubscription = mouseDown$
.subscribe((pos: String) => this.mousePosition = pos);
const mouseDown$ = Observable.fromEvent(document, 'mousemove')
.pipe(
map(val => `${val.offsetX} ${val.offsetY}`)
);
this.mouseSubscription.unsubscribe();Unsubscribe
33. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Async pipe
33
@Component({
selector: 'app-mouse-position',
template: '<span>{{mousePosition$ | async}}</span>'
})
export class MousePositionComponent implements OnInit {
mousePosition$: Observable<string>;
ngOnInit() {
this.mousePosition$ = fromEvent(document, 'mousemove')
.pipe(
map(val => `${val.offsetX} ${val.offsetY}`)
);
}
}
No subscription management
34. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
HttpClient
34
- Simple API
- Strong typing of request
and response objects
- Support for interceptors
35. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
HttpClient
35
get<T>(url: string, options?: {
headers?: HttpHeaders | {
[header: string]: string | string[];
};
observe?: 'body';
params?: HttpParams | {
[param: string]: string | string[];
};
reportProgress?: boolean;
responseType?: 'json';
withCredentials?: boolean;
}): Observable<T>;
httpClient.get<User>('http://localhost:8080/user/1')
.subscribe((user: User) => console.log('User: ' + user.name));
36. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Retry
36
- Retry the failed stream
- Propagates the error downstream if not successful
httpClient.get<User>('http://localhost:8080/user/1')
.pipe(
retry(3)
)
httpClient.get<User>('http://localhost:8080/user/1')
.pipe(
retryWhen(errors => errors.pipe(delay(1000).take(3)))
)
37. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Reactive Forms
37
• Two options to build forms
• Reactive and template-driven forms
• Monitor changes by subscribing to one of the form control properties
this.favoriteConference.valueChanges
this.favoriteConference.statusChanges
(valid / invalid)<input formControllName=“favoriteConference” ..>
38. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Server Sent Events
38
Wrapping an EventSource in an Observable
const eventSource$ = Observable.create(observer => {
const source = new EventSource('http://localhost:8080/search-push');
source.addEventListener('message',
event => observer.next(event));
}
);
* EventSource added in typings.d.ts
39. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
WebSocket
39
Wrapping a WebSocket in an Observable
const ws$ = Observable.create(observer => {
const source = new WebSocket(‘ws://localhost:8080/search-push');
source.addEventListener('message',
event => observer.next(event));
}
);
40. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
WebSocket
40
Wrapping a WebSocket in an Observable
ws$.subscribe(...)
new WebSocket(...)
ws$.subscribe(...)
new WebSocket(...)
ws$.subscribe(...)
new WebSocket(...)
41. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
Subscription sharing
41
const ws$ = Observable.create(observer => {
const source = new WebSocket(‘ws://localhost:8080/search-push');
source.addEventListener('message',
event => observer.next(event));
}
)
.share();
44. Unless otherwise indicated, these slides are © 2013-2016 Pivotal Software, Inc. and licensed under a Creative Commons
Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/ 44