Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Reactive Frontends with RxJS and Angular

Reactive Frontends with RxJS and Angular

VoxxedSG

June 17, 2018
Tweet

More Decks by VoxxedSG

Other Decks in Programming

Transcript

  1. REACTIVE EFFORTS IN SPRING Spring framework Spring Boot Project Reactor

    Spring Security Spring Data Spring Cloud Spring Integration …
  2. AGENDA •RxJS • Observables • Operators • Combining observables •

    Error handling •Angular • Managing subscriptions • HttpClient • Reactive Forms • WebSockets / SSE
  3. ASYNC IN WEB APPS •DOM Event (0-N values) •AJAX (1

    value) •WebSocket (0-N values) •Server Sent Event (0-N values) •Animation (0-N values)
  4. REACTIVE PROGRAMMING •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
  5. CREATING AN OBSERVABLE const welcome$ = create((subscriber) => { subscriber.next('Welcome');

    subscriber.next('to'); subscriber.next(‘Voxxed Days SG’); subscriber.next('!'); subscriber.complete(); });
  6. OBSERVER var observer = { next: x => console.log('Next value:

    ' + x), error: err => console.error('Error: ' + err), complete: () => console.log('Complete notification') }; export interface Observer<T> { closed?: boolean; next: (value: T) => void; error: (err: any) => void; complete: () => void; }
  7. UNSUBSCRIBE •Release resources allocated by the observable •No more events

    sent to the registered observer const mouseMove$ = fromEvent(document, 'mousemove'); const subscription = mouseMove$.subscribe(console.log); … subscription.unsubscribe();
  8. OPERATORS 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
  9. PIPEABLE OPERATORS <T, R>(source: Observable<T>) => Observable<R> interval(1000) .pipe( filter(x

    => x % 2 === 0), map(x => x * x), take(5) ) .subscribe(x => console.log(x))
  10. COMBINING OBSERVABLES: MERGE const spring$ = of('spring', 'spring-boot', ‘spring-data'); const

    cloud$ = of('cloud-native', 'cloudfoundry', 'bosh'); merge(spring$, cloud$).subscribe(console.log);
  11. Spring Spring cloud$ Spring Boot Spring Boot Cloud Native Cloud

    Native Spring Data Spring Data Cloud Foundry Cloud Foundry Bosh Bosh merge(spring$, cloud$) spring$
  12. switchMap 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
  13. forkJoin 1 const users$ = forkJoin( findUser(1), findUser(2), findUser(3) );

    ... users$.subscribe(console.log); 2 3 fork join
  14. forkJoin join 1 const users$ = forkJoin( findUser(1), findUser(2), findUser(3)

    ); users$.subscribe(console.log); fork 2 3 [ ] , ,
  15. ERRORS •When an error occurs the observable is cancelled and

    no data passes though range(1, 20) .pipe( map(num => { if (num === 13) { throw new Error('I don\'t like 13'); } return num; }) )
  16. REMEMBER THE OBSERVER? export interface Observer<T> { closed?: boolean; next:

    (value: T) => void; error: (err: any) => void; complete: () => void; }
  17. ERRORS 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 … … •When an error occurs the observable is cancelled and no data passes though
  18. catchError range(1, 20) .pipe( map(num => { if (num ===

    13) { throw new Error('I don\'t like 13'); } return num; }), catchError(err => range(100, 3)) ) .subscribe(console.log); Output: 1 2 3 4 5 6 7 8 9 10 11 12 100 101 102
  19. ANGULAR •RxJS can be used with any framework •Angular has

    reactivity at its core, leveraging RxJS • HttpClient • Reactive Forms • Router • Component Communication
  20. UNSUBSCRIBING @Component({ selector: 'app-mouse-position', template: '<span>{{mousePosition}}</span>' }) export class MousePositionComponent

    implements OnInit, OnDestroy { ngOnInit() { } ngOnDestroy() { } } const mouseDown$ = fromEvent(document, 'mousemove') .pipe( map(val => `${val.offsetX} ${val.offsetY}`) ); Define Observable
  21. UNSUBSCRIBING @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$ = fromEvent(document, 'mousemove') .pipe( map(val => `${val.offsetX} ${val.offsetY}`) ); Subscribe
  22. UNSUBSCRIBING @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$ = fromEvent(document, 'mousemove') .pipe( map(val => `${val.offsetX} ${val.offsetY}`) ); this.mouseSubscription.unsubscribe(); Unsubscribe
  23. ASYNC PIPE @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
  24. HttpClient 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));
  25. retry • 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))) )
  26. REACTIVE FORMS •Two options to build forms • Reactive and

    template-drive forms •Monitor changes by subscribing to one of the form control properties this.favoriteConference.valueChanges this.favoriteConference.statusChanges (valid / invalid) <input formControllName=“favoriteConference” ..>
  27. SERVER SENT EVENTS •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)); } );
  28. WEBSOCKET const ws$ = Observable.create(observer => { const source =

    new WebSocket(‘ws://localhost:8080/search-push'); source.addEventListener('message', event => observer.next(event)); } ); •Wrapping a WebSocket in an Observable
  29. THIS IS A TITLE ws$.subscribe(...) new WebSocket(...) ws$.subscribe(...) new WebSocket(...)

    ws$.subscribe(...) new WebSocket(...) •Wrapping a WebSocket in an Observable
  30. SUBSCRIPTION SHARING const ws$ = Observable.create(observer => { const source

    = new WebSocket(‘ws://localhost:8080/search-push'); source.addEventListener('message', event => observer.next(event)); } ) .share();