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

Reactive Programming with RxJS and Angular

Reactive Programming with RxJS and Angular

Reactive programming is perfect for some use cases in web development. For instance, Angular’s HTTP module transforms HTTP requests to RxJS Observables by default. If you enjoy using Lodash or Underscore, think of RxJS as their async version. What does this mean? What are Observables? How should you chain HTTP requests the RxJS way? This and more will be presented and show cased in on an Angular Application.
Presented at BASTA!

Shmuela Jacobs

September 27, 2017
Tweet

More Decks by Shmuela Jacobs

Other Decks in Programming

Transcript

  1. Using RxJS - everything is an event stream • Http

    • Websockets • DOM events • Animations • And more...
  2. Angular and RxJS • Routing • Http (HttpClient) • AngularFire2

    • Realtime DB - lists and objects • DB queries • User status • ngRx
  3. Subscription @Component({ ... template: ` <h1>{{ user?.name }}</h1>` }) export

    class RecipeComponent implements OnInit, OnDestroy { user; subscription; constructor(private userService: UserService) {} ngOnInit() { this.subscription = this.userService.user$.subscribe(user => { this.user = user; }); ngOnDestroy() { this.subscription.unsubscribe(); }
  4. Async Pipe @Component({ ... template: ` <h1>{{ (user$ | async)?.name

    }}</h1> <div *ngIf="recipe$ | async; let recipe"> <h2>{{ recipe?.id }}: {{ recipe?.title }}</h2> </div>` }) export class RecipeComponent implements OnInit { @Input() recipeId; recipe$; user$; constructor(private recipeService: RecipeService) {} ngOnInit() { this.user$ = this.userService.getUser(); this.recipe$ = this.recipeService.getRecipe(this.recipeId); }
  5. Manipulate Data - map @Injectable() export class DataService { constructor(private

    http: httpClient) {} getItem(id): Observable<Recipe> { return this.http.get<Recipe>(`https:/data.com/items/${id}`) .map(res => { return { ...res, id }; }) } }
  6. Useful (Popular) Operators import 'rxjs/add/operator/filter'; numbers$ .filter(res => res >

    100) .scan((total, current) => total + current) .do(res => updateNewTotal(res));
  7. Get User Data - switchMap @Injectable() export class UserService {

    user$; constructor( afAuth: AngularFireAuth, afDB: AngularFireDatabase) { this.user$ = afAuth.authState.switchMap(authDetails => {
 if (authDetails) { return afDB.object(`users/${authDetails.uid}`) } else { return Observable.of(null); } }); } }
  8. Routing export declare class ActivatedRoute { /** An observable of

    the URL segments matched by this route */ url: Observable<UrlSegment[]>; /** An observable of the matrix parameters scoped to this route */ params: Observable<Params>; /** An observable of the query parameters shared by all the routes */ queryParams: Observable<Params>; /** An observable of the URL fragment shared by all the routes */ fragment: Observable<string>; /** An observable of the static and resolved data of this route. */ data: Observable<Data>; ... }
  9. Routing Params - forEach @Component({ selector: 'app-recipe-display', template: ` <app-recipe-card

    [recipe]="recipe$ | async"> </app-recipe-card> ` }) export class RecipeComponent implements OnInit { recipe$; constructor( private recipeService: RecipeService, private route: ActivatedRoute) { } ngOnInit() { this.route.params.forEach(param => { this.recipe$ = this.recipeService.getRecipe(param.id);
 }); }
  10. Route Guards @Injectable() export class AuthGuard implements CanActivate { constructor(private

    userService: UserService) {} canActivate() { return this.userService.userData$.map(user => { return user.isAdmin; }); } } const routes: Routes = [ { path: 'admin', component: AdminComponent, canActivate: [AuthGuard] }, ... ];
  11. Route Guards CanActivate to mediate navigation to a route.
 CanActivateChild

    to mediate navigation to a child route.
 CanDeactivate to mediate navigation away from the current route.
 Resolve to perform route data retrieval before route activation.
 CanLoad to mediate navigation to a feature module loaded asynchronously.

  12. Dialog - Behavior Subject, next @Injectable() export class DialogService {

    dialog$ = new BehaviorSubject(null); createDialog(data) { const dialogData = { component: this.getDialogComponent(data), instanceData: { ... } // based on data }; this.dialog$.next(dialogComponentData); }
  13. Dialog - Behavior Subject, next ngOnInit() { this.dialogSubscription = this.dialog$.subscribe((dialogComponentData)

    => { this.mdDialog.open(dialogComponentData.component, { viewContainerRef: this.viewContainer, data: dialogComponentData.instanceData ... }); } } ngOnDestroy() { this.dialogSubscription.unsubscribe(); }
  14. Creating Observables const observable$ = Rx.Observable.create( observer => { observer.next(

    'hello' ) observer.next( 'world' ) }) const clicks$ = Rx.Observable.fromEvent(document, 'click') const promise$ = Rx.Observable.fromPromise(promise) const timer$ = Rx.Observable.timer(1000) const interval$ = Rx.Observable.interval(1000) const someArray = [...] const things$ = Rx.Observable.of(...someArray) // Array, an array-like object, a Promise, // an iterable object, or an Observable-like object const anything = Rx.Observable.from(someArray)
  15. DOM Events - fromEvent constructor(private el: ElementRef) {} ngOnInit(){ const

    nativeEl = this.el.nativeElement; const document = nativeEl.getRootNode(); const mousedown$ = Observable.fromEvent(nativeEl, 'mousedown')
 const mousemove$ = Observable.fromEvent(document, 'mousemove')
 const mouseup$ = Observable.fromEvent(document, 'mouseup'); const mousedrag$ = mousedown$.mergeMap((md: MouseEvent) => { return mousemove$.map((mm: MouseEvent) => { return { ... }; // new position }).takeUntil(mouseup$); }); this.subscription = mousedrag$.subscribe((pos) => { ... // update position }); }
  16. Completing Observables takeUntil(observable$): emits values until observable$ emits a value.

    take(n): emits N values before stopping the observable. takeWhile(predicate): tests the emitted values against a predicate, if it returns `false`, it will complete. first(): emits the first value and completes. first(predicate): checks each value against a predicate function, if it returns `true`, the emits that value and completes.
  17. More in Observables • Hot vs. Cold • Catch, Retry

    • Handle stream overload: • throttleTime • debounceTime • bufferTime, bufferCount
  18. Resources • RxJS Docs: http://reactivex.io/rxjs/ • Ben Lesh (YouTube, blog

    posts, conferences) • Interactive diagrams of Rx Observables: http://rxmarbles.com/ • 20 Examples: https://angularfirebase.com/lessons/rxjs-quickstart-with-20- examples/ • Thoughtram blog - three things you didn't know about the async pipe: 
 https://blog.thoughtram.io/angular/2017/02/27/three-things-you-didnt-know- about-the-async-pipe.html • Victor Savkin - Testing Race Conditions Using RxJS Marbles:
 https://blog.nrwl.io/rxjs-advanced-techniques-testing-race-conditions-using- rxjs-marbles-53e7e789fba5