observables structural directives nils mehlhorn
play

OBSERVABLES + STRUCTURAL DIRECTIVES = NILS MEHLHORN - PowerPoint PPT Presentation

OBSERVABLES + STRUCTURAL DIRECTIVES = NILS MEHLHORN nils-mehlhorn.de www freelance software engineer @n_mehlhorn founder of scenelab.io 2 NGRX BOOK Pay what you want for the complete learning resource gum.co/angular-ngrx-book


  1. OBSERVABLES + STRUCTURAL DIRECTIVES = ♥

  2. NILS MEHLHORN nils-mehlhorn.de www freelance software engineer @n_mehlhorn founder of scenelab.io 2

  3. NGRX BOOK Pay what you want for the complete learning resource gum.co/angular-ngrx-book @n_mehlhorn 3

  4. @Component({...}) export class UsersComponent implements OnInit { users: User[] = [] constructor(private userService: UserService) {} ngOnInit() { this.userService.getAll().subscribe(users => { this.users = users }) } } <p>{{ users.length }} users online</p> You forgot to unsubscribe! 🙆 … do you have to unsubscribe everytime? 🤕 @n_mehlhorn 4

  5. WHY UNSUBSCRIBE? One-Off Observables e.g. HTTP request, timer cancellation ➝ “observable etiquette” ➝ @n_mehlhorn 5

  6. CANCELLATION server q e r P T T H clicks app “submit” user @n_mehlhorn 6

  7. CANCELLATION cancelled if not yet done server q e r P T T H navigates app away user @n_mehlhorn 7

  8. WHY UNSUBSCRIBE? One-Off Observables Long-Lived Observables e.g. HTTP request, timer e.g. store, router events cancellation no memory leak ➝ ➝ “observable etiquette” ➝ @n_mehlhorn 8

  9. app MEMORY LEAK component service subscribe() observable cut by unsubscribe or completion subscriber Recommended Read How to create a memory leak in Angular -- Kevin Kreuzer @n_mehlhorn 9

  10. Observable is just a function that takes an observer and returns a function Ben Lesh RxJS Lead @n_mehlhorn 10

  11. subscribe() callbacks or Subject passed to subscribe() Observable is just a function that takes an observer and returns a function Ben Lesh cancellation returned by subscribe() RxJS Lead @n_mehlhorn 11

  12. @Component({...}) export class UsersComponent implements OnInit, OnDestroy { users: User[] subscription: Subscription constructor(private userService: UserService) {} ngOnInit() { this.subscription = this.userService.getAll().subscribe(users => { this.users = users }) } ngOnDestroy() { this.subscription.unsubscribe() } } IMPERATIVE MANUAL SUBSCRIPTION MANAGEMENT 12 @n_mehlhorn

  13. @Component({...}) export class UsersComponent implements OnInit, OnDestroy { users: User[] destroy$ = new Subject<void>() constructor(private userService: UserService) {} ngOnInit() { this.userService.getAll() .pipe(takeUntil(this.destroy$) .subscribe(users => { this.users = users }) } ngOnDestroy() { this.destroy$.next() } } DECLARATIVE MANUAL SUBSCRIPTION MANAGEMENT @n_mehlhorn 13

  14. rxjs-tslint-rules MANUAL SUBSCRIPTION MANAGEMENT verbose & error-prone 👏 full control 👎 OnPush change detection 👏 access to values from 👎 requires trigger other methods falsy values 👎 required for observables not reflected in view (e.g. updating a user) Recommended Read Loading Indication in Angular -- Nils Mehlhorn @n_mehlhorn 14

  15. @Component({ ... changeDetection: ChangeDetectionStrategy.OnPush }) export class UsersComponent implements OnInit { users$: Observable<User[]> constructor(private userService: UserService) {} ngOnInit() { this.users$ = this.userService.getAll() } } <p *ngIf="users$ | async as users; else loading"> {{ users.length }} users online </p> <ng-template #loading>Loading…</ng-template> AUTOMATIC SUBSCRIPTION MANAGEMENT WITH NGIF & ASYNCPIPE @n_mehlhorn 15

  16. @Component({ ... changeDetection: ChangeDetectionStrategy.OnPush }) export class UsersComponent implements OnInit { users$: Observable<User[]> constructor(private userService: UserService) {} ngOnInit() { this.users$ = this.userService.getAll() } } <p *ngIf="users$ | async as users; else loading"> {{ users.length }} users online </p> <ng-template #loading>Loading…</ng-template> AUTOMATIC SUBSCRIPTION MANAGEMENT WITH NGIF & ASYNCPIPE @n_mehlhorn 16

  17. ASYNCPIPE @Pipe({name: 'async', pure: false}) export class SimpleAsyncPipe implements OnDestroy, PipeTransform { private latestValue: any = null private subscription: Subscription = null unsubscribes ● constructor(private cd: ChangeDetectorRef) {} triggers change ● transform(observable: Observable<any>): any { detection this.subscription = observable.subscribe(value => { this.latestValue = value this.cd.markForCheck() }) return WrappedValue.wrap(this.latestValue) } ngOnDestroy(): void { this.subscription.unsubscribe() } } @n_mehlhorn 17

  18. @Component({ ... changeDetection: ChangeDetectionStrategy.OnPush }) export class UsersComponent implements OnInit { users$: Observable<User[]> constructor(private userService: UserService) {} ngOnInit() { this.users$ = this.userService.getAll() } } <p *ngIf="users$ | async as users; else loading"> {{ users.length }} users online </p> <ng-template #loading>Loading…</ng-template> AUTOMATIC SUBSCRIPTION MANAGEMENT WITH NGIF & ASYNCPIPE @n_mehlhorn 18

  19. ONPUSH CHANGE DETECTION updates view only when 1. @Inputs are reassigned 2. events occur on component or children 3. markForCheck() called ➜ faster due to less updates source: angular/change_detection_spec.ts L282 @n_mehlhorn 19

  20. @Component({ ... changeDetection: ChangeDetectionStrategy.OnPush }) export class UsersComponent implements OnInit { users$: Observable<User[]> constructor(private userService: UserService) {} ngOnInit() { this.users$ = this.userService.getAll() } } <p *ngIf="users$ | async as users; else loading"> {{ users.length }} users online </p> <ng-template #loading>Loading…</ng-template> AUTOMATIC SUBSCRIPTION MANAGEMENT WITH NGIF & ASYNCPIPE @n_mehlhorn 20

  21. Structural directives are responsible for HTML layout. They shape or reshape the DOM's structure, typically by adding, removing, or manipulating elements. Angular Docs @n_mehlhorn 21

  22. STRUCTURAL DIRECTIVES: MICROSYNTAX <p *ngIf="users$ | async as users; else loading"> {{ users.length }} users online </p> <ng-template #loading>Loading…</ng-template> microsyntax desugaring <ng-template [ngIf]="users$ | async as users" [ngIfElse]="loading"> <p>{{ users.length }} users online</p> </ng-template> <ng-template #loading>Loading…</ng-template> @n_mehlhorn 22

  23. STRUCTURAL @Directive({selector: '[ngIf]'}) export class SimpleNgIf<T> { DIRECTIVES: NGIF elseTemplate: TemplateRef context: NgIfContext<T> = {} <ng-template constructor(private view: ViewContainerRef, [ngIf]=”users$ | async as users” private template: TemplateRef<NgIfContext<T>>) {} [ngIfElse]=”loading”> @Input() <p>{{ users.length }} online</p> set ngIfElse(template: TemplateRef) { </ng-template> this.elseTemplate = template <ng-template #loading> } Loading… </ng-template> @Input() set ngIf(condition: T) { this.context.$implicit = this.context.ngIf = condition this.view.clear() interface NgIfContext<T> { if (condition) { $implicit: T this.view.createEmbeddedView(this.template, this.context) } else { ngIf: T this.view.createEmbeddedView(this.elseTemplate) } } } } @n_mehlhorn 23

  24. explicit binding to explicit binding to ngIf-property $implicit-property POP QUIZ: NGIF What’s the output? 1. hello, ngIf, undefined, hello <ng-template [ngIf]="'hello'" let-a="$implicit" let-b="ngIf" let-c> 2. undefined, undefined, hello, hello <p>{{ a }}</p> 3. hello, hello, hello, hello <p>{{ b }}</p> <p>{{ c }}</p> 4. hello, undefined, hello, hello </ng-template> <p *ngIf="'hello' as d">{{ d }}</p> interface NgIfContext<T> { $implicit: T ngIf: T } implicit binding to implicit binding to ngIf-property $implicit-property this.context.$implicit = this.context.ngIf = condition // 'hello' @n_mehlhorn 24

  25. NGIF & ASYNCPIPE no falsy values 👏 succinct 👎 no access to errors 👏 OnPush change 👎 detection same template for 👏 loading and error states fallback template 👎 (no access to values from 👏 other methods) @n_mehlhorn 25

  26. *observe A Structural Directive for Observables @n_mehlhorn 26

  27. <p *observe="users$ as users; before loadingTemplate; error errorTemplate"> {{ users.length }} users online </p> <ng-template #loadingTemplate> <p>Loading ...</p> </ng-template> <ng-template #errorTemplate let-error> <p>{{ error }}</p> </ng-template> DEMO AUTOMATIC SUBSCRIPTION MANAGEMENT WITH OBSERVE @n_mehlhorn 27

  28. <p *observe="users$ as users; @Directive({ before loadingTemplate; selector: "[observe]" error errorTemplate"> }) {{ users.length }} users online export class ObserveDirective<T> implements OnDestroy,OnInit { </p> <ng-template #loadingTemplate> constructor( <p>Loading ...</p> </ng-template> private view: ViewContainerRef, <ng-template #errorTemplate let-error> private nextRef: TemplateRef<ObserveContext<T>>, <p>{{ error }}</p> private changes: ChangeDetectorRef </ng-template> ) {} ... } @n_mehlhorn 28

Recommend


More recommend