Angular Tricks

Date: 2018-12-13

Install command-line interface

npm install -g @angular/cli

Generate component

ng generate component components/{component-name}
# generate more
ng generate --help

# serve local
ng serve

# run production build
ng build -c production

# run tests
ng test

# update angular packages
ng update
// update template values from async observable or promise  in 'realtime'
@Component({
  selector: 'async-observable-pipe',
  template: '<div>Time: {{ time | async }}</div>'
})
export class AsyncObservablePipeComponent {
  time = new Observable<string>((observer: Observer<string>) => {
    setInterval(() => observer.next(new Date().toString()), 1000);
  });
}
// Passive link (with preventDefault)
<a [routerLink]="" (click)="showArticles()">Articles</a>
// Style host element in component.less file
:host {
   display:block;
   background-color:red;
}
<!-- ng-container does not render a HTML Element -->
<ng-container *ngFor="let item of items; let i = index">
  <!-- ... -->
</ng-container>

*ngFor / ngClass

<div *ngFor="let season of seasons; let i = index" class="filter-item" [ngClass]="{'active': selectedValue == season.value}"></div>

Nested (inline) content

<!-- inside component -->
<div class="dialog-container">
    <div class="dialog">
        <div class="dialog-header">
            <ng-content select="header"></ng-content>
        </div>
        <div class="dialog-content">
            <ng-content select="content"></ng-content>
        </div>
    </div>
</div>

<!-- outside component -->
<app-inline-dialog>
    <header>
<!-- header content -->
    </header>
    <content>
<!-- content -->
    </content>
</app-inline-dialog>
//Reference child component as instance:
@ViewChild(SignatureComponent) signaturePad: SignatureComponent;
// NgFor even / odd
<tr *ngFor="let hero of heroes; let even = even; let odd = odd" 
    [ngClass]="{ odd: odd, even: even }">
    <td>{{hero.name}}</td>
</tr>
// NgFor first / last
<tr *ngFor="let hero of heroes; let first = first; let last = last" 
    [ngClass]="{ first: first, last: last }">
    <td>{{hero.name}}</td>
</tr>
// Getter + setter: template code
<app-expand-button [(expanded)]="expanded"></app-expand-button>
// Getter + setter: controller code
import { Component, OnInit, EventEmitter, Output, Input } from "@angular/core";
@Component({
  selector: "app-expand-button",
  templateUrl: "./expand-button.component.html",
  styleUrls: ["./expand-button.component.less"]
})
export class ExpandButtonComponent implements OnInit {
  public _expanded: boolean;
  @Output()
  expandedChange: EventEmitter<boolean> = new EventEmitter<boolean>();
  @Input()
  set expanded(value: boolean) {
    if (this._expanded === value)
      return;
    this._expanded = value;
    this.expandedChange.emit(value);
  }
  constructor() { }
  async ngOnInit() { }
}
// Reference to Html element: template
<div #myElementId></div>
// Reference to Html element: controller
@ViewChild("myElementId") myElement: ElementRef;
// Prevent router outlet from flickering
<div class="router-container" [hidden]="!o.isActivated">
    <router-outlet #o="outlet"></router-outlet>
</div>
//Inject html element in constructor
constructor(private _element: ElementRef) {
    console.log(this._element);
}
// Subscribe to router events
    constructor(_router: Router) {
        let prevUrl = '';
        _router.events.subscribe(event => {
            if (event instanceof ActivationEnd) {
                const newUrl = _router.url;
                const urlEquals = prevUrl === newUrl;
                const fromChildUrl = prevUrl.indexOf(newUrl) >= 0;
                //console.log('navigate component', prevUrl, '=>', newUrl);
                const hasChildren = event.snapshot.children.length > 0;
                const componentName =  (event.snapshot.component as any).name;
                if (!urlEquals && !fromChildUrl && !hasChildren) {
                    //console.log('activated:', componentName);
                    this.emitter.emit(Events.componentActivated, componentName)
                }
                prevUrl = newUrl;
            }
        });
    }
// Subscribe to window events
    @HostListener("document:visibilitychange")
    onPageVisibilityChanged() {
        this.emitter.emit(Events.pageVisibilityChanged, { visible: document.visibilityState === 'visible' });
    }

    @HostListener("window:online")
    @HostListener("window:offline")
    onOnlineStatusChanged() {
        this.emitter.emit(Events.onlineStatusChanged, { online: window.navigator.onLine });
    }
// OnInit + OnDestroy
export class MyComponent extends BaseComponent implements OnInit, OnDestroy {
    ngOnInit() {

    }
    ngOnDestroy() {

    }
}
// Null check in template
<div>{{customer?.name}} <span>#{{customer?.customerNumber}}</span></div>
// Relative navigation
this._router.navigate("..", { relativeTo: this._route });
// Use hash for routing + force refresh same route
RouterModule.forRoot(routes, { useHash: true, onSameUrlNavigation: "reload" })

// otherwise route redirect to home
 { path: "**", redirectTo: "/login" }


// Child components: route config
// Use child component with nested <router-outlet> in parent component
{
        path: "article-supply",
        component: ArticleSupplyComponent,
        runGuardsAndResolvers: "always",
        canActivate: [AuthGuard],
        children: [
            {
                path: "salesdisplay",
                component: SelectSalesDisplayComponent
            },
            {
                path: "new-salesdisplay",
                component: SelectSalesDisplayComponent
            }
        ]
    }

// track by function example: https://netbasal.com/angular-2-improve-performance-with-trackby-cc147b5104e5

@Component({
  selector: 'my-app',
  template: `
    <ul>
      <li *ngFor="let item of collection;trackBy: trackByFn">{{item.id}}</li>
    </ul>
    <button (click)="getItems()">Refresh items</button>
  `,
})
export class App {
  constructor() {
    this.collection = [{id: 1}, {id: 2}, {id: 3}];
  }  
  getItems() {
    this.collection = this.getItemsFromServer();
  }  
  getItemsFromServer() {
    return [{id: 1}, {id: 2}, {id: 3}, {id: 4}];
  } 
  trackByFn(index, item) {
    return index; // or item.id
  }
}
// DeactivateGuard for component deactivation delay for e.g. animations
// 1. In app.module.ts add: providers: [DeactivateGuard]
// 2. In app-routing.module.ts add: canDeactivate: [DeactivateGuard] to each route
// 3. Implement OnDeactivate in each component

import { Injectable, Component } from "@angular/core";
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from "@angular/router";
import { Observable } from "rxjs";

// tslint:disable-next-line: naming-convention
export interface OnDeactivate {
    onDeactivate(): Promise<boolean>;
}

@Injectable()
    export class DeactivateGuard implements CanDeactivate<Component> {
    canDeactivate(component: Component, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot)
        : Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
        if (component && (component as any).onDeactivate) {
            const c = component as OnDeactivate;
            return c.onDeactivate();
        }
        return true;
    }
}

Create a component dynamically

https://netbasal.com/dynamically-creating-components-with-angular-a7346f4a982d

constructor(private resolver: ComponentFactoryResolver) {}
  createComponent(type) {
    this.container.clear(); 
    const factory: ComponentFactory = this.resolver.resolveComponentFactory(AlertComponent);
    this.componentRef: ComponentRef = this.container.createComponent(factory);
  }

https://angular.io/guide/dynamic-component-loader

export class AdBannerComponent implements OnInit, OnDestroy {
  @Input() ads: AdItem[] = [];

  currentAdIndex = -1;

  @ViewChild(AdDirective, {static: true}) adHost!: AdDirective;
  interval: number|undefined;

  ngOnInit(): void {
    this.loadComponent();
    this.getAds();
  }

  ngOnDestroy() {
    clearInterval(this.interval);
  }

  loadComponent() {
    this.currentAdIndex = (this.currentAdIndex + 1) % this.ads.length;
    const adItem = this.ads[this.currentAdIndex];

    const viewContainerRef = this.adHost.viewContainerRef;
    viewContainerRef.clear();

    const componentRef = viewContainerRef.createComponent<AdComponent>(adItem.component);
    componentRef.instance.data = adItem.data;
  }

  getAds() {
    this.interval = setInterval(() => {
      this.loadComponent();
    }, 3000);
  }
}
16900cookie-checkAngular Tricks