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

Made with Slides.com