看到这儿,我猜你确定已经看过一些博客、技术大会录像了,如今应该已经准备好踏上angular2这条不归路了吧!那么上路后,哪些东西是咱们须要知道的?javascript
下面就是一些新手常见错误汇总,当你要开始本身的angular2
旅程时,尽可能避免吧。php
注:本文中,我假设诸位已经对angular2
的基础知识有所了解。若是你是绝对新手,以前只据说过,彻底没概念什么是angular2
的,先去读读下面这些资料:css
hidden
属性绑定数据在AngularJS 1
中,若是想切换DOM元素的显示状态,估计你会用AngularJS 1
内置的指令如:ng-show
或者 ng-hide
:angularjs
AngularJS 1
示例:web
<div ng-show="showGreeting"> Hello, there! </div>
而angular2
里,新的模版语法容许你将表达式绑定到DOM元素的任何原生属性上。 这个绝对牛逼的功能带来了无限的可能。其中一项就是绑定表达式到原生的hidden
属性上,和ng-show
有点像,也是为元素设置display: none
:typescript
angular2
的[hidden]
示例(不推荐):express
<div [hidden]="!showGreeting"> Hello, there! </div>
第一眼看上面的例子,彷佛就是AngularJS 1
里的ng-show
。其实否则,她们有着!important
的不一样。
ng-show
和ng-hide
都是经过一个叫ng-hide
的CSS class来控制DOM
元素的显示状态,ng-hide
class就是简单的把元素设置成display: none
。这里的关键在于,AngularJS 1
在ng-hide
class里增长了!important
,用来调整该class的优先级,使得它可以覆盖来自其余样式对该元素display
属性的赋值。
再来讲回本例,原生hidden
属性上的display: none
样式是由浏览器实现的。大多数浏览器是不会用!important
来调整其优先级的。所以,经过[hidden]="expression"
来控制元素显示状态就很容易意外的被其余样式覆盖掉。举个例子:若是我在其余地方对这个元素写了这样一个样式display: flex
,这就比原生hidden
属性的优先级高(看这里)。
基于这个缘由,咱们一般使用*ngIf
切换元素存在状态来完成相同目标:
angular2
的*ngIf
示例(推荐):
<div *ngIf="showGreeting"> Hello, there! </div>
和原生hidden
属性不一样,angular2
中的*ngIf
不受样式约束。不管你写了什么样的CSS,她都是安全的。但仍是有必要提一下,*ngIf
并非控制元素的显示状态,而是直接经过从模版中增长/删除元素该元素来达成显示与否这一效果的。
固然你也能够经过全局的样式给元素的hidden
属性增长隐藏的优先级,譬如:display: none !important
,来达到这个效果。你或许会问,既然angular
小组都知道这些问题,那干吗不在框架里直接给hidden
加一个全局最高优先级的隐藏样式呢?答案是咱们无法保证加全局样式对全部应用来讲都是最佳选择。由于这种方式其实破坏了那些依赖原生hidden
能力的功能,因此咱们把选择权交给工程师。
DOM
APIs只有极少的状况须要直接操做DOM
。angular2
提供了一系列牛X的高阶APIs来完成你指望的DOM
操做,例如:queries。利用angular2
提供的这些APIs有以下优点:
单元测试里不直接操做DOM
能够下降测试复杂度,使你的测试用例跑的更快
把你的代码从浏览器中解藕,容许你在任何渲染环境里跑你的程序,譬如:web worker
,或者彻底离开浏览器(好比:运行在服务器端,亦或是Electron
里)
当你手动操做DOM
时,就失去了上述优点,并且代码越写越不易读。
从AngularJS 1
(或者压根没写过Angular
的人)转型的朋友,我能猜到大概哪些场景是大家想直接操做DOM
的。那咱们来一块儿看下这些情况,我来演示下如何用queries重构她们。
场景 一:当须要获取当前组件模版里的某一个元素时
假设你的组件模版里有一个input
标签,而且你但愿在组件加载后当即让这个input
自动获取焦点
你或许已经知道经过@ViewChild
/@ViewChildren
这两个queries能够获取当前组件模版里的内嵌组件。但在这个例子里,你须要的是获取一个普通的HTML
元素,而非一个组件。一开始估计你就直接注入ElementRef
来操做了:
直接操做ElementRef
(不推荐)
@Component({ selector: 'my-comp', template: ` <input type="text" /> <div> Some other content </div> ` }) export class MyComp { constructor(el: ElementRef) { el.nativeElement.querySelector('input').focus(); } }
其实我想说的是,这种作法不必。
解决方案:@ViewChild
配合local template variable
程序员们没想到的是除了组件自己,其余原生元素也是能够经过local variable
获取的。在写组件时,咱们能够直接在组件模版里给这个input
标签加标记(譬如:#myInput
), 而后把标记传给@ViewChild
用来获取该元素。当组件初始化后,你就能够经过renderer
在这个input
标签上执行focus
方法了。
@ViewChild
配合local variable
(推荐)
@Component({ selector: 'my-comp', template: ` <input #myInput type="text" /> <div> Some other content </div> ` }) export class MyComp implements AfterViewInit { @ViewChild('myInput') input: ElementRef; constructor(private renderer: Renderer) {} ngAfterViewInit() { this.renderer.invokeElementMethod(this.input.nativeElement, 'focus'); } }
场景 二:当须要获取用户映射到组件里的某个元素时
若是你想获取的元素不在你的组件模版定义里怎么办?举个例子,假设你有个列表组件,容许用户自定义各列表项,而后你想跟踪列表项的数量。
固然你能够用@ContentChildren
来获取组件里的“内容”(那些用户自定义,而后映射到你组件里的内容),但由于这些内容能够是任意值,因此是没办法向刚才那样经过local variable
来追踪她们的。
一种方法是,要求用户给他将要映射的列表项都加上预约义的local variable
。这样的话,代码能够从上面例子改为这样:
@ContentChildren
和local variable
(不推荐)
// user code <my-list> <li *ngFor="#item of items" #list-item> {{item}} </li> </my-list> // component code @Component({ selector: 'my-list', template: ` <ul> <ng-content></ng-content> </ul> ` }) export class MyList implements AfterContentInit { @ContentChildren('list-item') items: QueryList<ElementRef>; ngAfterContentInit() { // do something with list items } }
但是,这须要用户写些额外的内容(#list-item
),真心不怎么优雅!你可能但愿用户就只写<li>
标签,不要什么#list-item
属性,那肿么办?
解决方案:@ContentChildren
配合li
选择器指令
介绍一个好方案,用@Directive
装饰器,配合他的selector
功能。定义一个能查找/选择<li>
元素的指令,而后用@ContentChildren
过滤用户映射进当前组件里的内容,只留下符合条件的li
元素。
@ContentChildren
配合@Directive
(推荐)
// user code <my-list> <li *ngFor="#item of items"> {{item}} </li> </my-list> @Directive({ selector: 'li' }) export class ListItem {} // component code @Component({ selector: 'my-list' }) export class MyList implements AfterContentInit { @ContentChildren(ListItem) items: QueryList<ListItem>; ngAfterContentInit() { // do something with list items } }
注:看起来只能选择<my-list>
里的li
元素(例如:my-list li
),须要注意的是,目前angular2
尚不支持"parent-child"模式的选择器。若是须要获取组件里的元素,用@ViewChildren
、 @ContentChildren
这类queries是最好的选择
第一次使用queries时,很容易犯这样的错:
在构造器里打印query的结果(错误)
@Component({...}) export class MyComp { @ViewChild(SomeDir) someDir: SomeDir; constructor() { console.log(this.someDir);// undefined } }
当看到打印出来undefined
后,你或许觉得你的query压根不能用,或者是否是构造器哪里错了。事实上,你就是用数据用的太早了。必需要注意的是,query的结果集在组件构造时是不能用的。
幸运的是,angular2
提供了一种新的生命周期管理钩子,能够很是轻松的帮你理清楚各种query何时是可用的。
若是在用view query(譬如:@ViewChild
,@ViewChildren
)时,结果集在视图初始化后可用。能够用ngAfterViewInit
钩子
若是在用content query(譬如:@ContentChild
,@ContentChildren
)时,结果集在内容初始化后可用。能够用ngAfterContentInit
钩子
来动手改一下上面的例子吧:
在ngAfterViewInit
里打印query结果集(推荐)
@Component({...}) export class MyComp implements AfterViewInit { @ViewChild(SomeDir) someDir: SomeDir; ngAfterViewInit() { console.log(this.someDir);// SomeDir {...} } }
ngOnChanges
侦测query结果集的变化在AngularJS 1
里,若是想要监听一个数据的变化,须要设置一个$scope.$watch
, 而后在每次digest cycle里手动判断数据变了没。在angular2
里,ngOnChanges
钩子把这个过程变得异常简单。只要你在组件里定义了ngOnChanges
方法,在输入数据发生变化时该方法就会被自动调用。这超屌的!
不过须要注意的是,ngOnChanges
当且仅当组件输入数据变化时被调用,“输入数据”指的是经过@Input
装饰器显式指定的那些数据。若是是@ViewChildren
, @ContentChildren
的结果集增长/删除了数据,ngOnChanges
是不会被调用的。
若是你但愿在query结果集变化时收到通知,那不能用ngOnChanges
。应该经过query结果集的changes
属性订阅其内置的observable。只要你在正确的钩子里订阅成功了(不是构造器里),当结果集变化时,你就会收到通知。
举例,代码应该是这个样子的:
经过changes
订阅observable,监听query结果集变化(推荐)
@Component({ selector: 'my-list' }) export class MyList implements AfterContentInit { @ContentChildren(ListItem) items: QueryList<ListItem>; ngAfterContentInit() { this.items.changes.subscribe(() => { // will be called every time an item is added/removed }); } }
若是你对observables一窍不通,赶忙的,看这里
*ngFor
在angular2
里,咱们介绍了一个新概念叫"structural directives",用来描述那些根据表达式在DOM
上或增长、或删除元素的指令。和其余指令不一样,"structural directive"要么做用在template tag上、 要么配合template attribute使用、要么前缀"*"做为简写语法糖。由于这个新语法特性,初学者经常犯错。
你能分辨出来如下错误么?
错误的ngFor
用法
// a:
<div *ngFor="#item in items"> <p> {{ item }} </p> </div> // b: <template *ngFor #item [ngForOf]="items"> <p> {{ item }} </p> </template> // c: <div *ngFor="#item of items; trackBy=myTrackBy; #i=index"> <p>{{i}}: {{item}} </p> </div>
来,一步步解决错误
5a:把"in"换成"of"
// incorrect
<div *ngFor="#item in items"> <p> {{ item }} </p> </div>
若是有AngularJS 1
经验,一般很容易犯这个错。在AngularJS 1
里,相同的repeater写做ng-repeat="item in items"
。
angular2
将"in"换成"of"是为了和ES6中的for-of
循环保持一致。也须要记住的是,若是不用"*"语法糖,那么完整的repeater写法要写做ngForOf
, 而非ngForIn
// correct
<div *ngFor="#item of items"> <p> {{ item }} </p> </div>
5b:语法糖和完整语法混着写
// incorrect
<template *ngFor #item [ngForOf]="items"> <p> {{ item }} </p> </template>
混着写是不必的 - 并且事实上,这么写也不工做。当你用了语法糖(前缀"*")之后,angular2
就会把她当成一个template attribute,而不是通常的指令。具体来讲,解析器拿到了ngFor
后面的字符串, 在字符串前面加上ngFor
,而后看成template attribute来解析。以下代码:
<div *ngFor="#item of items">
会被当成这样:
<div template="ngFor #item of items">
当你混着写时,他其实变成了这样:
<template template="ngFor" #item [ngForOf]="items">
从template attribute角度分析,发现template attribute后面就只有一个ngFor
,别的什么都没了。那必然解析不会正确,也不会正常运行了。
若是从从template tag角度分析,他又缺了一个ngFor
指令,因此也会报错。没了ngFor
指令,ngForOf
都不知道该对谁负责了。
能够这样修正,要么去掉"*"写完整格式,要么就彻底按照"*"语法糖简写方式书写
// correct
<template ngFor #item [ngForOf]="items"> <p> {{ item }} </p> </template> // correct <p *ngFor="#item of items"> {{ item }} </p>
5c:在简写形式里用了错误的操做符
// incorrect
<div *ngFor="#item of items; trackBy=myTrackBy; #i=index"> <p>{{i}}: {{item}} </p> </div>
为了解释这儿到底出了什么错,咱们先不用简写形式把代码写对了看看什么样子:
// correct
<template ngFor #item [ngForOf]="items" [ngForTrackBy]="myTrackBy" #i="index"> <p> {{i}}: {{item}} </p> </template>
在完整形式下,结构仍是很好理解的,咱们来试着分解一下:
咱们经过输入属性向ngFor
里传入了两组数据:
绑定在ngForOf
上的原始数据集合items
绑定在ngForTrackBy
上的自定义track-by函数
用#
声明了两个local template variables
,分别是:#i
和#item
。ngFor
指令在遍历items
时,给这两个变量赋值
i
是从0开始的items
每一个元素的下标
item
是对应每一个下标的元素
当咱们经过"*"语法糖简写代码时,必须遵照以下原则,以便解析器可以理解简写语法:
全部配置都要写在*ngFor
的属性值里
经过=
操做符设置local variable
经过:
操做符设置input properties
去掉input properties里的ngFor
前缀,譬如:ngForOf
,就只写成of
就能够了
用分号作分隔
按照上述规范,代码修改以下:
// correct
<p *ngFor="#item; of:items; trackBy:myTrackBy; #i=index"> {{i}}: {{item}} </p>
分号和冒号实际上是可选的,解析器会忽略它们。写上仅仅是为了提升代码可读性。所以,也能够再省略一点点:
// correct
<p *ngFor="#item of items; trackBy:myTrackBy; #i=index"> {{i}}: {{item}} </p>
但愿本章的解释对你有用。Happy coding!
原文地址:5 Rookie Mistakes to Avoid with Angular 2
来源:https://segmentfault.com/a/1190000004969541