Coding with Angular

Tips and tricks

Reactive Forms

Control Validation

export class AppComponent  {
  control = new FormControl(null, dummyValidator);
}

function dummyValidator(control: FormControl) {
  console.log('checking...')
  return null;
}

Control Validation with updateOn

export class AppComponent {
  control = new FormControl(null, {
    validators: Validators.minLength(4),
    updateOn: 'blur'
  });
}

Control Validation with valueChanges

ngOnInit() {
  const name = this.group.get('name');

  name.valueChanges.pipe(
    debounceTime(300),
    untilDestroyed(this)
  ).subscribe(
  	() => name.setErrors(
    	Validators.minLength(2)(name)
    )
  );
}

Control Validation with onlySelf

this.group.get('name').patchValue('value', { onlySelf: true })

Infinite Loops

ngOnInit() {
  this.store.subscribe(
  	value => this.form.patchValue(value)
  );

  this.form.valueChanges.subscribe(
  	value => this.updateStore(value)
  );
}
this.form.patchValue(value, { emitEvent: false })

Solve it with emitEvent

Control Disabling

new FormGroup({
  name: new FormControl({ 
	disabled: true, 
    
	// !!!
	value: null 
  }),
  email: new FormControl()
})
name.valueChanges.subscribe(() => {
  console.log('changed!!');
});

setTimeout(() => {
  name.enable();
}, 1000);

on the initialization

but it triggers valueChanges everytime...

Better Control Disabling

name.enable({ emitEvent: false });
name.disable({ emitEvent: false });
FormGroup.value

// to

FormGroup.getRawValue();

with emitEvent

and get controls that are disabled

Clean code

Variable and function names

function div(x, y)) {
 const val = x / y;
 return val;
}
function divide(divident, divisor) {
  const quotient = divident / divisor;
  return quotient;
}

Code comments

// check if meal is healthy or not
if (meal.calories < 1000 &&
    meal.hasVegetables) {
  ...
}
if (meal.isHealthy()) {
 ...
}

Subscribe in templates

@Component({
  ...
  template: `<FA [it]="item">`
})
class AppComponent {
  items: Item[];
  
  constructor(
  	private itemService: ItemService
  ) {}
  
  ngOnInit() {
    this.loadItems()
      .pipe(
        map(items => this.items = items;
      ).subscribe();
  }
  
  loadItems(): Observable<Item[]> {
    return this.itemService.findItems();
  }
}
@Component({
    ...
    template: `<FA [it]="items$ | async"></FA>`
})
class AppComponent {
  items$: Observable<Item[]>;

  constructor(
  	private itemService: ItemService
  ) {}
  
  ngOnInit() {
    this.items = this.loadItems();
  }
  
  loadItems(): Observable<Item[]> {
    return this.itemService.findItems();
  }
}

Memory leaks

this.itemService.findItems()
  .pipe(
    map((items: Item[]) => items),
  )
  .subscribe()
private unsubscribe$: Subject<void> = new Subject<void>();

...

this.itemService.findItems()
.pipe(
  map(value => value.item)
  takeUntil(this._destroyed$)
)
.subscribe();

...

public ngOnDestroy(): void {
  this.unsubscribe$.next();
  this.unsubscribe$.complete();
  this.unsubscribe$.unsubscribe();
}

Imports with path aliases

// tsconfig.json

...

"paths": {
  "@app/*": ["app/*"],
  "@data/*": ["app/data/*"],
  "@lib/*": ["app/lib/*"],
  "@module/*": ["app/module/*"],
  "@shared/*": ["app/module/app-shared/*"],
  "@page/*": ["app/page/*"],
  "@service/*": ["app/service/*"],
  "@pipe/*": ["app/pipe/*"],
  "@assets/*": ["assets/*"],
  "@environments/*": ["environments/*"]
},

...
import { BillboardService } from '@module/billboard/service/billboard.service';

// instead of

import { BillboardService } from '../../../service/billboard.service';

Optimization

Lazy Loading for main bundle

import { NgModule } from '@angular/core';
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';


const routes: Routes = [];

@NgModule({
  imports: [RouterModule.forRoot(routes, {
  	preloadingStrategy: PreloadAllModules
  })],
  exports: [RouterModule]
})
export class AppRoutingModule { }
polyfills.js
scripts.js
runtime.js
styles.css
main.js

Bundle Analyzer

"scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    
    // Add
    "bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer stats.json"
},

Lazy Loading for images

<html>
<title>Lazy Load Images</title>
<body>
    <div>
        <div style="">
            <img class="lzy_img" src="lazy_img.jpg" data-src="img_1.jpg" />
        </div>
    </div>
    <script>
        document.addEventListener("DOMContentLoaded", function() {
            const imageObserver = new IntersectionObserver(
              (entries, imgObserver) => {
                  entries.forEach((entry) => {
                      if (entry.isIntersecting) {
                          const lazyImage = entry.target
                          console.log("lazy loading ", lazyImage)
                          lazyImage.src = lazyImage.dataset.src
                      }
                  })
              }
            );
            
            const arr = document.querySelectorAll('img.lzy_img')
            arr.forEach((v) => {
                imageObserver.observe(v);
            })
        })
    </script>
</body>
</html>

Virtual Scrolling

Fonts

etc.

Thanks