安装与配置
angular不同的版本对typescript的版本要求是不同的,可以参考这里
angular升级是非常简单的,只要参考官方升级文档 一步一步升即可
1 2 3 4 5 6 7 8 9 10 11 ng serve --host 0.0.0.0 --port 3000 # 启动,指定host,指定port ng build --aot --optimization --build-optimizer # 编译项目 --aot # 默认为false,是否用提前编译进行构建 --optimization # 默认为false,使用构建输出优化 --build-optimizer # 默认为false,使用aot进行优化,推荐加上这个参数 --extract-css # 默认为false,从全局样式中提取css到css文件而不是放在js文件 --source-map # 默认为true,输出source-map文件 --vendor-chunk # 默认为true,将第三方包单独放到一个vendor文件中 ng build --deploy-url /app/ --deploy-url /app/ # 如果想要app运行在一个子路由路径下可以这样做
Module 1 2 3 4 5 6 7 8 9 10 11 @NgModule ({ declarations : [ UserComponent ], imports : [ ], entryComponents : [ DialogComponent , ] })
Lazy loading延迟加载
延迟加载是基于页面路由的,每个路由都可以单独作为一个延迟加载,在进入页面的时候加载该页面所需要的组件
如果实现了延迟加载我们在进入对应的页面后会发现新请求一个1.xxxx.js
的文件,开头是一个数字。这就是当前页面的一些组件,同时我们会发现当前页面的组件在main.js
中没有了
如果我们的页面都是单纯的component而不是module的话需要做这些改造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 export const routes : Routes = [ {path : '' , component : DashboardComponent } ] import {routes} from './dashboard.route' ;@NgModule ({ imports : [ CommonModule , RouterModule .forChild (routes) ], declarations : [ DashboardComponent , ] }) export class DashboardComponent { }const routes : :Routes = [ { path : 'dashboard' , loadChildren : () => import ('./dashhboard/dashboard.module' ).then (m => m.DashboardModule ) } ]
模板语法 数据绑定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <img src="https://haofly.net/{{ image.url }}" /> [ngClass]="{'myClass': selected}" [ngClass]="type='xxx' ? 'mt-1' : 'mt-2'" [ngStyle]="{'pointer-events': ok ? 'none' : 'auto'}" <div [innerHTML]="string" ></div> <div [innerHTML]="var" ></div> constructor(protected _sanitizer: DomSanitizer) { this .var = this ._sanitizer.bypassSecurityTrustHTML('string' ) }
控制语句 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <ul> <li *ngFor ="let item of items; let i = index" > {{i}}:{{item}} </li > <li *ngFor ="let item of map | mapToIterable" > {{item.key}}:{{item.value}} </li > </ul> <div [ngSwitch ]="myvalue" > <div *ngSwitchCase ="'aaa'" > ... </div > <div *ngSwitchCase ="'bbb'" > ... </div > <div *ngSwitchDefault > ... </div > </div > [hidden]="myVar"
get方法/computed方法
1 2 3 get 字段名() { return this .firstname + ' ' + this .lastname ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import { AbstractControl , FormBuilder , FormGroup , Validators } from '@angular/forms' ;export class MyComponent implements OnInit { myForm : FormGroup ; constructor (private formBuilder: FormBuilder ); ngOnInit (): void { this .myForm = this .formBuilder .group ({ formFieldName : ['初始值' , [Validators .required , this .checkName ()]], 字段2 : ['' , []], 字段3 : new FormControl ('' , { validators : [ this .aaaaaa .bind (this ) ], updateOn : 'blur' }), 字段4 : [{value : '初始值' , disabled : true }] }, { validator : this .checkAll }) } this .checkName (): any { return (control : AbstractControl ): { [key : string]: boolean } | null => { return control.value >= 0 && control.value <= 2 ? null : {nameValueError : true }; }; } this .checkAll (formGroup : FormGroup ): any { return (formGroup.value .formName !== 'new' ) ? null : {typeEmpty : true }; } onSubmit (): void { this .submitting = true ; this .myForm .get ('field1' ).setValue (value); if (this .myForm .valid ) { console .log ('its ok' ); } } } <form [formGroup]="myForm" (ngSubmit)="onSubmit()" > <div class ="form-group" > <label > Name</label > <input type ="text" class ="form-control" (input )="inputChange" formControlName ="formFieldName" [(ngModel )]="user.name" > <p class ="form-warning" *ngIf ="submitting && createForm.get('formName').errors" > <span *ngIf ="createForm.get('formName').errors.nameValueError" > // 这是上面自定义的错误 Name Should be 1 or 2. </span > </p > </div > <div class ="mat-form-field" > // 注意表单级别的校验error,不能写在field下面,后者不会显示出来,mmp <mat-error class ="form-errors" *ngIf ="formGroup.hasError('wrongDate')" i18n > The end date should be after the start date. </mat-error > </div > <button type =submit "> Submit</button > </form >
filter过滤器 1 {{ timestamp * 1000 | date : 'yyyy-MM-dd' }}
样式 1 2 3 4 :host ::ng-deep .xxx { }
组件通信 父组件至子组件通信
1 2 3 4 5 <app-child [field]="value" ></app-child> export class ChildComponent { @Input () field : any; }
子组件至父组件通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <app-child (field)="onChildClick($event)" ></app-child> export class ParentComponent { onChildClick (field ) { console .log (field); } } export class ChildComponent { @Output () field = new EventEmitter <String >(); onClick ( ) { this .field .emit ('click' ); } }
用@ViewChild
不仅能获取子组件的字段,还能直接使用子组件的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <app-child></app-child> export class ParentComponent { @ViewChild (ChildComponent ) private childComponent : ChildComponent onTest () { this .childComponent .onTest1 (); } } export class ChildComponent { onTest1 () {} }
不相关的组件通信
创建service来通信,复杂的应用场景这个还是用得比较多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Injectable () export class MyFieldService { private myField : Subject <string> = new Subject <string>(); setMessage (value: string ) { this .myField .next (value) } getMessage ( ) { return this .myField .asObservable () } } export class Component1 { constructor (private myFieldService: MyFieldService ) onFieldChange ( ) { this .myFieldService .setMessage ('new value' ); } } export class Component2 { constructor (private myFieldService: MyFieldService ) { this .myFieldService .getMessage ().subscribe ((value ) => { ... } } }
生命周期 依次是
ngOnChanges(需implements OnChanges): 当设置或重新设置数据绑定的输入属性时响应,但是当组件没有输入,或者使用它时没有提供任何输入,那么框架就不会调用ngOnChanges()
ngOnInit(需implements OnInit)
ngDoCheck
ngAfterContentInit()
ngAfterContentChecked()
ngAfterViewInit(需implements AfterViewInit): 当初始化完组件视图以及子视图或包含该指令的视图之后调用,只会调用一次
ngAfterViewChecked
ngOnDestroy
扩展
事件 Angular1里元素绑定点击事件用ng-click
,但是Angular2里元素绑定点击事件用(click)
,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <button (click)="toggleImage()" > <input (input )="onInput()" (change )="onChange()" (keyup )="onKeyUp(event)" // 键盘输入事件 ,event.target.value可以获取input的value > <select (change )="onChange($event.target.value)" > <option *ngFor ="let item of devices | keyvalue" value ="{{ item.key }}" > {{ item.value }}</option > </select > // keydown事件指定键,例如按下回车 <input (keydown.enter )="" />
网络请求
angularjs的网络操作由HttpClient
服务提供,在4.3.x开始使用HttpClient
代替Http
angular的http请求返回的是一个Observable(可观察对象),在被消费者subscribe(订阅)之前,不会被执行。subscribe函数返回一个subscription对象,里面有一个unsubscribe函数,可以随时拒绝消息的接收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 constructor (private http: HttpClient ) {}ngOnInit (): void { this .http .get ('/' ).subscribe ( data => {}, error => { error.json } ); this .http .post ('' , body, {}, {params : new HttpParams ().set ('id' , 3 ')}); // 添加url参数 this.http.post(' ', body).subscribe(...); // post请求 this.http.post(' ', body, {headers: new HttpHeaders().set(' Authorization ', ' my-auth-token')}); // 设置请求头 this.http.get(' ').subscribe( data => {} err => {' 错误处理'} ); this.http.get(' ').retry(3).subscribe(...); // 设置重试次数 this.http.get(' '). {responseType: ' text'}.subscribe(...); // 请求非json数据 // 设置自定义的超时时间 import { timeout, catchError } from ' rxjs/operators'; import { of } from ' rxjs/observable/of '; this.http.get(' ').pipe(timeout(2000), catchError(e => { return of(null); // 需要注意的是,这里的of的参数会传递给subscribe的res作为返回值 })).subscribe((res) => {}); await this.http.get(' ').toPromise(); // 将网络请求转换为promise就可以用promise的await语法了 // 如果一个函数需要返回一个Observable对象,但是又根据条件来进行http请求,条件满足直接返回结果可以用of来封装一下 if ([condition]) { return of(' result'); } else { return this.http.get(' '); } }
httpclient全局error handler 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import { Injectable } from '@angular/core' ;import { HttpEvent , HttpInterceptor , HttpHandler , HttpRequest , HttpErrorResponse , HTTP_INTERCEPTORS } from '@angular/common/http' ;import { Observable } from 'rxjs/Observable' ;import { _throw } from 'rxjs/observable/throw' ;import 'rxjs/add/operator/catch' ;@Injectable () export class ErrorInterceptor implements HttpInterceptor { intercept (req : HttpRequest <any>, next : HttpHandler ): Observable <HttpEvent <any>> { return next.handle (req) .catch (errorResponse => { if (errorResponse.error && errorResponse.error .msg ) { ... } throw errorResponse; }); } } export const ErrorInterceptorProvider = { provide : HTTP_INTERCEPTORS , useClass : ErrorInterceptor , multi : true , }; @NgModule ({ providers : [ ErrorInterceptor ] })
文件上传 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <input #photoUpload type="file" accept="image/*" (change)="onInput($event)" > <button class ="primaryButton" (click )="uploadImage()" > Upload Image</button > export class MyComponent { @ViewChild ('photoUpload' ) adminPhotoUpload : ElementRef ; uploadImage (): { const files = this .photoUpload .nativeElement .files ; const formData : FormData = new FormData (); formData.append ('file' , file, file.name ); const headers = new HttpHeaders (); headers.append ('Content-Type' , 'multipart/form-data' ); headers.append ('Accept' , 'application/json' ); return this .http .post (`${apiURL} /api/storage` , formData, { headers }); } onInput (event): { this .file = event.target .files [0 ]; } }
单元测试 所有的单元测试文件均以.spec.ts
结尾,该文件具体语法规则如下:
1 2 3 4 describe ('test haofly"s function' , () => { it ('true is true' , () => expect (true ).toEqual (true )); it ('null is true' , () => exect (null ).not .toEqual (true )); });
推荐扩展包 ngx-dropzone
ngx-socket-io
Socket-io扩展
有一个问题是该第三方包现在是支持extraHeaders
的(支持自定义header传入后端),但是却没有发布到npm仓库,参考这个issue ,下面有人提出解决办法,参考这里 ,但是登录的时候还没有token,所以最好是在组件的init里面自己new一个Socket对象吧
TroubleShooting
Cannot read property ‘stringify’ of undefined : 在模板中无法直接使用JSON
等原生对象,可以在constructor()
中传入:
1 2 3 public constructor ( ) { this .JSON = JSON ; }
can’t bind to ‘ngSwitchWhen’ since it isn’t a known property of ‘template’ : ngSwitchWhen
已经被ngSwitchCase
替代了
**can’t bind to ‘ngModel’ since it isn’t a known property of ‘input’: ** 尝试将FormsModule
添加到@NgModule
的imports
中
ng: command not found : npm install -g @angular/cli@latest
URLSearchParams is not a constructor : 通常是因为引用URLSearchParams
是通过import { URLSearchParams } from "url"
引入的,但其实它早就内置于nodejs
中了,可以不用写import语句直接用就可以了
相同路由改变query params页面不跳转 : 这是和很多单页框架一样的特性,这个时候可以用window.location.search
进行页面刷新或者通过监听请求参数的变化来重新获取数据,例如:
1 2 3 4 5 ngOnInit ( ) { this .route .params .subscribe (params => { this .service .get (params).then (...); } }
**ExpressionChangedAfterItHasBeenCheckedErrord: Expression has changed after it was checked.**:这是因为在子组件里面直接改变了父组件的值,通常是在ngAfterViewInit
或者ngOnChanges
中,因为这种改变可能会导致无限循环,所以是禁止的,但是如果确保不会发生无限循环,可以将改变的语句写到setTimeout
中去
给用代码生成的元素绑定事件/addEventListener需要使用.bind方法才能在回调函数内部使用this :
1 2 3 4 5 6 7 ngAfterViewInit ( ) { document .querySelector ('my-element' ).addEventListener ('click' , this .onClick .bind (data, this )); } onClick (data, event ) { }
扩展