一.数据绑定

1.数据绑定基本内容

  • <h1>{{productTitle}}!</h1>使用插值表达式将一个表达式的值显示在模板上
  • <img [src]="imgUrl">使用方括号将HTML标签的一个属性绑定到一个表达式上
  • <button (click)="toProductDetail()">商品详情</button>使用小括号将组件控制器的一个方法绑定为模板上一个事件的处理器
  • Angular默认数据是单向绑定,双向绑定变为可选项

2.事件绑定

Angular 数据绑定、响应式编程和管道-LMLPHP

  • 当处理事件的方法需要了解事件的属性,可以给处理事件的方法添加$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属性绑定示例图

Angular 数据绑定、响应式编程和管道-LMLPHP

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属性绑定演示图

Angular 数据绑定、响应式编程和管道-LMLPHP

  • 单项绑定表示的是从控制器到模板的数据绑定

  • 因为绑定到的是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>
      
10-07 11:30