1. *ngFor
*ngFor指令定义了一些行属性:
export declare class NgForRow {
$implicit: any;
index: number;
count: number;
constructor($implicit: any, index: number, count: number);
first: boolean;
last: boolean;
even: boolean;
odd: boolean;
}
它们只能用在*ngFor语句中,如果在语句外部使用我们需要导出为局部变量。
<a *ngFor="let hero of heroes; let index = index">
{{index + ". " + hero.name}}
</a>
$implicit
为当前行本身,实际上就是我们这里的单个hero。其它几个属性都见名知意。
*ngFor
的语句实际上属于Angular特有的微语法,它被Angular自身解析。
*ngFor
正因为常常操作大量数据而应当在性能上被我们格外关注,首先*ngFor
的改变规则
- 添加项目时,模板的新实例将添加到DOM。
- 当项目被删除时,其模板实例将从DOM中删除。
- 当项目重新排序时,它们各自的模板在DOM中重新排序。
- 否则,该项的DOM元素将保持不变。
以上的情况,*ngFor更新我们的视图都保持足够节源。
但也有些例外,比如当我们改变heroes的引用
heroes = heroes.concat(newHero);
*ngFor
将重新渲染所有DOM,即便它只是增加了一个英雄。这种情况尤其发生在我们的列表数据从服务器更新下来时,它的每一次更新都差不多是一次引用的转变。
Angular为我们提供了NgForTrackBy
指令,它被设置为组件内部类的一个函数,该函数要求返回资源被判断为没有变化的依据。
*ngFor = "let hero of heroes trackBy:trackByHeroes"
trackByHeroes(index : number, hero : Hero){
return hero.id;
}
它可以接收两个参数,当前项索引和当前项原值,这里我们返回英雄的id值,表明两个id值如果一致,则他们是同一个英雄,此时不需要完全重新渲染该dom,而是复用它。
2. 指令加*
的原因
语法糖,将总是展开为模板,例如
<span *ngIf="show" other="other">example</span>
*
语法糖直接展开为
<template [ngIf]="show">
<span other="other">example</span>
</template>
其中,ngIf
使用方括号使得show
作为变量解析,而不是通常html
语义上的直接属性。
什么情况下使用*
语法糖?
所有直接性控制元素渲染与否的指令需要使用*
,例如*ngFor
、*ngIf
、*ngSwitchCase
、*ngPluralCase
,它们都将被包裹在template
内,根据表达式值判定是否被渲染。而ngSwitch
和ngPlural
总是显示,它们的下级指令才直接控制渲染,所以不使用*
语法糖。
其它情况的指令,一般遵守一个规则:
- 需要从元素流入数据到内部类,使用
()
包裹指令,例如所有事件都是从元素触发从而流入内部类引发控制,所以事件指令都是用()
。 - 需要从内部类流出数据到元素,使用
[]
包括指令,例如ngIf
、ngFor
、ngSwitch
等,它们都需要以内部类的变量为判断依据,所以都需要使用[]
,只是ngIf
、ngFor
等由于涉及控制元素是否渲染,它们需要template
参与,所以提供了*
语法糖简化编写,内部依然是[]
。 - 需要双向流动数据的,使用
[()]
,例如[(ngModel)]
就是一个典例。
2. 模板输入变量和模板引用变量
在模板上定义,作用域只在模板内部的普通变量称为模板输入变量。例如
<li *ngFor="let hero of heroes"></li>
其中
let hero
定义了一个模板输入变量,它不能在模板外部使用。
模板引用变量是模板中对 DOM 元素或指令的引用,可在同一元素、兄弟元素或任何子元素中被使用。例如
//原生dom对象,使用#定义
<span #cur></span>
//原生dom对象,使用规范的ref-定义
<span ref-cur></span>
//被Angular封装的form对象
<form #curForm="ngForm">
<input name="name" required [(ngModel)]="name">
//提交按钮被Angular内置的表单验证控制
<input type="submit" [disable]="!curForm.form.valid" value="提交">
</form>
3. 安全导航操作符?.
和管道操作符|
this is my name {{my.name}}
类似上面的表达式,my
对象如果为null
将导致应用报错。我们可以手动判断,例如
this is my name {{my?my.name:''}}
this is my name {{my && my.name}}
应对这种情况,Angular提供了比较优雅的一个表达式操作符?.
,当遇到空值时跳出,避免应用出错。更重要的是,它非常适合多重路径的处理。
this is my name {{my?.name}}
this is the test {{a?.b?.c?.d}}
至于管道操作符,它可以一级一级的流动,还可以使用:
添加管道条件。
4. Angular的插值表达式什么不被支持?
- 赋值 (=, +=, -=, ...)
- new运算符
- 使用;或,的链式表达式
- 自增或自减操作符 (++和--)
- 不支持位运算|和&
- 具有新的模板表达式运算符,比如|和?.
除以上官方明确说明的不被支持,实际上也有更多是不支持的,例如console、typeof/instanceof操作符等。但我们能做到吗?当然可以,在内部类声明方法即可,模板可以调用内部类方法,所以也就解开了插值表达式的限制。例如我们需要判断是否数组:
//*.component.ts
isArray(arr){
return Array.isArray(arr);
}
//*.component.html
*ngIf="isArray(arr)"
5. 分清property属性绑定和Attribute绑定
//完全等同的两种绑定
<img s rc="{{img_url}}" />
<img [src]="img_url" />
Angular
只允许绑定元素已有的原生属性,例如img的src属性,不存在的会报错。
<img [myAttr]="img_attr" />
这时候我们只能使用Attribute
绑定:
<img [attr.myAttr]="img_attr" />
像这种绑定方式还有下面的场景:
<img [class]="imgClass" />
<img [class.imgClass]="true" />
//当然,我们更倾向于使用ngClass来批量管理类名
<img [ngClass]="{imgClass : true}">
<span [style.color]="gray">cmx</span>
<span [style.background-color]="gray">cmx</span>
//跟单位
<span [style.font-size.em]="2">cmx</span>
//驼峰命名
<span [style.fontSize.em]="2">cmx</span>
//当然,我们还是有ngStyle作为批量管理的选择
<span [ngStyle]="{'font-size':'10px'}">cmx</span>
5. 路由返回两种方式
- 路由
constructor(
private router : Router,
private route : ActivatedRoute
){}
this.router.navigate(['../'],{relativeTo:this.route});
- location
constructor(
private location : Location
){}
this.location.back();
6. 信任安全的值
angular2有自身的一套安全过滤系统,例如,动态绑定一个url,angular2会自动把它无害化,诸如使用:unsafe:xxx
的手段。但有时候它会导致我们得不到预期的运行结果,例如当我们使用URL.createObjectUrl用于预览本地选择的图片时,直接将其对象赋值给img标签的src通常会由于该安全机制而失败。如果我们确信自己是对的,就有必要使用angular2提供的api信任它。
//注入DomSanitizer
constructor(
private sanitizer : DomSanitizer
) { }
//根据需要调用下面的方法之一
sanitizer.bypassSecurityTrustHtml(html)
sanitizer.bypassSecurityTrustScript(script)
sanitizer.bypassSecurityTrustStyle(style)
sanitizer.bypassSecurityTrustUrl(url)
sanitizer.bypassSecurityTrustResourceUrl(rurl)
7. 编写一个图片加载完成的指令
目前的情况看来,angular2并没有提供图片load的事件绑定,有需要的话,自己编写也并不难
@Directive({
selector: 'img[loaded]'
})
export class ImgLoadedDirective {
@Input()
loaded : any
@Input()
data : any
constructor(renderer : Renderer, el : ElementRef) {
renderer.listen(el.nativeElement, 'load', () => {
this.loaded(this.data);
});
}
}
指令监听img标签的loaded属性,传入一个方法名,有必要的话还可以传入data,当img触发load事件时,会自动调用该方法并传入data。
//html
![](...)
//ts
load(data){
dosomething;
}
8. ngFor中同时使用ngIf
在一个*ngFor中,如果同时使用了*ngIf将导致转化为template出错,那么*ngFor中我们如果也想条件性渲染部分跳过呢?放弃语法糖可以做到
<p *ngFor="let hero of heroes">{{hero.name}}<p>
/*====================*/
//只显示cmx除外的英雄
<template ngFor let-hero [ngForOf]="heroes">
<p *ngIf="hero.name !== 'cmx'">{{hero.name}}</p>
</template>
9. 使用hash风格的路由
angular2默认采用HTML5的pushState来管理路由,它会导致前端路由与后端路由的冲突,例如当部署到nginx环境时,我们通过首页进入子路由一切正常,但是在子路由路径下,刷新就会报404了。默认情况下nginx会当成这个路径是实际web路径下的资源而去定位它,但可想而知实际是并不存在的。折中的方案可以改回hash风格:
RouterModule.forRoot(routes, {useHash: true})