迈向angularjs2系列(2):angular2指令详解

时间:2023-03-09 15:35:13
迈向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显示为:

迈向angularjs2系列(2):angular2指令详解

结论:并没有用到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。安装的过程我只是改了一下安装目录。

安装目录:

迈向angularjs2系列(2):angular2指令详解

检测git是否安装正确:

首先在开始菜单里找到git cmd(也可以吧快捷方式发送到桌面),然后输出命令git --version。

迈向angularjs2系列(2):angular2指令详解

step2:进入自己的项目目录,运行命令 git clone https://github.com/mgechev/switching-to-angular2.git 。

迈向angularjs2系列(2):angular2指令详解

迈向angularjs2系列(2):angular2指令详解

已经克隆下来。good!

step3:进入项目目录,模块安装,然后启动Server。

$ npm install ;
$ npm start;//启动server

浏览器显示结果为:

迈向angularjs2系列(2):angular2指令详解

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);

浏览器显示结果为:

迈向angularjs2系列(2):angular2指令详解

2.hello-world深度解析

注意:代码位于switching-to-angular2/app/ch4/ts/hello-world目录)

首先,看一下一共有4个文件。

迈向angularjs2系列(2):angular2指令详解

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

迈向angularjs2系列(2):angular2指令详解

step2:搜索"TITLE"。

迈向angularjs2系列(2):angular2指令详解

所以,它的定义在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 );
}
};

build.index.ts

第三,分析一下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,那么打开浏览器,进入红色框的链接。

迈向angularjs2系列(2):angular2指令详解

(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>

默认页面类似之前DEMO的内容

meta.json:

{
"title": "List of items (detailed syntax)",
"description": "List of items using explicit template for ng-for",
"id": ,
"presented": true
}

这里的title在index.html中有使用到。

(3)结果显示。

打开http://localhost:5555/dist/dev/ch4/ts/ng-for/detailed-syntax/地址,显示如下:

迈向angularjs2系列(2):angular2指令详解

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,那么打开浏览器,进入红色框的链接。

迈向angularjs2系列(2):angular2指令详解

(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/地址,那么会有如下结果:

迈向angularjs2系列(2):angular2指令详解

(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元素。

迈向angularjs2系列(2):angular2指令详解

再来看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();
}