angular从入门到精通(六) — 数据定义展示与模板语法
文章栏目154515450
发布时间:2019-10-16 14:17:51

六、数据定义展示与模板语法

你可以通过把 HTML 模板中的控件绑定到 Angular 组件的属性来显示数据。

1.使用插值表达式显示组件属性

要显示组件的属性,最简单的方式就是通过插值表达式 (interpolation) 来绑定属性名。 要使用插值表达式,就把属性名包裹在双花括号里放进视图模板,如 {{myHero}}

删除 index.component.html 文件,这个范例中不再需要它了。然后,到 index.component.ts 文件中修改组件的模板和代码。

修改完之后,它应该是这样的:

index.component.ts

import { Component, OnInit } from '@angular/core';
import  as $ from 'jquery';

@Component({
  selector: 'app-index',
  template:   '<nav-public></nav-public>
  <div style="margin-top:93px; color: #fff;">
      我是首页{{myHero}}
  </div>',
  styleUrls: ['./index.component.scss']
})

export class IndexComponent implements OnInit {
  title = 'Tour of Heroes';
  myHero = 'Windstorm';
  constructor() {
      this.openMenu();
  };

  ngOnInit() {
  }
  openMenu(){
      $('body').removeClass('noScroll');
      if ($('.collapse').hasClass('collapse-active')) {
          $('.collapse').removeClass('collapse-active');
          $('.collapse').removeClass('collapse-active');
      }
      else {
          $('.collapse').addClass('collapse-active');
      }
  }
}

效果如下:

angular从入门到精通(六) — 数据定义展示与模板语法

这中代入式的模板在大项目中使用可能会遇见小麻烦,维护起来也很糟糕!建议使用templateUrl引入模板

2.内联 (inline) 模板还是模板文件?

你可以在两种地方存放组件模板。 你可以使用 template 属性把它定义为内联的,或者把模板定义在一个独立的 HTML 文件中, 再通过 @Component 装饰器中的 templateUrl 属性, 在组件元数据中把它链接到组件。

到底选择内联 HTML 还是独立 HTML 取决于个人喜好、具体状况和组织级策略。 上面的应用选择内联 HTML ,是因为模板很小,而且没有额外的 HTML 文件显得这个演示简单些。

无论用哪种风格,模板数据绑定在访问组件属性方面都是完全一样的。

内联模板(template)
模板文件(templateUrl)

3.使用构造函数还是变量初始化?

虽然这个例子使用了变量赋值的方式初始化组件,你还可以使用构造函数来声明和初始化属性。

export class AppComponent {
  title: string;
  myHero: string;

  constructor() {
    this.title = 'Tour of Heroes';
    this.myHero = 'Windstorm';
  }
}

4.使用 ngFor 显示数组属性

index.component.ts

import { Component, OnInit } from '@angular/core';
import  as $ from 'jquery';
@Component({
  selector: 'app-index',
  template: <nav-public></nav-public>
  <div style="margin-top:93px; color: #fff;">
      我是首页{{myHero}}
  </div>,
  styleUrls: ['./index.component.scss']
})
export class IndexComponent implements OnInit {
  title: string;
  myHero: string;
  heroes: Array;
  constructor() {
      this.openMenu();
      this.title = 'Tour of Heroes';
      this.heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
      this.myHero = this.heroes[0];
  };
  ngOnInit() {
  }
  openMenu(){
      $('body').removeClass('noScroll');
      if ($('.collapse').hasClass('collapse-active')) {
          $('.collapse').removeClass('collapse-active');
          $('.collapse').removeClass('collapse-active');
      }
      else {
          $('.collapse').addClass('collapse-active');
      }
  }
}

效果如图:

angular从入门到精通(六) — 数据定义展示与模板语法

5.为数据创建一个类

应用代码直接在组件内部直接定义了数据。 作为演示还可以,但它显然不是最佳实践。

现在使用的是到了一个字符串数组的绑定。在真实的应用中,大多是到一个对象数组的绑定。

要将此绑定转换成使用对象,需要把这个英雄名字数组变成 Hero 对象数组。但首先得有一个 Hero 类。

执行命令

ng generate class hero

src/app/hero.ts

export class Hero {
  constructor(
    public id: number,
    public name: string) { }
}

你定义了一个类,具有一个构造函数和两个属性:id 和 name。

它可能看上去不像是有属性的类,但它确实有,利用的是 TypeScript 提供的简写形式 —— 用构造函数的参数直接定义属性。

来看第一个参数:

src/app/hero.ts (id)

public id: number,

这个简写语法做了很多:

声明了一个构造函数参数及其类型。

声明了一个同名的公共属性。

当创建该类的一个实例时,把该属性初始化为相应的参数值。

使用 Hero 类
导入了 Hero 类之后,组件的 heroes 属性就可以返回一个类型化的Hero 对象数组了。

src/app/index/index.component.ts (heroes)

heroes = [
  new Hero(1, 'Windstorm'),
  new Hero(13, 'Bombasto'),
  new Hero(15, 'Magneta'),
  new Hero(20, 'Tornado')
];
myHero = this.heroes[0];

接着,修改模板。 现在它显示的是英雄的 id 和 name。 要修复它,只显示英雄的 name 属性就行了。

src/app/index/index.component.ts (template)
template: `
<h1>{{title}}</h1>
<h2>My favorite hero is: {{myHero.name}}</h2>
<p>Heroes:</p>
<ul>
    <li *ngFor="let hero of heroes">{{ hero.name }}</li>
</ul>`

显示上还和以前一样,不过代码更清晰了。

src/app/index/index.component.ts

import { Component, OnInit } from '@angular/core';
import * as $ from 'jquery';
import { Hero } from '../hero';//引用类
@Component({
    selector: 'app-index',
    template: `  <nav-public></nav-public>
        <div style="margin-top:93px; color: #fff;">我是首页{{myHero}}</div>
        <li *ngFor="let hero of heroes" style="color:#f00">{{ hero.name }}</li>`,
    styleUrls: ['./index.component.scss']
})
export class IndexComponent implements OnInit {
    title: string;
    myHero: string;
    heroes: Array;
    constructor() {
        this.openMenu();
        this.title = 'Tour of Heroes';      //使用类
        this.heroes = [
            new Hero(1, 'Windstorm'),
            new Hero(13, 'Bombasto'),
            new Hero(15, 'Magneta'),
            new Hero(20, 'Tornado')
        ];
        this.myHero = this.heroes[0];
    };
    ngOnInit() {  }
    openMenu(){
        $('body').removeClass('noScroll');
        if ($('.collapse').hasClass('collapse-active')) {
            $('.collapse').removeClass('collapse-active');
            $('.collapse').removeClass('collapse-active');
        }
        else
        {
            $('.collapse').addClass('collapse-active');
        }
    }
}

最终显示结果如图

angular从入门到精通(六) — 数据定义展示与模板语法

七、模板语法

1.插值与模板表达式

插值能让你把计算后的字符串合并到 HTML 元素标签之间和属性赋值语句内的文本中。模板表达式则是用来供你求出这些字符串的。

1-1.插值表达式 {{…}}

src/app/index/index.component.ts

<div style="margin-top:93px; color: #fff;">
    我是首页{{myHero}}
</div>

这个表达式可以调用宿主组件的方法,就像下面用的 getVal():

<!-- "The sum of 1 + 1 is not 4" -->
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}.</p>

代码如下src/app/index/index.component.ts

import { Component, OnInit } from '@angular/core';
import * as $ from 'jquery';
import { Hero } from '../hero';
@Component({
    selector: 'app-index',
    template: `  <nav-public></nav-public>
            <div style="margin-top:93px; color: #fff;">我是首页{{myHero}}</div>
            <li *ngFor="let hero of heroes" style="color:#f00">{{ hero }}</li>
            <p>The sum of 1 + 1 is {{1 + 1}}.</p>
            <p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}.</p>  `,
    styleUrls: ['./index.component.scss']
})
export class IndexComponent implements OnInit {
    title: string;
    myHero: string;
    heroes: Array;
    constructor() {
        this.openMenu();
        this.title = 'Tour of Heroes';
        this.heroes = ['Windstorm1', 'Bombasto2', 'Magneta3', 'Tornado4'];
        this.myHero = this.heroes[0];
    };
    ngOnInit() {  }
    getVal () {    return 2  }
    openMenu(){
        $('body').removeClass('noScroll');
        if ($('.collapse').hasClass('collapse-active')) {
            $('.collapse').removeClass('collapse-active');
            $('.collapse').removeClass('collapse-active');
        }
        else
        {
            $('.collapse').addClass('collapse-active');
        }
    }
}

Angular 对所有双花括号中的表达式求值,把求值的结果转换成字符串,并把它们跟相邻的字符串字面量连接起来。最后,把这个组合出来的插值结果赋给元素或指令的属性。从表面上看,你就像是在元素标签之间插入了结果并对标签的属性进行了赋值。

1-2.模板表达式

模板表达式会产生一个值,并出现在双花括号 {{ }} 中。 Angular 执行这个表达式,并把它赋值给绑定目标的属性,这个绑定目标可能是 HTML 元素、组件或指令。{{1 + 1}} 中所包含的模板表达式是 1 + 1。 在属性绑定中会再次看到模板表达式,它出现在 = 右侧的引号中,就像这样:[property]=”expression”。在语法上,模板表达式与 JavaScript 很像。很多 JavaScript 表达式都是合法的模板表达式,但也有一些例外。你不能使用那些具有或可能引发副作用的 JavaScript 表达式,包括:

赋值 (=, +=, -=, …)

new、typeof、instanceof 等操作符。

使用 ; 或 , 串联起来的表达式

自增和自减运算符:++ 和 —

一些 ES2015+ 版本的操作符

和 JavaScript 语法的其它显著差异包括:

不支持位运算,比如 | 和 &

新的模板表达式运算符,比如 |、?. 和 !。

表达式上下文

典型的表达式上下文就是这个组件实例,它是各种绑定值的来源。 在下面的代码片段中,双花括号中的 myHero和引号中的 heroes所引用的都是 IndexComponent 中的属性。

<div style="margin-top:93px; color: #fff;">
  我是首页{{myHero}}
</div>
{{ hero }}

表达式的上下文可以包括组件之外的对象。 比如模板输入变量 (let customer)和模板引用变量(#customerInput)就是备选的上下文对象之一。

<ul>
  <li *ngFor="let customer of customers">{{customer.name}}</li>
</ul>
<label>Type something:
    <input #customerInput>{{customerInput.value}}
</label>

表达式中的上下文变量是由模板变量、指令的上下文变量(如果有)和组件的成员叠加而成的。 如果你要引用的变量名存在于一个以上的命名空间中,那么,模板变量是最优先的,其次是指令的上下文变量,最后是组件的成员。

2.表达式使用指南

没有可见的副作用

模板表达式除了目标属性的值以外,不应该改变应用的任何状态。

这条规则是 Angular “单向数据流”策略的基础。 永远不用担心读取组件值可能改变另外的显示值。 在一次单独的渲染过程中,视图应该总是稳定的。

最好使用幂等的表达式,因为它没有副作用,并且能提升 Angular 变更检测的性能。

在 Angular 的术语中,幂等的表达式应该总是返回完全相同的东西,直到某个依赖值发生改变。

在单独的一次事件循环中,被依赖的值不应该改变。 如果幂等的表达式返回一个字符串或数字,连续调用它两次,也应该返回相同的字符串或数字。 如果幂等的表达式返回一个对象(包括 Date 或 Array),连续调用它两次,也应该返回同一个对象的引用。

执行迅速

Angular 会在每个变更检测周期后执行模板表达式。 变更检测周期会被多种异步活动触发,比如 Promise 解析、HTTP 结果、定时器时间、按键或鼠标移动。

表达式应该快速结束,否则用户就会感到拖沓,特别是在较慢的设备上。 当计算代价较高时,应该考虑缓存那些从其它值计算得出的值。

非常简单

虽然也可以写复杂的模板表达式,不过最好避免那样做。

属性名或方法调用应该是常态,但偶然使用逻辑取反 ! 也是可以的。 其它情况下,应该把应用程序和业务逻辑限制在组件中,这样它才能更容易开发和测试。

3.模板语句

代码如下src/app/index/index.component.ts

<button (click)="deleteHero()">Delete hero</button>
export class IndexComponent implements OnInit {
  deleteHero () {
    console.log('123')
  }
}

点击deleteHero控制台会打印出来123
如下图

angular从入门到精通(六) — 数据定义展示与模板语法

和表达式中一样,语句只能引用语句上下文中 —— 通常是正在绑定事件的那个组件实例。(click)=”deleteHero()” 中的 deleteHero 就是这个数据绑定组件上的一个方法。

4.绑定语法:概览

数据绑定是一种机制,用来协调用户所见和应用数据。 虽然你能往 HTML 推送值或者从 HTML 拉取值, 但如果把这些琐事交给数据绑定框架处理, 应用会更容易编写、阅读和维护。 只要简单地在绑定源和目标 HTML 元素之间声明绑定,框架就会完成这项工作。
Angular 提供了各种各样的数据绑定,本章将逐一讨论。 先从高层视角来看看 Angular 数据绑定及其语法。
绑定的类型可以根据数据流的方向分成三类: 从数据源到视图、从视图到数据源以及双向的从视图到数据源再到视图。

数据方向 语法 绑定类型
单向从数据源到视图 {{expression}} [target]=”expression” bind-target=”expression” 插值属性AttributeCSS 类样式
从视图到数据源的单向绑定 (target)=”statement” on-target=”statement” 事件
双向 [(target)]=”expression” bindon-target=”expression” 双向

5.绑定目标

数据绑定的目标是 DOM 中的某些东西。 这个目标可能是(元素 | 组件 | 指令的)property、(元素 | 组件 | 指令的)事件,或(极少数情况下) attribute 名。 下面是的汇总表:

绑定类型 目标 范例
属性 元素的 property、组件的 property、指令的 property <img [src]="heroImageUrl"><app-hero-detail [hero]="currentHero"></app-hero-detail><div [ngClass]="{'special': isSpecial}"></div>
事件 元素的事件、组件的事件、指令的事件 <button (click)="onSave()">Save</button><app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail><div (myClick)="clicked=$event" clickable>click me</div>
双向 事件与 property <input [(ngModel)]="name">
Attribute attribute(例外情况) <button [attr.aria-label]="help">help</button>
CSS 类 class property <div [class.special]="isSpecial">Special</div>
样式 style property <button [style.color]="isSpecial ? 'red' : 'green'">