SlideShare a Scribd company logo
The Hidden Docs
In Angular
谢亚东(执衡)
Senior Front-end Engineer
Alibaba
谢亚东(执衡)
Senior Front-end Engineer
Alibaba
谢亚东(执衡)
Senior Front-end Engineer
Alibaba
谢亚东(执衡)
Senior Front-end Engineer
Alibaba
谢亚东(执衡)
Senior Front-end Engineer
Alibaba
Hidden Docs in Angular
Angular Evolution
2.0 4.0 5.0 6.0 7.0 8.0 9.0
2016 Sep 2019 Nov
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `<div><h2>Hello {{name}}</h2></div>`,
})
export class AppComponent {
name = 'Angular';
}
Code After Compiler
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `<div><h2>Hello {{name}}</h2></div>`,
})
export class AppComponent {
name = 'Angular';
}
2.0
Code After Compiler
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
Code After Compiler
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
Code After Compiler
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
4.0
1170 KB
Bundle Size After Compiler
4.0
1170 KB
Bundle Size After Compiler
5.0
152 KB
4.0
1170 KB
Bundle Size After Compiler
8.0
133 KB
Differential
Loading
5.0
152 KB
4.0
1170 KB
Bundle Size After Compiler
8.0
133 KB
Differential
Loading
9.0.0-rc.3
94.8 KB
Ivy
5.0
152 KB
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
Broccoli Webpack Bazel
2 3 4
Command-Line Interface
@angular/cli
+
Stable & Reliable
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
The Hidden Docs in Angular
The Hidden Docs in Angular
The Hidden Docs in
Change Detection
The Hidden Docs in
@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
DEMO
@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
Hidden Docs in Angular
Change Detection
Change Detection
React
Change to state cause change detection

setState or hooks
Change Detection
React
Change to state cause change detection

setState or hooks
Angular
?
When to Trigger Change Detection?
• Events: all browser events (click, mouseover, keyup, etc.)

• Timers: setTimeout() and setInterval()

• XHR: Ajax requests
Browser Async API
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
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
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
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
detach()
markForCheck()
detectChanges()
OnPush
ChangeDetectionRef
Explore the Source Code
ViewModel View
Detection Sequence
Why is it important?
DEMO
Error:
ExpressionChangedAfterItHasBeenCheckedError:
Expression has changed after it was checked.
DEMO
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
One Way Data Flow
Unidirectional Data Flow
VS
Summary

1. When

2. Source Code

3. Detection Sequence
Summary

1. When

2. Source Code

3. Detection Sequence
zone.js + tick
ApplicationRef + ChangeDetectionRef
ViewModel —> View & Unidirectional Data Flow
The Hidden Docs in
Component | Directive

1. Selector

2. @Input

3. Content Projection

4. Dynamic Component
The Hidden Docs in
Selector
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
Attribute Selector in
Component
Attribute Selector in
Component
VS
1. Supporting Original Attributes
<button ngx-button disabled>{{name}}</button>
<ngx-button ngxDisabled></ngx-button>
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>
Tag Selector in
Directive
Lighthouse Scoring
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';
}
@Input
typeof @Input data
<check [data]="true"></check>
<check bind-data="true"></check>
<check data="true"></check>
<check data="{{true}}"></check>
typeof @Input data
<check [data]="true"></check>
<check bind-data="true"></check>
<check data="true"></check>
<check data="{{true}}"></check>
typeof @Input data
<check [data]="true"></check>
<check bind-data="true"></check>
<check data="true"></check>
<check data="{{true}}"></check>
this.data === true
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
@Input as Attribute
<button disabled></button>
<button [disabled]="true"></button>
<button [disabled]="false"></button>
@Input as Attribute
<button disabled></button>
<button [disabled]="true"></button>
<button [disabled]="false"></button>
<ngx-attribute [ngxOpen]="true"></ngx-attribute>
<ngx-attribute [ngxOpen]="false"></ngx-attribute>
<ngx-attribute ngxOpen></ngx-attribute>
@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>
@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
@Input with *
@Input with *
<div *ngIf="heroes else loading">
  ...
</div>
<ng-template #loading>
  <div>Loading...</div>
</ng-template>
@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
Content Projection
Content Projection
<ng-content select="slot-name"></ng-content>
Multi-Slot
Content Projection
<ng-content select="slot-name"></ng-content>
Multi-Slot
1. Tag Selector

2. Attribute Selector

3. Not Selector

4. Default Selector

5. ngProjectAs
Content Projection
Component Interacting
ContentChildren
1. ChangeDetection Error

2. NgTemplateOutlet
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>
Content Projection
Content Empty Detection
Content Projection
Content Empty Detection
<nz-badge [nzCount]="25"></nz-badge>
Content Projection
Content Empty Detection
<nz-badge [nzCount]="25"></nz-badge>
<nz-badge [nzCount]="5">
  <a class="head-example"></a>
</nz-badge>
Content Projection
Content Empty Detection
<nz-badge [nzCount]="25"></nz-badge>
<nz-badge [nzCount]="5">
  <a class="head-example"></a>
</nz-badge>
Content Projection
Content Empty Detection
ContentObserver
Demo
MutationObserver
Content Projection
Copy Content
<copy [copy]="8">
  <button>copy me</button>
</copy>
Content Projection
Copy Content
<copy [copy]="8">
  <button>copy me</button>
</copy>
<button>copy me</button>
X 8
Content Projection
Copy Content
<copy [copy]="8">
  <button>copy me</button>
</copy>
<button>copy me</button>
X 8
Content Projection
Copy Content
<ng-template>
*
Demo
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
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
Content Projection
Move Content
<ng-template>
  <ng-content></ng-content>
</ng-template>
Eager Load
Lazy Load
VS
Dynamic Component
Dynamic Component
The missing compile
Template String
compile
Dynamic Component
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
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] });
    });
  }
}
Hidden Docs in Angular
Hidden Docs in Angular
Hidden Docs in Angular
Hidden Docs in Angular
yadong.xyd@alibaba-inc.com
NG-ZORRO
钉钉答疑
前端@杭州 简历发送

More Related Content

Hidden Docs in Angular