Bad Practices, what NOT to do in Angular

@ValentinKononov

Full Stack Developer

Articles

Trainings

AKVEO.COM

https://slides.com/valentinkononov/

angular-bad-practices

 

https://github.com/valentinkononov/

angular-bad-practices

 

@ValentinKononov

How do we study new technology?

Book

Articles

Watch YouTube video

Download boilerplate

Start coding

Stackoverflow

SHAKE it

RxJs Pipable Operators

@ValentinKononov

SHAKE it

import { map, take } from 'rxjs/operators';

...

this.service.getData()
    .pipe(
        map(value => value.item)
        take(1)
    );
...
// to have build optimization
npm install @angular-devkit/build-optimizer

// to analyze build statistics
npm install webpack-bundle-analyzer --save-dev

BEFORE

AFTER

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}`);
}

INPUT that crap

call child component function

@ValentinKononov

INPUT that crap

@Component({
 template: `<ngx-child #child />
  <button (click)="child.expand()">Expand</button>
`})
export class ParentComponent {
   @ViewChild('child') child: ChildComponent;
}

@Component({ template: `
    <span *ngIf="expanded">I am expanded</span>
`})
 export class ChildComponent {
   @Input() expanded: boolean = false;

   public expand(): void {
       this.expanded = true;
   }
 }

INPUT that crap

communication via Inputs only

@Component({
 template: `<ngx-child [expanded]="childExpanded"/>
  <button (click)="expand()">Expand</button>
`})
export class ParentComponent {
   childExpanded: boolean = false;
   public expand(): void { this.childExpanded = true;}
}

@Component({ template: `
    <span *ngIf="expanded">I am expanded</span>`,
    changeDetection: ChangeDetectionStrategy.OnPush,
})
 export class ChildComponent {
   @Input() expanded: boolean = false;
 }

SUBSCRIBE me

@ValentinKononov

SUBSCRIBE me

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

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

ISSUE: Memory Leak

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');
}

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');
}

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

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

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

ITERATE me

@ValentinKononov

ITERATE me

<button type="button" (click)="sortCollection()">
    Sort Collection</button>

<div *ngFor="let item of items">
    <app-iterate-item [data]="item">
    </app-iterate-item>
</div>

ITERATE me, trackably

*ngFor with trackBy

<button type="button" (click)="sortCollection()">
    Sort Collection</button>

<div *ngFor="let item of items; trackBy: trackByFn">
    <app-iterate-item [data]="item">
    </app-iterate-item>
</div>

trackByFn(index: number, item: ItemData) {
    return item.id;
}

FAST and FURIOUS

expandable wrapper

<button type="button" (click)="sortCollection()">
    Sort Collection</button>
<div *ngFor="let item of items; trackBy: trackByFn">
    <app-expandable 
        [label]="getLabel(item)" [expanded]="false">
          <app-iterate-item [data]="item"></app-iterate-item>
    </app-expandable>
</div>

trackByFn(index: number, item: ItemData) {
    return item.id;
}

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

FAST and FURIOUS

expandable wrapper

<button type="button" (click)="sortCollection()">
    Sort Collection</button>
<div *ngFor="let item of items; trackBy: trackByFn">
    <app-expandable 
        [label]="getLabel(item)" [expanded]="false">
          <app-iterate-item [data]="item"></app-iterate-item>
    </app-expandable>
</div>

trackByFn(index: number, item: ItemData) {
    return item.id;
}

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: number, args?: any): any {
    console.log(`Item ${value} label call`);
    return `Item ${value}`;
  }
}

<div>
  <button type="button" (click)="sortCollection()">
    Sort Collection</button>
  <div *ngFor="let item of items; trackBy: trackByFn">
    <app-expandable [label]="item.id | label" 
        [expanded]="false">
      <app-iterate-item [data]="item"></app-iterate-item>
    </app-expandable>
  </div>
</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 Pipes instead functions in templates
  • Use trackBy when possible
  • Make inputs dependent components
  • Avoid ANY
  • Enjoy coding!

@ValentinKononov

Thanks!

@ValentinKononov

Bad Practices, what NOT to do in Angular

Angular Bad Practices

By Valentin Kononov

Angular Bad Practices

  • 1,688