Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Hidden Docs in Angular

Yadong Xie
November 23, 2019

The Hidden Docs in Angular

The official Angular documentation gives an excellent hands-on tutorial, but there are still many advanced Angular practical skills that are not detailed taught on the official website. In this sharing, I will focus on various practical skills you may not know about in Angular.

Yadong Xie

November 23, 2019
Tweet

More Decks by Yadong Xie

Other Decks in Programming

Transcript

  1. import { Component } from '@angular/core'; @Component({ selector: 'app-root', template:

    `<div><h2>Hello {{name}}</h2></div>`, }) export class AppComponent { name = 'Angular'; } 2.0 4.0 6.0 defineComponent({ type: AppComponent, selectors: [ ["app-root"] ], decls: 3, vars: 1, template: function AppComponent_Template(rf, ctx) { if (rf & 1) { elementStart(0, "div"); elementStart(1, "h2"); text(2); elementEnd(); elementEnd(); } if (rf & 2) { advance(2); textInterpolate1("Hello ", ctx.name, ""); } }, encapsulation: 2 }); 9.0.0-rc.3 Code After Compiler
  2. 4.0 1170 KB 9.0.0-rc.3 gzip 30 KB Bundle Size After

    Compiler 8.0 133 KB Differential Loading 9.0.0-rc.3 94.8 KB Ivy 5.0 152 KB
  3. DATE STABLE RELEASE COMPATIBILITY October/November 2019 9.0.0 ^8.0.0 May 2020

    10.0.0 ^9.0.0 Release schedule & Semantic Versioning ng update & Update Guide ng update @angular/cli @angular/core
  4. @Component({ selector: "hello", template: ` <div>{{ count }}</div> <button (click)="add()">add</button>

    <button (click)="minus()">minus</button> ` }) export class HelloComponent { count = 0; add() { this.count += 1; } minus() { this.count -= 1; } } Counter Change Detection DEMO
  5. When to Trigger Change Detection? • Events: all browser events

    (click, mouseover, keyup, etc.) • Timers: setTimeout() and setInterval() • XHR: Ajax requests Browser Async API
  6. When to Trigger Change Detection? Monkey Patch zone.js • Events:

    all browser events (click, mouseover, keyup, etc.) • Timers: setTimeout() and setInterval() • XHR: Ajax requests Browser Async API DEMO
  7. this.applicationRef.tick() this._zone.onMicrotaskEmpty.subscribe( { next: () => { this._zone.run(() => {

    this.tick(); }); } }); _loadComponent(componentRef) { this.tick(); } Explore the Source Code ApplicationRef Demo with zone.js
  8. for (let view of this._views) { view.detectChanges(); } this.applicationRef.tick() ChangeDetectionRef

    CD CD CD CD CD CD CD CD CD CD CD this.cdr.detectChanges() Explore the Source Code
  9. for (let view of this._views) { view.detectChanges(); } this.applicationRef.tick() ChangeDetectionRef

    CD CD CD CD CD CD CD CD CD CD CD Demo with ChangeDetectionRef Explore the Source Code
  10. 1. update bound properties for all child components 2. call

    OnChanges, OnInit, DoCheck and AfterContentInit lifecycle hooks on all child components 3. update DOM for the current component 4. run change detection for a child component 5. call ngAfterViewInit lifecycle hook for all child components Detection Sequence Sequence is slightly different under Ivy engine
  11. Summary 1. When 2. Source Code 3. Detection Sequence zone.js

    + tick ApplicationRef + ChangeDetectionRef ViewModel —> View & Unidirectional Data Flow
  12. Selector const _SELECTOR_REGEXP = new RegExp( '(\\:not\\()|' + //":not(" '([-\\w]+)|'

    + // "tag" '(?:\\.([-\\w]+))|' + // ".class" // "-" should appear first in the regexp below as FF31 parses "[.-\w]" as a range '(?:\\[([-.\\w*]+)(?:=([\"\']?)([^\\]\"\']*)\\5)?\\])|' + // "[name]", "[name=value]", // "[name="value"]", // "[name='value']" '(\\))|' + // ")" '(\\s*,\\s*)', // "," 'g'); export interface Component extends Directive Explore the Source Code 1. :not() 2. Tag 3. Attribute Same Selector
  13. Attribute Selector in Component VS 1. Supporting Original Attributes <button

    ngx-button disabled>{{name}}</button> <ngx-button ngxDisabled></ngx-button> .parent > .child{ height: 200px; background: gray; } 2. Keep DOM structure <div class="parent"> <div class="child" ngx-attribute></div> </div>
  14. Tag Selector in Directive Lighthouse Scoring import { Directive, HostBinding

    } from '@angular/core'; @Directive({ selector: 'a[target="_blank"]:not([rel="noopener"])' }) export class SafeLinkDirective { @HostBinding('style.color') color = 'red'; @HostBinding('rel') rel = 'noopener'; }
  15. typeof @Input data Input Correction <check [data]="true"></check> <check bind-data="true"></check> <check

    data="true"></check> <check data="{{true}}"></check> this.data === true
  16. @Input as Attribute <button disabled></button> <button [disabled]="true"></button> <button [disabled]="false"></button> @Input()

    set ngxOpen(value) { this._open = this.coerceBooleanProperty(value); } get ngxOpen() { return this._open; } coerceBooleanProperty(value: any): boolean { return value != null && `${value}` !== "false"; } <ngx-attribute [ngxOpen]="true"></ngx-attribute> <ngx-attribute [ngxOpen]="false"></ngx-attribute> <ngx-attribute ngxOpen></ngx-attribute> @InputBoolean() ngxOpen = false; Decorators
  17. @Input with * <div *ngIf="heroes else loading"> ... </div> <ng-template

    #loading> <div>Loading...</div> </ng-template> @Input() set ngIfElse(templateRef: TemplateRef<NgIfContext>|null) { assertTemplate('ngIfElse', templateRef); this._elseTemplateRef = templateRef; this._elseViewRef = null; // clear previous view if any. this._updateView(); } DEMO
  18. Content Projection Component Interacting Dependency Injection 1. NgTemplateOutlet 2. Get

    Root Component <app-dropdown> <app-menu> <app-menu-item></app-menu-item> <app-menu-item></app-menu-item> <app-menu-item></app-menu-item> </app-menu> </app-dropdown> <app-menu> <app-menu-item></app-menu-item> <app-menu-item></app-menu-item> <app-menu-item></app-menu-item> </app-menu> <app-menu-item></app-menu-item> <app-menu-item></app-menu-item> <app-menu-item></app-menu-item>
  19. Content Projection Move Content <tabset> <tab title="title 1">content 1</app-tab> <tab

    title="title 2">content 2</app-tab> <tab title="title 3">content 3</app-tab> </tabset> content 3 content 2 content 1
  20. Content Projection Move Content <tabset> <tab title="title 1">content 1</app-tab> <tab

    title="title 2">content 2</app-tab> <tab title="title 3">content 3</app-tab> </tabset> title 1 title 2 title 3 content 1
  21. Dynamic Component The missing compile export function createCompiler(compilerFactory: CompilerFactory) {

    return compilerFactory.createCompiler([compilerOptions]); } providers: [ { provide: COMPILER_OPTIONS, useValue: compilerOptions, multi: true }, { provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] }, { provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory] } ], const dynamicComponent = Component({ template })( class { context = context; } ); const dynamicModule = NgModule({ declarations : [ dynamicComponent ], exports : [ dynamicComponent ], entryComponents: [ dynamicComponent ], imports : [ FormsModule ] })(class DynamicModule { }); this.compiledModule = await this.compiler.compileModuleAndAllComponentsAsync(dynamicModule); DEMO
  22. Dynamic Component Ivy Render import { Component, ɵrenderComponent as renderComponent,

    Injector, ɵLifecycleHooksFeature as LifecycleHooksFeature } from "@angular/core"; @Component({ selector : 'app-root', templateUrl: './app.component.html', styleUrls : ['./app.component.css'] }) export class AppComponent { constructor(private injector: Injector) { import('./dynamic/dynamic.component').then(({ DynamicComponent }) => { renderComponent(DynamicComponent, { injector, host: '#slot', hostFeatures: [LifecycleHooksFeature] }); }); } }