剖析 Angular 2
By
汤桂川 @广发证券
About me
汤桂川
- Github : @lightningtgc
- Weibo : @汤桂川_gc
- Zhihu : @汤桂川
- Email : tangguichuan@gmail.com
爱Coding,也爱Traveling
@ 腾讯 - AlloyTeam 前端工程师
- WebQQ and Smart QQ
- PC QQ and QQ群
- 手机QQ:吃喝玩乐,兴趣部落
@ 广发证券 - 前端技术专家
- 广发微店 - 易淘金
- 广发大数据日志平台
- 金钥匙 - 金融界的嘀嘀打车
Agenda - 大纲
① 概述
- Angular2 前世今生
- ES6
- TypeScript
② 核心部分
- Component
- DI
- Template
- Tools
- ngUpgrade
③ 延伸
-
Change Detection - Zone.js
- Reactive Programming - Rx.js
- Web worker
- server-side Render
Part 1 - 概述
👐
- Angular2 前世今生
- ES6
- TypeScript
Angular 2 前世今生
- 2009年第一版 0.X.X
- 2012.06 1.0.0
- 2016.02 1.5.0 (component)
- angularjs.org vs angular.io
- 2014年宣布开发2.0
- 2015.02 Alpha.1 - 55
- 2015.12 Beta.0
- 2016.03.19 Beta.11 & Build
-
SPEED(change detection,启动时间)
-
CROSS PLATFORM(worker,server,ionic)
-
Mobile (Android 4.1, iOS 7.1,IE9+)
-
Flexible (TypeScript/ES6, ES5,dart)
New Feature:
将ng2的Hello World例子的负载减少到 <10KB min+compressed
Angular 1 版本:
<p>Today is {{todayDate | date}}</p>
Angular Dart 版本:
<p>Today is {{todayDate | date}}</p>
Angular 2 版本:
<p>Today is {{todayDate | date}}</p>
语法 - (eg: 按一定格式输出今天的日期)
import { bootstrap } from 'angular2/platform/browser';
import { Component, Output, AfterViewInit } from 'angular2/core';
import { LoggerService } from './logger.service';
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router';
@Component({
selector: 'my-app',
directives: [ROUTER_DIRECTIVES, HomeComponent],
providers: [LoggerService, ROUTER_PROVIDERS],
template: `
<a [routerLink]="['Home']">Home</a>
<router-outlet></router-outlet>
<!--<form (submit)="addName()" *ngIf="canAddName">
<input [(ngModel)]="newName" [style.color] = "isSpecial ? 'red' : 'green'">
<img [src]="addImageUrl" >
</form>
<p *ngFor="#name of personNames">{{name}}</p> -->
`,
styles: ['p{padding: 8px;}']
})
@RouteConfig([{ path: '/home', name: 'Home', component: HomeComponent }])
export class AppComponent implements AfterViewInit{
private _logger:LoggerService;
static addImageUrl = '';
@Output('everyFiveSeconds') five5Secs = new EventEmitter();
constructor(logger:LoggerService){
this._logger = logger;
setInterval(() => this.five5Secs.emit("event"), 5000);
}
ngAfterViewInit() {
this._logger.log(`After View Init.`);
}
}
bootstrap(AppComponent).catch(err => console.error(err));
import { Directive, ElementRef, Renderer } from 'angular2/core';
@Directive({
selector: '[myHighlight]'
})
export class HighlightDirective {
constructor(el: ElementRef, renderer: Renderer) {
// renderer.setElementStyle(el, 'backgroundColor', 'yellow');
// renderer接口更改了
renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'yellow');
}
}
SO 暂不关注太多细节。。。
ECMAScript 6
- 2015. 06. 17
ECMAScript 6 在浏览器的实现情况
Modules (1/4)
// 模块导入
import { lightRed } from './colors';
import { Component } from 'angular2/core';
import * as UserModule from './user.module';
let userCtrl = UserModule.ctrl;
// 模块导出
export { userCtrl };
export class App{
constructor() {}
};
export default lightRed;
- ng2 is modular
- vs CommonJS
- 循环依赖?
- Native, HTTP 2
- System.js
var webpackOptions = {
resolve: {
root: [
path.resolve(__dirname, "../src/app"),//根路径
path.resolve(__dirname, "../../public-modules")
],
extensions: ['', '.js'],
alias: {
functions: "utils/functions.js" // 配置需要的文件别名
}
}
};
//import {resolveInject} from '../../../../public-modules/utils/functions';
import {resolveInject} from 'functions';
Classes (2/4)
class Animal {
constructor(name) {
this.name = name;
}
say(msg){
alert(`${this.name} says ${msg}`);
}
static staticMethod() {
return '不能被实例继承,子类可继承父类';
}
}
class Panda extends Animal {
say(msg = 'hi') {
super(msg); // 调用父类say方法
}
}
let panda = new Panda('Tuan');
panda.say(); // 'Tuan says hi'
panda.say('Ooooh'); // 'Tuan says Ooooh'
- constructor
- this
- default
- extends
- super
- 静态方法
IN TypeScript
Template String (3/4)
Notice?
// 多行字符串,引号
var multiStr = `In 'JavaScript' this is
not legal.`;
// 字符串插值
var name = "ITA1024", time = "today";
console.log(`Hello ${name},
how are you ${time}?`);
More detail in:
Arrows (4/4)
const name = 'object name';
let obj = {
name,
init() {
// Lexical this
$.on('click', () => this.name);
}
};
// 以上代码等同于 ES5
var name = 'object name';
var obj = {
name: name,
init: function init() {
var _this = this;
$.on('click', function () {
return _this.name;
});
}
};
- C# 3.0 lambda
- Lexical this
- 单行隐性return value
- No arguments object
ES6Features
查看详情: ES6features(中文版)
- map + set
- weakmap + weakset
- enhanced object literals - 增强对象字面量
- proxies - 代理
- symbols - 第七种数据类型
- binary and octal literals
- unicode - 字符串扩展
- reflect api - 反射api
- tail calls - 尾调用
TypeScript
Type + Java Script
interface ClockInterface {
currentTime: Date;
color?: string;
}
class Clock implements ClockInterface {
private name: string;
currentTime: Date;
static origin = {x: 0, y: 0};
constructor(h: number, m: number) {}
}
Decorator - 修饰器
@SomeDecorator
class Test {}
// 等同于
class Test {}
Test = SomeDecorator(Test) || Test;
- compile,no runtime
- class , class method
- vs Annotation
- @ AtScript ?
function someDecorator(target) {
// 对目标函数进行处理
target.isTestable = true;
}
@Directive
@Animation
@Inject
@InjectLazy
@Optional
@Host
@Parent
@Pipe
@Property
@Event
@RouteConfig
@HostBinding
@HostEvent
@ContentChildren
@ViewChild
@ViewChildren
@Input
@Output
@Attribute
@CanActivate
学习资源:
Part 2 - 核心部分
- Component
- DI
- Template
- Tools
- ngUpgrade
component 组件
- Web Component
- 组件化开发
- Tree - 分治
- 相互独立
HTML
CSS
JS
import { Component } from 'angular2/core';
import {AppHeadComponent} from './app-head.component';
import { LogService } from './log.service';
@Component({
selector: 'my-app',
providers: [ LogService ],
directives: [ AppHeadComponent ],
pipes: [],
styles: [`
.main-body { width: 60px; }
`],
template: `
<my-app-head [title]="titleStr"></my-app-head>
<div class="main-body">
Test..
</div>
`
});
export class MyAppComponent {
titleStr = 'My App';
constructor(private _log: LogService){ }
}
- @Component
- metaData
- 多组件依赖
组件树工作机制
[property]="expression"
@Input()
(event)="update()"
@Output()
根组件
子组件
子组件
孙组件
<cmp [property]="someExp" (someEvent)="update()"></cmp>
class Cmp {
@Input(name) property: any;
@Output() someEvent: EventEmitter;
//...
}
组件的生命周期
Hooks:
- OnInit
- OnDestroy
- DoCheck
- OnChanges
- AfterViewInit
- AfterViewChecked
- AfterContentInit
- AfterContentChecked
- 调用方法:ng+.. (eg: ngOnInit)
- ngOnChanges 搭配SimpleChange拿前后值
- DoCheck 自定义变化检测,不与changes同时存在
import {Component, OnInit} from 'angular2/core';
@Component({
selector: 'my-test'
})
export class MyTest implements OnInit{
ngOnInit() {
console.log(`onInit`);
}
}
import { Component } from 'angular2/core';
import {AppHeadComponent} from './app-head.component';
import { LogService } from './log.service';
@Component({
selector: 'my-app',
providers: [ LogService ],
directives: [ AppHeadComponent ],
pipes: [],
styles: [`
.main-body { width: 60px; }
`],
template: `
<my-app-head [title]="titleStr"></my-app-head>
<div class="main-body">
Test..
</div>
`
});
export class MyAppComponent {
titleStr = 'My App';
constructor(private _log: LogService){ }
}
Dependency Injection 依赖注入
interfaces, no implement
组件
服务接口
服务实现
实现
提供给
依
赖
于
// log.service.ts
import {Injectable} from 'angular2/core';
@Injectable()
export class LogService{
log(msg:string){
console.log(msg)
}
}
Services
-
服务
// 使用服务
import {Component} from 'angular2/core';
import {LogService} from './log.service';
@Component({
selector: 'my-app',
providers: [LogService]
})
export class AppComponent {
constructor(private _log: LogService){ }
doLog(msg) {
this._log.log(msg);
}
}
全局注入(不推荐)
bootstrap(AppComponent, [LogService]);
可能(可选)注入
import {Optional} from 'angular2/core';
constructor(@Optional() private _logger:Logger) { }
Child Injectors - 非单例
var injector = Injector.resolveAndCreate([Engine]);
var childInjector = injector.resolveAndCreateChild([Engine]);
injector.get(Engine) !== childInjector.get(Engine);
- 子注入器可继承父类注入器中声明的依赖项
- 声明某依赖创建的实例与父注入器相同token的实例不一致
延伸:
指令注入指令:
@ContentChildren,@ViewChildren
@Component({selector: 'tab', template: 'tab.html'})
class Tab {
@ContentChildren(Pane) panes: QueryList<pane>;
ngAfterContentInit() {
// contentChildren is set
}
}
@Component({selector: 'pane', template: 'pane.html'})
class Pane {
}
export provide(
token: any,
{useClass, useValue, useExisting, useFactory, deps, multi}: {
useClass?: Type,
useValue?: any,
useExisting?: any,
useFactory?: Function,
deps?: Object[],
multi?: boolean
}
) : Provider
可提供的服务 Provider:类,值,工厂
- useExisting:新旧共存,起别名
- multi: 多依赖,[ ]
var injector = Injector.resolveAndCreate([
new Provider("message", { useValue: 'Hello' })
]);
expect(injector.get("message")).toEqual('Hello');
// 可用provide简写new Provider,metadata
providers:[provide('tokenStr', {useValue: 'CONFIG'})]
import { Component } from 'angular2/core';
import {AppHeadComponent} from './app-head.component';
import { LogService } from './log.service';
@Component({
selector: 'my-app',
providers: [ LogService ],
directives: [ AppHeadComponent ],
pipes: [],
styles: [`
.main-body { width: 60px; }
`],
template: `
<my-app-head [title]="titleStr"></my-app-head>
<div class="main-body">
Test..
</div>
`
});
export class MyAppComponent {
titleStr = 'My App';
constructor(private _log: LogService){ }
}
- templateUrl
- Component = Directives + template
Template 模板
- PROPERTY BINDINGS - 属性绑定
- EVENT BINDINGS - 事件绑定
[] vs {{}}
<img src={{picUrl}}>
默认行为
主动权?
-
INTERPOLATION - 插值
<div [textContent]="interpolate(['My name is'], [name])"></div>
// 语法糖
<div>My name is {{name}}</div>
<cmp bind-prop="someExp"></cmp>
// 语法糖
<cmp [prop]="someExp"></cmp>
<cmp on-event="someEvent()"></cmp>
// 语法糖
<cmp (event)="someEvent()"></cmp>
<todo-cmp [model]="todo" (complete)="onComplete(todo)"></todo-cmp>
@Component({
// inputs: ['model'],
// outputs: ['complete'],
selector: 'todo-cmp'
})
class TodoCmp {
@Input() model;
@Output() complete = new EventEmitter(); // emit() subscibe
constructor(){ }
}
inputs vs @Input ?
TodoCmp 内部:
[],() vs 数据流动机制 (~~组件树)
input
output
双向绑定 - In Forms
@Directive({
selector: '[ngModel]',
host: {
"[value]": 'ngModel',
"(input)": "ngModelChange.next($event.target.value)"
}
})
class NgModelDirective {
@Input() ngModel:any; // 存储数据
@Output() ngModelChange:EventEmitter = new EventEmitter() // 事件触发
}
实现原理:
<input [ngModel]="todo.text" (ngModelChange)="todo.text=$event"></input>
// 语法糖
<input [(ngModel)]="todo.text"></input>
ngControl in forms:跟踪状态变化和有效性校验
Pipe - 过滤加工器
<p>Your birthday is {{ birthday | date:'shortDate'}} /p>
以上将输出 'Your birthday is 9/3/1999'
- 内置的Pipes
Date, UpperCase, LowerCase, Currency, Percent
- 链式调用Pipes
<p> Your birthday is{{ birthday | date | uppercase}} </p>
MAR 9, 1999
- 自定义Pipes
import {Pipe, PipeTransform} from 'angular2/core';
//求某数的指数倍
@Pipe({name: 'exponentialStrength'})
export class ExponentialStrengthPipe implements PipeTransform {
transform(value:number, args:string[]) : any {
return Math.pow(value, parseInt(args[0] || '1', 10));
}
}
模板内置指令
ng-show ?
<p [hidden]="expression">
NgClass, NgStyle, NgIf, NgSwitch, NgFor
<my-cmp *ngIf="isShow" [personName]="myName"></my-cmp>
eg:深入剖析NgIf
<my-cmp template="ngIf:isShow" [personName]="myName"></my-cmp>
<template [ngIf]="isShow">
<my-cmp [personName]="myName"></my-cmp>
</template>
Tools
工具
Angular 2 Style Guide
// 不好
<input bindon-ngModel="person.firstName">
// 好
<input [(ngModel)]="person.firstName">
注:避免ElementRef nativeElement尽量使用顶层的方法封装
等等
Debug
Angular 2.0 Batarangle
- chrome extension
- Hot loader - 热加载器:mgechev/angular2-hot-loader
- Angular 2与Redux:wbuchwalter/ng2-redux
Scaffold - 脚手架
ngUpgrade
升级
-
准备工作
-
使用更新适配器进行更新
-
How The Upgrade Adapter Works
-
Bootstrapping Hybrid Angular 1+2 Applications
-
Using Angular 2 Components from Angular 1 Code
-
Using Angular 1 Component Directives from Angular 2 Code
-
Projecting Angular 1 Content into Angular 2 Components
-
Transcluding Angular 2 Content into Angular 1 Component Directives
-
Making Angular 1 Dependencies Injectable to Angular 2
-
Making Angular 2 Dependencies Injectable to Angular 1
-
import {UpgradeAdapter} from 'angular2/upgrade';
var adapter = new UpgradeAdapter();
var app = angular.module('myApp', []);
adapter.bootstrap(document.body, ['myApp']);
混合NG1,NG2代码
资源:
Part 3 - 延伸
- Change Detection - Zone.js
- Reactive Programming - Rx.js
- Web Worker
- server-side Render
Change Detection
Zone.js
造成变化的因素:
- Events - click, change …
- XHR - Ajax远程获取数据
- Timers - setTimeout(), setInterval()
异 步!
怎么通知变化了?
Zone.js
- zone? (Dart, 异步时序, hooks)
dirty:scope.$apply()
foo();
setTimeout(doSth, 0);
bar();
zone.run(() => {
foo();
setTimeout(doSth, 0);
bar();
});
monkey-patched (内置对象扩展) in zone
- Zone.setInterval()
- Zone.alert()
- Zone.prompt()
- Zone.requestAnimationFrame()
- Zone.addEventListener()
- Zone.removeEventListener()
zone.fork({
beforeTask: function () {...},
afterTask: function () {...},
...
})
.run(() => {
foo();
setTimeout(doSth, 0);
bar();
});
NgZone
- onTurnStart()
- onTurnDone()
- onEventDone()
- based on Observables
this.zone.onTurnDone
.subscribe(() => {
this.zone.run(() => this.tick() })
})
tick() {
this.changeDetectorsRefs
.forEach((ref) => ref.detectChanges())
}
如何变化
根组件
子组件
子组件
孙组件
孙组件
孙组件
孙组件
发生变化
孙组件
- 自上而下
- 几毫秒做成百上千次检查
- VM优化
根组件
子组件
子组件
孙组件
孙组件
孙组件
孙组件
发生变化
孙组件
Immutable Objects
- 来源?
- 可变数据的问题?(引用)
- 只依赖于input属性
智能的变化检测
var someData = someAPIForImmutables.create({
name: 'test'
});
var someData2 = someData.set('name', 'test2');
someData === someData2 // false
@Component({
template: `
<h2>{{someData.name}}</h2>
<span>{{someData.email}}</span>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
class CardCmp {
@Input() someData;
}
还可以避免 input不变的 子组件树 的更新
根组件
子组件
子组件
孙组件
孙组件
孙组件
孙组件
发生变化
孙组件
onPush
Observables
订阅模式 (emit, subscribe)
@Component({
template: '{{counter}}',
changeDetection: ChangeDetectionStrategy.OnPush
})
class CartBadgeCmp {
@Input() addItemStream:Observable<any>;
counter = 0;
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
this.addItemStream.subscribe(() => {
this.counter++; // application state changed
this.cd.markForCheck(); // marks path
})
}
}
markForCheck() : 标示变化的路径
根组件
子组件
子组件
孙组件
孙组件
孙组件
孙组件
发生变化
- Big O
- 组合
- 使用场景
孙组件
Reactive Programming
Rx.js
Gulp ?
订阅,观察者
HTTP Client
好处:vs promise - 大一统(events),加工
- 自定义触发时机
- 缓存数据
- 多、无序的请求:switchMap(自动退订)
@Component({
selector: 'my-app',
template: `
<div>
<h2>Wikipedia Search</h2>
<input type="text" [ngFormControl]="term"/>
<ul>
<li *ngFor="#item of items | async">{{item}}</li>
</ul>
</div>
`
})
export class App {
items: Observable<Array<string>>;
term = new Control();
constructor(private wikipediaService: WikipediaService) {
this.items = this.term.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(term => this.wikipediaService.search(term));
}
}
More Detail:
Webworkers
Note:
- 不能访问DOM,window, Document
- 浏览器API
- 不能与UI共享内存
- 并发的问题
使用场景?
<script src="angular2/web_worker/ui.js"></script>
// index.js
import {WORKER_RENDER_PLATFORM, WORKER_RENDER_APPLICATION,
WORKER_SCRIPT} from "angular2/platform/worker_render";
import {platform} from "angular2/core";
platform([WORKER_RENDER_PLATFORM])
.application([WORKER_RENDER_APPLICATION, provider(WORKER_SCRIPT, {useValue: "loader.js"});
// loader.js
importScripts("./es6-shim.js", "./system@0.16.js", "angular2/web_worker/worker.js");
System.import("app");
// app.ts
import {Component, View, platform} from "angular2/core";
import {WORKER_APP_PLATFORM, WORKER_APP_APPLICATION} from "angular2/platform/worker_app";
@Component({
selector: "hello-world"
template: "<h1>Hello {{name}}</h1>
})
export class HelloWorld {
name: string = "Jane";
}
platform([WORKER_APP_PLATFORM])
.application([WORKER_APP_APPLICATION])
.then((ref) => ref.bootstrap(RootComponent));
server-side Render
服务端渲染
preboot - control
注意事项:
- 不要直接操作nativeElement,用Render
-确保多平台能正确更新view
constructor(element: ElementRef, renderer: Renderer) {
renderer.setElementStyle(element.nativeElement, 'font-size', 'x-large');
}
- 尽量让指令无状态化,减少跟随属性的变化<img url="">
- 从"angular2/src/platform/dom/dom_adapter"
使用支持的DOM操作
Summary - 总结
① 概述
- Angular2 前世今生
- ES6
- TypeScript
② 核心部分
- Component
- DI
- Template
- Tools
- ngUpgrade
③ 延伸
-
Change Detection - Zone.js
- Reactive Programming - Rx.js
- Web worker
- server-side Render
更多资源:
- Angular 2官网
- ng2开发者指南 by 广发证券 (Google i18n)
- Angular 2相关书籍
- blog.thoughtram.io
- victorsavkin.com
- angular material2 alpha.0(2016.03.15)
<thank-you author="汤桂川" [date]="'2016.03'">
</thank-you>
tangguichuan@gmail.com
加入广发证券,
一起做前沿好玩的事儿:
Dissect Angular 2
By Tang Guichuan
Dissect Angular 2
Dissect Angular2 (ng2) core ideas
- 7,956