试着实现myHide功能,会让我觉得我了解了Angular2
这是Wantedly Advent Calendar 2015活动第17天的帖子。
我是Wantedly工程师@KentoMoriwaki。
想不想试一试Angular2?
Angular2.0 beta终于发布了,但是由于缺乏向后兼容性和使用TypeScript编写,给人一种难以入手的印象。
然而,当我亲自试用后,解决了我在1.x版本中不满意的部分,可以相当漂亮地进行描述,所以我非常喜欢它。
因此,我希望通过在Angular1和Angular2中实现一个简化版本的ngHideDirective,名为myHide,来进行比较,以便让大家对Angular2有一定的了解。我写了这篇文章,希望大家能够从中受益。
这里的目标群体是那些曾经稍微接触过Angular1的人。我写的内容意图是使不了解TypeScript的人也能理解。
Angular1的myHide
首先,我会在Angular1中试着写一下。
my-hide指令根据给定属性的值变化,会添加或移除ng-hide类。
使用该指令的HTML代码如下所示。
<div my-hide="hidden">
Hide me!
</div>
隐藏的值决定了“隐藏我!”的显示和隐藏切换的形象。
这是Directive的定义形式。
angular.module('myApp').directive('myHide', function() {
return {
restrict: 'A',
link: function(scope, element, attr) {
scope.$watch(attr.ngHide, function(value) {
element[value ? 'addClass' : 'removeClass']('ng-hide')
});
}
};
});
本家的ngHide实现大体相同,尽管使用了$animate进行动画效果的控制,同时还包含了一些性能方面的设置。
简单来解释正在进行的事情,就是将指令的名称和返回其配置的函数传递给app.directive。当找到使用该指令的元素时,link函数在配置中被调用,开始监视hidden参数的变化,并根据变化来操作DOM并切换ng-hide类的附加。
Angular2的myHide
我们来试着用Angular2编写这个myHide。
首先,HTML部分明显与1不同。
<div [myHide]="hidden">
Hide me!
</div>
原本是用连字符写的my-hide,现在改为驼峰式写法的myHide,并且用神秘的[]括起来了。
我很高兴ng-hide变为myHide,因为这样能使指令定义更加统一。以前有时候在定义里会写错成app.directive(“my-hide”)之类的。
数据绑定
对于大多数人来说,他们无法直观地理解[]表示什么。
在[]的成员中,还存在()和[()],这些是用来明确绑定类型的。
在Angular 1中,仅通过查看视图无法直观地了解定义了哪些指令,以及哪些属性已经被绑定,但在Angular 2中,一眼就能看出绑定的方式。
在Angular 2中,绑定可以分为以下三种类型。
[] data => view の一方向バインディング
例: [myHide]=”hidden”
データの変更によりビューを変更する際に使用する。
Property Bindingと呼ばれる。
() view => data の一方向バインディング
例: (click)=”onClick()”
ビューのイベントによりデータを変更する際に使用する。
Event Bindingと呼ばれる。
[()] 双方向バインディング
例: [(ngModel)]=”model.name”
[]と()を組み合わせて双方向のバインディングを実現するためのシンタックス。
実際、この例は[ngModel]=”model.name” (ngModelChange)=”model.name=$event”のように2つに分けて書くこともできる。
尽管这个语法一开始看上去非常奇怪,但一旦理解了,我认为与之前一直使用的ng-foo=”bar”相比,对结构的理解变得更简单了。然而,我也担心它与HTML模板引擎的兼容性进一步下降了。
接下来,让我们来定义Directive。
虽然我是用TypeScript写的,但我想要用简单易懂的方式解释,以便初学者也能理解。
import {Directive, Input, ElementRef, Renderer} from 'angular2/core';
@Directive({
selector: '[myHide]'
})
export class HideDirective {
@Input('myHide') set value(val: boolean) {
this.renderer.setElementClass(this.el, 'ng-hide', val);
}
constructor(private el: ElementRef, private renderer: Renderer) { }
}
我们所做的只是定义了HideDirective类。由于@XXX这种写法并不常见,可能会让人感到困惑,但是暂时忽略它并读一下,大概就能理解了。
依赖注入
首先,让我们看一下HideDirective类的构造函数。
constructor(private el: ElementRef, private renderer: Renderer) { }
接受两个参数el和renderer,它们的类型分别是ElementRef和Renderer。将它们设为实例变量。ElementRef和Renderer是Angular2中定义的类,在初始化时通过DI传递了它们的实例。
接下来,我们正在观察进行ng-hide类切换的部分。
@Input('myHide') set value(val: boolean) {
this.renderer.setElementClass(this.el, 'ng-hide', val);
}
如果IgnoreInput,则通过setValue(val:boolean){}定义了value属性的setter。在setter内部,根据接收到的值,添加或删除元素上的ng-hide类。
为了在Angular2中实现服务器端和Web Worker的渲染,不推荐直接操作DOM。因此,我们需要使用先前在构造函数中接收到的ElementRef和Renderer来间接操作DOM。
用装饰器实现
剩下的@Input部分是对应于Angular1中的scope.$watch部分。
为了理解这部分内容,需要解释一下类似于@XXX这样的写法。这是一种叫作TypeScript的装饰器的机制。
通过在类、方法或者属性的定义之前使用它,我们可以给这些对象附加或者扩展元信息的能力。在Angular2中经常使用这种写法,一开始可能会有些迷惑,但是一旦明白了这是定义元信息的方式,就会变得轻松。
@Input(‘myHide’) 为 value 属性提供了用于属性绑定的元信息。这样,当 myHide 的属性名称发生变化时,该 setter 将被调用,从而实现在元素上添加 ng-hide 类的流程。
最后,使用@DirectiveDecorator编写指令的设置即可完成。
@Directive({
selector: '[myHide]'
})
这个指令是作为属性使用的,所以在选择器中写上[myHide]和css选择器。
解放了从Angular1的restrict: ‘AEC’这个咒语中,变得直观和令人高兴。
如果你写到这里,我想你应该对Angular2有个大致的了解了。
在Angular1中,为了定义简单的Directive,必须记住代码的格式,并且熟练使用link、scope、$watch等。然而在Angular2中,基本上只需要进行类的定义,并通过Decorator附加一些信息,就可以编写非常整洁的代码。因此,我觉得编写测试用例也会变得简单,希望能够实现这一点。不过这些方面我会在下次再深入讨论!
总结
-
- 基本的にクラスを定義するだけ
-
- Decorator便利
-
- Bindingがひと目みて分かるようになった
-
- DOMは触らない
- Angular2怖くない