迈向angularjs2系列(2):angular2指令详解
目录
1.hello world!
2.配置开发环境
源代码下载
链接: https://pan.baidu.com/s/1i5pGloT 密码: g7ub
一:helloworld!
注意:这一小节的内容,并非生产环境的做法,读者可以不必操作,看演示就好了。
为了简单快速的运行ng2程序,那么引入直接angular2版本和页面的基本框架。
index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <app></app> <!--使用app组件--> <script src="https://code.angularjs.org/2.0.0-beta.9/angular2-polyfills.min.js"> </script> <script src="https://code.angularjs.org/2.0.0-beta.9/Rx.umd.min.js"> </script> <script src="https://code.angularjs.org/2.0.0-beta.9/angular2-all.umd.min.js"> </script> <script src="./app.js"></script> </body> </html>
app.js:
var App=ng.core.Component({ //定义了名称为App的组件。 selector:"app", //匹配所有的app标签 template:"<h1>hello {{target}}</h1>" }) .Class({ //Class函数传递了一个对象字面量,只有constuctor方法 constructor:function(){ this.target="world"; } }); ng.platform.browser.bootstrap(App); //ng.platform.browser是命名空间
直接打开index.html,那么html显示为:
结论:并没有用到typescript,所以它不是必须的,但ng2强烈推荐使用。
二: 配置开发环境和angular2的typescript实现
1.通过git克隆项目
step1:安装git
到网站下载exe文件,https://github.com/git-for-windows/git/releases/download/v2.13.2.windows.1/Git-2.13.2-64-bit.exe。安装的过程我只是改了一下安装目录。
安装目录:
检测git是否安装正确:
首先在开始菜单里找到git cmd(也可以吧快捷方式发送到桌面),然后输出命令git --version。
step2:进入自己的项目目录,运行命令 git clone https://github.com/mgechev/switching-to-angular2.git 。
已经克隆下来。good!
step3:进入项目目录,模块安装,然后启动Server。
$ npm install ;
$ npm start;//启动server
浏览器显示结果为:
success!
step4:上手试玩(optional)
内容替换,switching-to-angular2/app/ch4/ts/hello-world/app.ts:
import {Component} from '@angular/core'; import {bootstrap} from '@angular/platform-browser-dynamic'; @Component({ selector: 'app', templateUrl: './app.html' }) class App { target:string; constructor() { this.target = 'world'; } } bootstrap(App);
浏览器显示结果为:
2.hello-world深度解析
注意:代码位于switching-to-angular2/app/ch4/ts/hello-world目录)
首先,看一下一共有4个文件。
index.html负责hello-world的首页(默认)文件的显示,app.html负责app模板的内容,meta.json是一些元信息,app.ts负责js代码逻辑。
接下来详细看index.html。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title><%= TITLE %></title> <!--TITLE变量注入--> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- inject:css --> <!-- endinject --> </head> <body> <app>Loading...</app> <!--app组件--> <!-- inject:js --> <!-- endinject --> <%= INIT %> <!--INIT是变量注入--> </body> </html>
app标签有文本内容,"Loading"一直处于可见状态,直到应用启动好、主组件渲染完毕为止。而 <%= TITLE %> 和 <%= INIT %> 是用来注入变量的。这些变量是在哪里定义的呢?
另,传授一个全局搜索文本的方法:
step1:文件目录右键,选择find in path
step2:搜索"TITLE"。
所以,它的定义在switching-to-angular2/tools/tasks的build.index.ts文件中。
build.index.ts:
import {join, sep} from 'path'; import {APP_SRC, APP_DEST, DEPENDENCIES, SYSTEM_CONFIG, ENV} from '../config'; import {transformPath, templateLocals} from '../utils'; export = function buildIndexDev(gulp, plugins) { return function () { return gulp.src(join(APP_SRC, '**', 'index.html')) // NOTE: There might be a way to pipe in loop. .pipe(inject()) .pipe(plugins.template( require('merge')(templateLocals(), { TITLE: 'Switching to Angular 2', INIT: ` <script> System.config(${JSON.stringify(SYSTEM_CONFIG)}); System.import("./app") .catch(function () { console.log("Report this error to https://github.com/mgechev/switching-to-angular2/issues", e); }); </script>` }) )) .pipe(gulp.dest(APP_DEST)); }; function inject() { return plugins.inject(gulp.src(getInjectablesDependenciesRef(), { read: false }), { transform: transformPath(plugins, 'dev') }); } function getInjectablesDependenciesRef() { let shims = DEPENDENCIES.filter(dep => dep['inject'] && dep['inject'] === 'shims'); let libs = DEPENDENCIES.filter(dep => dep['inject'] && dep['inject'] === 'libs'); let all = DEPENDENCIES.filter(dep => dep['inject'] && dep['inject'] === true); return shims.concat(libs).concat(all).map(mapPath); } function mapPath(dep) { let prodPath = join(dep.dest, dep.src.split(sep).pop()); return ('prod' === ENV ? prodPath : dep.src ); } };
第三,分析一下ch4/ts/hello-world/app.ts。
import {Component} from '@angular/core'; //导入了component装饰器 import {bootstrap} from '@angular/platform-browser-dynamic'; //导入了bootstrap函数 @Component({ //Component装饰了class App selector: 'app', templateUrl: './app.html' //对象字面量参数和ES5类似,app选择器和视图内容。模板既可以template内联,也可以使用url,和ng1类似。 }) class App { target:string; constructor() { this.target = 'world'; } } bootstrap(App); //启动APP
四: angular2指令详解
开发app组件现在开始了!
1.基础to-do list应用
(1)运行代码:
进入switchingToNG2/switching-to-angular2目录,运行npm start,那么打开浏览器,进入红色框的链接。
(2)进入switching-to-angular2/app/ch4/ts/ng-for/detailed-syntax目录,一共4个部分。
app.ts:
import {Component} from '@angular/core'; import {bootstrap} from '@angular/platform-browser-dynamic'; //导入 @Component({ //装饰器 selector: 'app', templateUrl: './app.html', }) class App { todos:string[]; name:string; constructor() { //app类的属性定义,可以直接在app组件的代码里使用 this.name = "爱莎"; this.todos = ['呼风唤雪', "保护妹妹"]; } } bootstrap(App); //启动应用
app.html:
<h1>你好,{{name}}!</h1> <h3> to-do清单: </h3> <ul> <template ngFor let-todo [ngForOf]="todos"> <li>{{todo}}</li> </template> </ul>
说明:template标签内部可以放HTML片段。template的作用是屏蔽了浏览器的直接渲染,交给模板引擎来处理。
index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title><%= TITLE %></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- inject:css --> <!-- endinject --> </head> <body> <app>Loading...</app> <!-- inject:js --> <!-- endinject --> <%= INIT %> </body> </html>
meta.json:
{ "title": "List of items (detailed syntax)", "description": "List of items using explicit template for ng-for", "id": 3, "presented": true }
这里的title在index.html中有使用到。
(3)结果显示。
打开http://localhost:5555/dist/dev/ch4/ts/ng-for/detailed-syntax/地址,显示如下:
2. 更加优秀的ngFor指令
ngFor用来遍历数据,和ng1的ng-repeat类似,也更优秀。
<template ngFor let-todo [ngForOf]="todos"> <li>{{todo}}</li> </template>
ng1的指令用法五花八门,需要对各种属性值深入去理解。而ng2引入了更加简单的约定,语义性更高。
属性有3种用法:
●propertyName="value"
第一种语法:普通字面量
propertyName接收字符串字面量,要使用引号哦。angular不会对它进行进一步处理。
●[propertyName]="expression"
第二种语法:方括号语法
提示angular2要当做表达式来处理。属性包围在方括号里面,angular就会尝试执行这个表达式,执行上下文就是模板对应的组件。
●(eventName)="handlerFunc()"
第三种语法:小括号语法
当然是事件绑定咯。
:) 可以看到,谁是谁,非常清晰。
(1)模板如何使用ngFor指令
<template ngFor let-todo [ngForOf]="todos"> [ngForOf]是遍历的数据集合,let-todo(这里是var-变量名语法)告诉ng2遍历创建的新变量名字叫todo,类型是let。
(2)使用语法糖
语法糖,简单的说就是更方便更简洁的写法。
使用星号,扔掉template标签 ,指令也直接用到容器标签上。
<ul> <li *ngFor="#todo of todos">{{todo}}</li> </ul>
angular2会自动对上面代码进行“脱糖”处理,就能变成上面比较啰嗦的形式了。
3.自定义angular2指令
上一节讲了如何在DOM标签上使用内置指令,这一节讲自定义指令。自定义才是高级玩法。
本小节将构建一个简单的tooltip自定义指令。
(1)运行代码:
进入switchingToNG2/switching-to-angular2目录,运行npm start,那么打开浏览器,进入红色框的链接。
(2)进入目录switching-to-angular2/app/ch4/ts/tooltip,
app.html:
<div saTooltip="Hello world!"> </div>
ch4/ts/tooltip/app.ts:
一共分为导入、Overlay类、指令、常规的APP类定义。
//导入 import {HostListener, Input, Injectable, ElementRef, Inject, Directive, Component} from '@angular/core'; import {bootstrap} from '@angular/platform-browser-dynamic'; //Overlay类的定义 class Overlay { private el: HTMLElement; constructor() { var el = document.createElement('div'); el.className = 'tooltip'; this.el = el; } close() { this.el.hidden = true; } open(el, text) { this.el.innerHTML = text; this.el.hidden = false; var rect = el.nativeElement.getBoundingClientRect(); this.el.style.left = rect.left + 'px'; this.el.style.top = rect.top + 'px'; } attach(target) { target.appendChild(this.el); } detach() { this.el.parentNode.removeChild(this.el); } } //指令定义 @Directive({ selector: '[saTooltip]' }) export class Tooltip { @Input() saTooltip:string; constructor(private el: ElementRef, private overlay: Overlay) { this.overlay.attach(el.nativeElement); } @HostListener('mouseenter') onMouseEnter() { this.overlay.open(this.el, this.saTooltip); } @HostListener('mouseleave') onMouseLeave() { this.overlay.close(); } } //APP类定义,并被component装饰器修饰以及最后的启动 @Component({ selector: 'app', templateUrl: './app.html', providers: [Overlay], directives: [Tooltip] }) class App {} bootstrap(App);
(3)结果显示。
打开http://localhost:5555/dist/dev/ch4/ts/tooltip/地址,那么会有如下结果:
(2)导入的类HostListener、Directive、ElementRef
app.ts导入了HostListener, Input, Injectable, ElementRef, Inject, Directive, Component这些类。
●HostListener(eventname)
用于事件处理。指令实例化的时候,angular2会把这个装饰过的方法当成宿主标签上对应eventname的事件处理函数。
@HostListener('mouseenter') //定义监听器 onMouseEnter() { this.overlay.open(this.el, this.saTooltip); } //定义监听函数
●Directive
用于定义指令。用来添加额外的元数据。
//指令定义 @Directive({ selector: '[saTooltip]' //大小写敏感哦 })
●ElementRef
在宿主标签里注入其他标签的引用,不仅仅是DOM。比如这里的模板就是angular包装过的div标签,里面有tooltip属性。
<div saTooltip="Hello world!"> </div>
(3)指令输入的input装饰器
接收的参数是需要绑定的属性名。如果不传递参数,,默认绑定到同名属性上。
注意:angular的HTML编译器对大小写敏感。
我们来看一下input的构造器到底干了什么?
@Input() //给指令定义一个saTooltip属性,属性的值是表达式。 saTooltip:string;
(4)constructor构造器的理解
constructor(private el: ElementRef, private overlay: Overlay) { //overloay是定义的overlay类 //我们来看一下ElementRef究竟是什么 console.log(el); this.overlay.attach(el.nativeElement); }
●私有属性el
首先,看一下el,也就是ElementRef是什么。根据打印结果,ElementRef就是app.html模板里包装好的div元素。
再来看attach。这行代码负责刚刚的原生div元素添加内容。
attach(target) { target.appendChild(this.el); //target是原生的div,给它添加子元素,内容是overloay定义的this.el }
●私有属性overlay
overlay的类型为Overlay,也就是定义的class类。Overlay类实现了维护tooltip组件外观的逻辑,并可以用angular的DI依赖注入,当然要加上顶层组件Component的定义。
ch4/ts/tooltip/app.ts:
@Component({ selector: 'app', templateUrl: './app.html', providers: [Overlay],//数组哦,实现Overlay的依赖注入 directives: [Tooltip] })
(5)封装指令要在顶层组件声明。
注意:声明的是数组哦。整个组件的内部全部指令都会声明在这里。尽管显式声明有些麻烦,但能带来更好的封装性。而在ng1种所有指令都在全局命名空间中,坏处是容易命名冲突。同时我们知道了组件用了哪些指令,更好理解了。
@Component({ //component装饰器,传递对象字面量作为参数 selector: 'app', templateUrl: './app.html', providers: [Overlay], directives: [Tooltip]//声明指令数组,angular编译器就可以发现tooltip指令了 }) class App {}
五: 其他,重命名指令的输入输出、语法糖的本来面目
先回顾语法糖怎么书写的:
@Directive(...) class Dir{ @Output()outputNmae=new EventEmitter(); @Input() inputName; } //最佳实践推荐的语法糖方式,更容易阅读和理解
本来面目是:
@Directive({
outputs:['outputName:XXXX'],
inputs:['inputs:XXXX']
})
class Dir{
outputName=new EventEmitter();
}