Bad Practices, what NOT to do in Angular

@ValentinKononov

https://slides.com/valentinkononov/angular-bad-practices-gorki

 
 

@ValentinKononov

https://github.com/valentinkononov/

angular-bad-practices

 

QUESTIONS

 
 

@ValentinKononov

sli.do

 

#devfest_volga

 

How do we study new technology?

Book

Articles

Watch YouTube video

Download boilerplate

Start coding

Stackoverflow

ANYthing

usage of Any in typescript

@ValentinKononov

ANYthing

public ngOnInit(): void {
   const movie = {
       name: 'Game of Thrones',
   };
   this.showToast(movie);
}

private showToast(item: any): void {
   this.toaster.show(`Today's movie is: ${item.name}`);
}
public ngOnInit(): void {
   const movie = {
       title: 'Game of Thrones',
   };
   this.showToast(movie);
}

private showToast(item: any): void {
   this.toaster.show(`Today's movie is: ${item.name}`);
}

'Undefined' in message

ANYthing

use interfaces and classes

interface Movie {
   name: string;
}

public ngOnInit(): void {
   const movie: Movie = {
       name: 'Game of Thrones',
   };
   this.showToast(movie);
}

private showToast(item: Movie): void {
   this.toaster.show(`Today's movie is: ${item.name}`);
}

TS2339: Property 'name' does not exist on type 'Movie'.

interface Movie {
   title: string;
}

public ngOnInit(): void {
   const movie: Movie = {
       name: 'Game of Thrones',
   };
   this.showToast(movie);
}

private showToast(item: Movie): void {
   this.toaster.show(`Today's movie is: ${item.name}`);
}

@ValentinKononov

SUBSCRIBE me

SUBSCRIBE me

ngOnInit() {
    console.log('Initialized');
    this.timerService.initTimer()
      .subscribe(x => {
        this.result = x;
        console.log(x);
      });
}

ngOnDestroy() {
    console.log('Destroyed');
}

ISSUE: Memory Leak

Issues

@ValentinKononov

  • memory leaks
  • doesn't work with OnPush

SUBSCRIBE me

Manual Unsubscribe

subscription: Subscription;

ngOnInit() {
    console.log('Initialized');
    this.subscription = this.timerService.initTimer()
      .subscribe(x => {
        this.result = x;
        console.log(x);
      });
}

ngOnDestroy() {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
    console.log('Destroyed');
}

Issues

@ValentinKononov

  • no leaks
  • a lot of manual work -> human mistakes
  • doesn't work with OnPush

SUBSCRIBE me

takeWhile operator

alive = true;

ngOnInit() {
    console.log('Initialized');
    this.timerService.initTimer()
      .pipe(takeWhile(() => {
        console.log('check'); return this.alive; }))
      .subscribe(x => {
        this.result = x;
        console.log(x);
      });
}

ngOnDestroy() {
    this.alive = false;
    console.log('Destroyed');
}

Issues

@ValentinKononov

  • no leaks
  • BUT still, have 1 extra subscription tick
  • easy to use, less code
  • doesn't work with OnPush

SUBSCRIBE me

takeUntil operator

unsubscribe: Subject<void> = new Subject();

ngOnInit() {
    console.log('Initialized');
    this.timerService.initTimer()
      .pipe(takeUntil(this.unsubscribe))
      .subscribe(x => {
        this.result = x;
        console.log(x);
      });
}

ngOnDestroy() {
    this.unsubscribe.next();
    this.unsubscribe.complete();
    console.log('Destroyed');
}

takeUntil should be the LAST in sequence

Issues

@ValentinKononov

  • no leaks
  • a lot of boilerplate code -> human mistakes
  • doesn't work with OnPush

SUBSCRIBE me

untilDestroyed operator

import {untilDestroyed} from "ngx-take-until-destroy";

ngOnInit() {
    console.log('Initialized');
    this.timerService.initTimer()
      .pipe(untilDestroyed(this))
      .subscribe(x => {
        this.result = x;
        console.log(x);
      });
}

ngOnDestroy() {
    console.log('Destroyed');
}

Issues

@ValentinKononov

  • no leaks
  • less code
  • doesn't work with OnPush
  • thrid party dependency

SUBSCRIBE me

Async Pipe

@Component({ 
 template: `<span>{{ this.result$ | async }}</span>`,
 changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SubscriptionComponent 
    implements OnInit, OnDestroy {
  
    result$: Observable<number>;

    ngOnInit() {
        console.log('Initialized');
        this.result$ = this.timerService.initTimer()
          .pipe(tap(x => { console.log(x); }));
    }

    ngOnDestroy() {
        console.log('Destroyed');
    }
} 

NOTE: change detection will trigger each time when Observable emits value

Issues

@ValentinKononov

  • no leaks
  • WORK with OnPush
  • a minimal amount of code
  • cannot use in TS code as is
  • cannot use in several places in template

SUBSCRIBE me

Async Pipe - Template Improvement

<ng-container *ngIf="{
	id: id$ | async,
	count: count$ | async
} as data">
	Subscription result: 
	{{ data.id }} | {{ data.count }}
</ng-container>

id$: Observable<number>;
count$: Observable<number>;

SUBSCRIBE me

Async Pipe - Template Improvement

<div>
	Subscription result: 
	{{ id$ | async }} | {{ count$ | async }}
</div>

id$: Observable<number>;
count$: Observable<number>;

ngOnInit() {
	console.log('Initialized');
	this.result$ = this.timerService
      	.initTimerForObject()
      	.pipe(tap(x => {
        	console.log(`Timer: ${x}`);
      	}));
    this.id$ = this.result$.pipe(map(x => x.id));
    this.count$ = this.result$.pipe(map(x => x.count));
}

Issues

@ValentinKononov

  • no leaks
  • WORKs with OnPush
  • a minimal amount of code
  • CAN use in TS code as is
  • CAN use in several places in the template
  • Amount if requests INCREASED

SUBSCRIBE me

Async Pipe - Share Pipe

<div>
	Subscription result: 
	{{ id$ | async }} | {{ count$ | async }}
</div>

id$: Observable<number>;
count$: Observable<number>;

ngOnInit() {
	this.result$ = this.timerService
      	.initTimerForObject()
      	.pipe(share());
  
    this.id$ = this.result$.pipe(map(x => x.id));
    this.count$ = this.result$.pipe(map(x => x.count));
}

Issues

@ValentinKononov

  • no leaks
  • WORKs with OnPush
  • a minimal amount of code
  • CAN use in TS code as is
  • CAN use in several places in the template
  • Observable is reused

Multicast Pipes

@ValentinKononov

share()

publish(), refCount()

observer1
observer1
Subject
Source

Multicast Pipes

@ValentinKononov

publishReplay(), refCount()

shareReplay({refCount: true})

All new Subscribers will get N Buffered values

SUBSCRIBE me

Async Pipe - share

<div>{{ id$ | async }} | {{ count$ | async }}</div>

id$: Observable<number>;
count$: Observable<number>;

ngOnInit() {
	this.result$ = this.timerService
      	.initTimerForObject()
      	.pipe(
      		share(),
		);
  
    this.id$ = this.result$.pipe(map(x => x.id));
    this.count$ = this.result$.pipe(map(x => x.count));
}

SUBSCRIBE me

Async Pipe - share - catchError

<div>{{ id$ | async }} | {{ count$ | async }}</div>

id$: Observable<number>;
count$: Observable<number>;

ngOnInit() {
	this.result$ = this.timerService
      	.initTimerForObject()
      	.pipe(
      		share(),
      		catchError((error: any) => {
			console.log(error);
			return of({ id: 0, count: 0 });
        }),
    );
  
    this.id$ = this.result$.pipe(map(x => x.id));
    this.count$ = this.result$.pipe(map(x => x.count));
}

TS LINT

rxjs-no-ignored-subscription

rxjs-no-ignored-subscribe

rxjs-no-ignored-observable

rxjs-no-ignored-error

rxjs-no-nested-subscribe

rxjs-no-unsafe-takeuntil

rxjs-prefer-async-pipe

 

 

https://www.nmpjs.com/package/rxjs-tslint-rules

PIPE it

@ValentinKononov

Pipe it

function in a template

<button type="button" (click)="sortCollection()">
    Sort Collection</button>
<div *ngFor="let item of items; trackBy: trackByFn">
	{{ getLabel(item) }}
</div>

getLabel(item:  ItemData): string {
    console.log(`Item ${item.id} label call`);
    return `Item ${item.id}`;
}

PIPE it

create a pipe for that purpose

@Pipe({ name: 'label'})
export class LabelPipe implements PipeTransform {
  transform(value: Item, args?: any): string {
    console.log(`Item ${value} label call`);
    return `Item ${value.id}`;
  }
}

<button type="button" (click)="sortCollection()">
    Sort Collection</button>
<div *ngFor="let item of items; trackBy: trackByFn">
	{{ item | label }}
</div>

PIPE it

change detection

items: ItemData[] = [{
      parentId: 1,
      name: 'Game Of Thrones',
    },
    {
      parentId: 2,
      name: 'The Lord of the Rings',
    },
    {
      parentId: 3,
      name: 'Big Bang Theory - Season 1',
    },
    {
      parentId: 3,
      name: 'Big Bang Theory - Season 2',
    }];

CONSLUSION

  • Use Async Pipes for subscription
  • Use share() / publish() + refCount() for multiple usages
  • Use Pipes instead functions in templates
  • Avoid ANY
  • Enjoy coding!

@ValentinKononov

Thanks!

@ValentinKononov

Bad Practices, what NOT to do in Angular

Angular Bad Practices Gorky

By Valentin Kononov

Angular Bad Practices Gorky

Update bad practices version for Gorky Dev Fest 2019, November 16

  • 294