1. 程式人生 > >Angular 資料繫結、響應式程式設計和管道

Angular 資料繫結、響應式程式設計和管道

一.資料繫結

1.資料繫結基本內容

  • <h1>{{productTitle}}!</h1>使用插值表示式將一個表示式的值顯示在模板上
  • <img [src]="imgUrl">使用方括號將HTML標籤的一個屬性繫結到一個表示式上
  • <button (click)="toProductDetail()">商品詳情</button>使用小括號將元件控制器的一個方法繫結為模板上一個事件的處理器
  • Angular預設資料是單向繫結,雙向繫結變為可選項

2.事件繫結

在這裡插入圖片描述

  • 當處理事件的方法需要了解事件的屬性,可以給處理事件的方法新增$event引數,$event

    是標準的事件物件,它的target屬性指向產生事件的DOM節點(即input節點)

  • 等號右側可以是函式呼叫,也可以是屬性賦值,如<button (click)="saved=true">表示當按鈕被點選時元件控制器中save屬性被賦值為true

  • 應用例項

    • 建立新的應用ng new eventbind,建立新元件ng g component testevent

    • 在元件的控制器中新增事件處理函式列印事件物件

      import { Component, OnInit } from '@angular/core';
      
      @Component({
        selector: 'app-testevent',
        templateUrl: './testevent.component.html',
        styleUrls: ['./testevent.component.css']
      })
      export class TesteventComponent implements OnInit {
      
        constructor() { }
      
        ngOnInit() {
        }
      
        onInputEvent(event) {
          console.log(event);
        }
      
      }
      
    • 在頁面給button控制元件繫結click事件

      <p>
        click me
        <button (click)="onInputEvent($event)">test</button>
      </p>
      
    • 在應用頁面使用對應元件

      <app-testevent></app-testevent>
      

3.屬性繫結

  • 宣告:插值表示式和屬性繫結是一個東西

    • 舉例說明
      • <img [src]='imgUrl'>表示將控制器中的imgUrl屬性繫結到src屬性上
      • <img src='{{imgUrl}}'>
        效果同上
      • 實際上執行時插值表示式的形式會轉換成屬性繫結的形式
  • html和dom的區別

    • dom是一個型別是html對應xxxElement的物件,每個dom都有自己的屬性和方法
    • 如:<input value="Tom" (input)="doInput($event)">input標籤的value屬性會被初始化成inputElement物件的value屬性值。我們修改input裡面內容時,檢視event.target.value是一直變化的(此方式檢視的是dom屬性),檢視event.target.getAttribute('value')是不變的(此方式檢視的是html屬性)。總結:html屬性不變,指定的是初始值;dom屬性改變,表示是當前的值。html屬性用於初始化dom屬性。【重點部分】
    • 當我們使用button控制元件時,預設的disabled屬性值是false,當我們想讓按鈕禁用時需要在html標籤中新增disabled屬性【不論有沒有屬性值,不論屬性值是任何值,該按鈕就是被禁用的】。如果我們想使按鈕可用,則需要修改dom屬性的disabled屬性值為false
    • html屬性和dom屬性的關係
      • 少量HTML屬性和DOM屬性之間有著1:1的對映,如id
      • 有些HTML屬性沒有對應的DOM屬性,如colspan
      • 有些DOM屬性沒有對應的HTML屬性,如textContent
      • 就算名字相同,HTML屬性和DOM也不是同一個東西
      • HTML屬性的值指定了初始值;DOM屬性的值表示當前值;DOM屬性的值可以改變;HTML屬性的值不能改變
      • 模板繫結是通過DOM屬性和事件來工作的,而不是HTML屬性
    • 插值表示式是DOM屬性繫結
  • DOM屬性繫結示例圖

在這裡插入圖片描述

4.HTML繫結

  • 基本HTML屬性繫結<td [attr.colspan]="tableColspan">Something</td>,將tableColspan繫結到html的colspan屬性上

  • CSS類繫結

    • <div class="aaa bbb" [class]="someExpression">something</div>表示someException的值會完全替換掉之前class中的值
    • <div [class.special]="isSpecial">something</div>表示isSpecial表示式返回一個boolean值,若返回值為true時,div上就會多一個special的class
    • <div [ngClass]="{aaa:isA, bbb:isB}">使用ngClass指令表示可以同時控制多個css類是否顯示,對應的是一個物件,aaa、bbb就是css類的名字,通過isA和isB的返回值為true或false決定是否有對應樣式
  • 樣式繫結(跟CSS類類似,只不過控制的是樣式)

    • <button [style.color]="isSpecial?'red':'green'>Red</button>"表示isSpecial的值如果是true時,style中的color值就是red
    • <button [ngStyle]='{"font-style":this.canSave?"italic":"normal"}'ngStyle指令使用類似上方ngClass
  • 應優先使用DOM屬性繫結的方式進行繫結,為什麼我們還需要HTML屬性繫結呢?當元素沒有DOM屬性可綁時,就需要使用HTML屬性進行繫結。【預設的是DOM屬性繫結】

  • colspan屬性繫結的舉例

    • 在頁面使用<td [colspan]="size">一列資料</td>頁面會報錯Can't bind to 'colspan' since it isn't a known property of 'td'. (",因為預設繫結的是DOM屬性,但是DOM屬性沒有colspan
    • 故需要使用<td [attr.colspan]="size">一列資料</td>進行繫結,則可以成功繫結
  • HTML屬性繫結演示圖

在這裡插入圖片描述

  • 單項繫結表示的是從控制器到模板的資料繫結

  • 因為繫結到的是HTML屬性,故設定的是HTML屬性值,瀏覽器會自動將HTML屬性值同步到DOM屬性值上,最終渲染頁面顯示內容

  • CSS類繫結:為元素新增和移除CSS類

    • 全有或全無的class替換[class]="表示式",表示式直接在控制器中定義字串即可
    • 有固定不變的css類和動態變化的CSS類[class.動態變化的樣式名]='表示式',表示式如果返回true則新增此CSS類,返回false則不新增
    • 同時管理多個動態變化的CSS類[ngClass]="css類名1:表示式1,css類名2:表示式2}",表示式為true則新增對應的樣式。當然後面值的也可以直接傳入控制器中的物件,如[ngClass]="物件名",在控制器中定義物件名={css類名1:表示式1,css類名2:表示式2}也是一樣使用的。
  • 樣式繫結:與CSS類繫結相似,但不是以class開頭,而是變成了style

    • <div [style.color]="isDev?'red':'green'">Color</div>表示isDev的值如果是true時,style中的color值就是red即字型為紅色
    • 如需加單位,如em、px等等,需要使用[style.樣式名.單位名]=…宣告,如<div [style.font-size.em]="isDev?3:1">Color</div>,如果isDev的值是true,則字型大小為3em
    • 同時設定多個內聯樣式使用[ngStyle]="物件名",控制器中定義物件名={樣式名:樣式值,樣式名:樣式值},從而進行樣式繫結的實現

5.雙向繫結

  • 雙向繫結:無論檢視和模型哪一方改變,另一方都會立即同步
  • 事件繫結方向:模板到控制器,模板變化會帶動控制器資料的變化
  • 屬性繫結方向:控制器到模板,控制器資料的變化會帶動模板顯示的變化
  • 使用[(ngModel)]="控制器屬性"可以實現雙向繫結,常用於表單的處理,處理表單時處理表單欄位與資料模型的值

二.響應式程式設計

  • 概念

    • 可觀察者Observable(流):表示一組值或者事件的集合
    • 觀察者Observer:一個回撥函式的集合,它知道怎樣去監聽被Observable傳送的值
    • 訂閱Subscription:表示一個可觀察物件,主要用於取消註冊
    • 操作符Operators:純粹的函式,使開發者可以以函式程式設計的方式處理集合
    • A被賦值為B和C的值。這時,如果我們改變B的值,A的值並不會隨之改變。而如果我們運用一種機制,當B或者C的值發現變化的時候,A的值也隨之改變,這樣就實現了”響應式“。
    • 下面三個重要的概念是響應式流API的構建基礎:
      • 釋出者是事件的傳送方,可以向它訂閱。
      • 訂閱者是事件訂閱方。
      • 訂閱將釋出者和訂閱者聯絡起來,使訂閱者可以向釋出者傳送訊號。
  • 響應式程式設計就是:就是非同步資料流程式設計

    constructor() {
        /**
         * 被觀察者可以做三件事
         * 1,發射下一個元素,這個元素可以是任何東西。陣列,事件,變數。。。
         * 2.流可以拋一個異常
         * 3.發射一個訊號告訴觀察者流已經結束
         * Observable來自於rxjs包,這是JavaScript的一個響應式程式設計的包
         *
         */
        //Observable是被觀察的,被觀察者
        Observable.from([1,2,3,4])//from方法用於建立一個流
          .filter(e=>e%2==0)//把偶數過濾出來
          .map(e=>e*e)//計算數字的平方
          .subscribe(//訂閱這個流,接受並處理流中的物件。subscribe括號中的組合起來其觀察者
            e=>console.log(e),//打印出這個流
            err=>console.error(err),//如果出錯的話,打印出錯誤資訊
            ()=>console.log("結束了")//結束之後執行這句話
          )
    
        /**
         * 相應的對於觀察者可以定義三個方法來處理這三件事
         * 第一個方法處理流中發出的元素
         * 第二個方法處理流丟擲的異常
         * 第三個方法在流結束的時候被呼叫
         * 後兩個方法是可選的
         */
      }
    
  • 獲得DOM元素的相關資訊

    • 在瀏覽器中產生的每一個事件,在JavaScript中都會被封裝成event物件

      <input (keyup)="onKey($event)">
      
      • angular既可以處理標準的事件比如keyup,也可以建立和發射自定義的事件,一個事件的處理方法可以傳遞一個可選的$event引數來引用這個事件物件,如果一個事件物件是一個標準的DOM事件,那就可以呼叫這個事件物件的相關屬性和方法來獲取資訊,比如

        //獲取使用者的輸入資訊
         onKey(event){
            console.log(event.target.value);
         }
        
      • event的target屬性指向發射事件的那個HTML元素,這裡也就是input

    • 還有一種模板本地變數的方式來可以來獲得元素的相關資訊

      //myField就代表這個input標籤
      <input #myField (keyup)="onKey(myField.value)">
      
       onKey(value:string){
          console.log(value);
       }
      
      • 在傳統的JavaScript中事件作為一次性的東西。觸發一次,就呼叫一下。但在Angular中事件作為一個永不結束的流來處理。
  • 案例

    • 現在有個搜素框,我輸入東西后就開始進行搜尋。我想搜尋Kingland,當我輸入K的時候就會觸發keyup事件去伺服器搜尋,當我輸入Ki的時候也會去伺服器搜尋。這不是我們想要的。我們想輸入完Kingland後去搜尋。這裡可以設定一個時間間隔,在這個時間間隔內沒有再輸入東西的時候就去伺服器搜尋。這個功能用傳統的JavaScript實現起來比較麻煩。現在用響應式程式設計的方式來實現。

    • 實現程式碼

      //app.module.ts
      import{FormsModule,ReactiveFormsModule} from '@angular/forms'
      imports: [
          BrowserModule,
          FormsModule,
          ReactiveFormsModule
        ],
          
      //輸入框
      <input [formControl]="searchInput">
          
      //元件控制器
      export class BindComponent implements OnInit {
        searchInput:FormControl =new FormControl();
        constructor() {
          this.searchInput.valueChanges//valueChanges的事件流
            .debounceTime(500)//設定為500毫秒時間間隔
            .subscribe(stockCode=>this.getStockInfo(stockCode));//當輸入字元後不會馬上去列印,而是會在500毫秒內沒有再輸入新東西時才會去列印
        }
        getStockInfo(value:string){
          console.log(value);
        }
        ngOnInit() {
        }
      }
      

三.管道

  • 管道處理原始值到顯示值的轉換。如我的生日是{{birthday}}顯示的是後臺傳過來new Date()的值,使用者體驗不好;則可以使用date管道格式化對應的birthday內容{{birthday | date:'yyyy-MM-dd HH:mm:ss'}};或者繼續利用管道將所有字母變成大寫{{birthday | date | uppercase}}

  • 圓周率是{{pi}}顯示的是完整的資料長度;使用number管道格式化數字{{ pi | number:'2.2-2'}},其中2.2-2表示2位整數,2位小數;2.1-4則是2位整數,最少小數保留1位,最多保留4位

  • 使用async管道{{ pi | async }}在頁面上處理非同步的流

  • 自定義管道

    • 生成管道ng g pipe pipe/multiple

    • 管道和元件一樣都是需要宣告模組的declarations屬性中

    • 管道是一個帶有管道元資料裝飾器@Pipe({name:'multiple'})的類,管道通過name定義名字,該名字用在模板表示式中。使用時使用{{ xxx | multiple}}

      • 例項

        import { Pipe, PipeTransform } from '@angular/core';
        @Pipe({
          name: 'multiple'
        })
        export class MultiplePipe implements PipeTransform {
          transform(value: number, args?: number): any {
            if (!args) {
              args = 1;
            }
            return value * args;
          }
        }
        
        //使用時使用{{ 3 | multiple:3}}結果為9
        
      • transform第一個引數為原始值,args表示的是可選項引數,即{{birthday | date:'yyyy-MM-dd HH:mm:ss'}}中輸入值是birthday可選項是'yyyy-MM-dd HH:mm:ss'

四.實戰

  • 新增商品搜尋功能

  • 例項程式碼

    • 在商品元件product.compontent.html中新增輸入框

      <div class="row" style="margin-top: 10px">
        <div class="col-md-12 col-sm-12">
          <div class="form-group">
            <input class="form-control" placeholder="請輸入商品資訊">
          </div>
        </div>
      </div>
      <!-- Angular指令ngFor含義(在頁面上迴圈建立html):迴圈products屬性,每次迴圈的元素放在product變數中 -->
      <div *ngFor="let product of products" class="col-md-4 col-sm-4 col-lg-4" style="padding:20px;float: left;">
        <div class="img-thumbnail">
          <!-- 利用[]進行屬性繫結,[]對標籤屬性括上,並在值中給出對應控制器的變數,則可以將變數值繫結給標籤屬性 -->
          <img [src]="imgUrl" style="width:100%">
          <div class="figure-caption">
            <h6 class="float-right">{{product.price}}元</h6>
            <h6><a [routerLink]="['/product',product.id]">{{product.title}}</a></h6>
            <p>{{product.desc}}</p>
          </div>
          <div>
            <!-- 表示star元件中的rating屬性,應該由product.rating傳遞進去 -->
            <app-stars [rating]="product.rating"></app-stars>
          </div>
        </div>
      </div>
      
    • 在app.module.ts中新增ReactiveFormsModule模組用於響應式程式設計

      import { BrowserModule } from '@angular/platform-browser';
      import { NgModule } from '@angular/core';
      import { AppComponent } from './app.component';
      import { NavbarComponent } from './navbar/navbar.component';
      import { FooterComponent } from './footer/footer.component';
      import { SearchComponent } from './search/search.component';
      import { ProductComponent } from './product/product.component';
      import { StarsComponent } from './stars/stars.component';
      import { CarouselComponent } from './carousel/carousel.component';
      import { ProductDetailComponent } from './product-detail/product-detail.component';
      import { HomeComponent } from './home/home.component';
      import { RouterModule, Routes} from '@angular/router';
      import {ProductService} from './shared/product.service';
      import { FilterPipe } from './pipe/filter.pipe';
      import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
      
      const routeConfig: Routes = [
        {path: '', component: HomeComponent},
        {path: 'product/:productId', component: ProductDetailComponent}
      ]
      
      @NgModule({
        declarations: [
          AppComponent,
          NavbarComponent,
          FooterComponent,
          SearchComponent,
          ProductComponent,
          StarsComponent,
          CarouselComponent,
          ProductDetailComponent,
          HomeComponent,
          FilterPipe
        ],
        imports: [
          BrowserModule,
          FormsModule,
          RouterModule.forRoot(routeConfig),
          ReactiveFormsModule
        ],
        providers: [ProductService],
        bootstrap: [AppComponent]
      })
      export class AppModule { }
      
    • 修改product控制器,新增關鍵字屬性和titleFilter的屬性,在構造方法中訂閱titleFilter的valueChanges事件

      import { Component, OnInit } from '@angular/core';
      import {Product, ProductService} from '../shared/product.service';
      import {FormControl} from '@angular/forms';
      import 'rxjs/Rx';
      @Component({
        selector: 'app-product',
        templateUrl: './product.component.html',
        styleUrls: ['./product.component.css']
      })
      export class ProductComponent implements OnInit {
        public keyword: string;
        public titleFilter: FormControl = new FormControl();
        public products: Product[];
      
        public imgUrl = 'https://www.baidu.com/img/bd_logo1.png?where=super';
      
        constructor(public productService: ProductService) {
          this.titleFilter.valueChanges
            .debounceTime(500)// 需要引入rxjs/Rx
            .subscribe(
              value => this.keyword = value
            );
        }
      
        // 元件被例項化的時候,此方法被呼叫一次,用來初始化元件中的資料
        ngOnInit() {
          this.products = this.productService.getProducts();
        }
      }
      
    • 生成filter管道,transform方法引數1是要過濾的商品列表,引數2定義過濾的欄位,引數3是使用者輸入的關鍵字

      import { Pipe, PipeTransform } from '@angular/core';
      
      @Pipe({
        name: 'filter'
      })
      export class FilterPipe implements PipeTransform {
        transform(list: any[], filterField: any, keyword: any): any {
          if (!filterField || !keyword) {
            return list;
          }
          return list.filter(item => {
            const fieldValue = item[filterField];
            return fieldValue.indexOf(keyword) >= 0;
          });
        }
      }
      
    • 修改product的html使用管道過濾顯示的商品

      <div class="row" style="margin-top: 10px">
        <div class="col-md-12 col-sm-12">
          <div class="form-group">
            <input class="form-control" placeholder="請輸入商品資訊" [formControl]="titleFilter"/>
          </div>
        </div>
      </div>
      <!-- Angular指令ngFor含義(在頁面上迴圈建立html):迴圈products屬性,每次迴圈的元素放在product變數中 -->
      <div *ngFor="let product of products | filter: 'title': keyword" class="col-md-4 col-sm-4 col-lg-4" style="padding:20px;float: left;">
        <div class="img-thumbnail">
          <!-- 利用[]進行屬性繫結,[]對標籤屬性括上,並在值中給出對應控制器的變數,則可以將變數值繫結給標籤屬性 -->
          <img [src]="imgUrl" style="width:100%">
          <div class="figure-caption">
            <h6 class="float-right">{{product.price}}元</h6>
            <h6><a [routerLink]="['/product',product.id]">{{product.title}}</a></h6>
            <p>{{product.desc}}</p>
          </div>
          <div>
            <!-- 表示star元件中的rating屬性,應該由product.rating傳遞進去 -->
            <app-stars [rating]="product.rating"></app-stars>
          </div>
        </div>
      </div>