邁向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();
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM