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

 

(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": 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();
}

 

posted @ 2017-07-01 21:48  陈蒙的技术空间  阅读(4240)  评论(0编辑  收藏  举报