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 @n_mehlhorn 3
@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
WHY UNSUBSCRIBE? One-Off Observables e.g. HTTP request, timer cancellation ➝ “observable etiquette” ➝ @n_mehlhorn 5
CANCELLATION server q e r P T T H clicks app “submit” user @n_mehlhorn 6
CANCELLATION cancelled if not yet done server q e r P T T H navigates app away user @n_mehlhorn 7
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
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
Observable is just a function that takes an observer and returns a function Ben Lesh RxJS Lead @n_mehlhorn 10
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
@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
@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
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
@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
@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
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
@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
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
@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
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
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
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
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
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
*observe A Structural Directive for Observables @n_mehlhorn 26
<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
<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