【後转前要多久】# Day28 Angular - 四种资料系结 Binding

Angular产生的档案实在太多辣,我们先专注在以下三个档案:

  • app.component.css
  • app.component.html
  • app.component.ts

先专注於这些档案

xxx.spec.ts 是写测试时的档案,学习阶段时看到.spec.ts都可以先忽略。

使用WebStorm IDE时,我个人会额外安装以下这两个套件

WebStorm IDE额外安装套件

Component Folding 会让同一个元件的四个档案,以资料夹的形式(元件)来预览
CLI QuickSwitch 则是按下 alt+S 时,在同一个元件内切换档案(.html.css.ts)

两个套件的功能

样板、样式、程序元件

范本、样板 template

什麽是范本、样板(template)? 就是 .html 档案

样式 style

什麽是样式(style)? 就是 .css 档案

程序、元件 component

什麽是程序、元件(component)? 就是 .ts档案

有时後元件指的是以上三个(样板、样式、程序)档案的集合体,
有时候是单纯指.ts档案。


系结 binding

什麽是资料系结(binding)?
资料系结就做一件事——绑定属性值

其中又分成单向系结、双向系结

单向系结、双向系结

单向系结仅能从component把值传给template。

在绑定成功後,数值会传递过去、连动修改,
可以想像成是TS丢一个变数过去给HTML吃。

而双向系结则是两边都能连动、互传资料。

以下四种方法前三种都是单向系结,只有最後一项是双向系结。

内嵌系结 interpolation

{{property}}

app.component.html档案内容修改为以下
HTML

<div class="container my-5">
    <h1>{{title}}</h1>
    <a href="{{url}}">{{link}}</a>
</div>

当看到两个大括号{{}}包住的值,
Angular会先对他们做解析(如果他不处理的话,HTML会看不懂这两个大括号在这干嘛用的)

并在app.component.ts中设定属性变数
TS

...
export class AppComponent {
  title = 'My Website';
  link = '前往Google';
  url = 'https://google.com';
}

内嵌系结 interpolation

也可以在程序部分额外加上callback函式,
在两秒後更换title文字,制造动态效果

TS

export class AppComponent {
  title = 'My Website';
  link = '前往Google';
  url = 'https://google.com';

    constructor() {
        setTimeout(() => {
            this.title = '我的网站';
        }, 2000);
    }
}

除了传递值,也可以在样板中直接做运算

HTML

<p>a+b: {{a + b}}</p>

TS

...
export class AppComponent {
  a = 10;
  b = 50;
}

在样板中直接做运算

属性系结 property binding

[property] = 'statement'

HTML

<div class="container my-5">
    <h1>标题</h1>
    <a [href]="url">Google连结</a>
</div>

这次不用两个大括号了,当看到一个中括号[]包住的属性(property),Angular会先对他们做解析(如果他不处理的话HTML也会看不懂这个中括号是在干嘛用的)

在HTML5里面,我们可以透过data-前缀开头的标签来自由定义扩充attribute。
但自定义的data-title没办法透过属性系结来做绑定,因为h1 DOM底下没有这个property。不是所有有出现的attrubite都可以做属性绑定

要如何查h1有哪些property?

在chrome浏览器中进入开发者模式检查

最右边这一大串,这些property都可以进行binding

那除了property以外,要如何绑定自定义的attribute呢?

内嵌系结 <h1 data-title={{title}}}>标题</h1> 也没办法使用

解法:使用attr前缀来说明这是attribute属性 [attr.data-title]=title

回头来看,HTML确实吃到了自定义attribute

事件系结 event binding

(event) = "method($event)"
(event) = "property=value"

此时加上一个button按钮,将HTML稍微修改一下。
HTML

<div class="container my-5">
    <input type="button" value="更换标题">
    <h1 [attr.data-title]=title>标题</h1>
</div>

我希望点击按钮之後才会更换网页大标题,可以透过Angular事件系结做到

可以用(click)="changeTitle()"来绑定事件

HTML

<div class="container my-5">
    <input type="button" value="更换标题" (click)="changeTitle()">
    <h1>{{title}}</h1>
</div>

同时也在typesrcipt中加一个方法(method)

TS

export class AppComponent {
  title = 'My Website';
  changeTitle(){
      this.title = '更换後的标题';
  }
}

也有另一种比较不常用的做法 on-click="method()"
概念同JS的onclick事件做到

HTML

<input type="button" value="更换标题" on-click="changeTitle()">

传入事件参数$event

透过event参数可以做到更多样化的事

HTML

<div class="container my-5">
    <input type="button" value="更换标题 (需同时按下Ctrl)" (click)="changeTitle($event)">
    <h1>{{title}}</h1>
</div>

TS

export class AppComponent {
  title = 'My Website';
  changeTitle($event){
      if ($event.ctrlKey){
          this.title = '更换後的标题';
      }
  }
}

标注传入参数的型别
标注为MouseEvent事件型别

changeTitle($event: MouseEvent){
    if ($event.ctrlKey){
        this.title = '更换後的标题';
    }
}

也可修改如下:
HTML

(click)="changeTitle($event.ctrlKey)

TS

changeTitle(ctrlKey: boolean){
    if (ctrlKey){
        this.title = '更换後的标题';
    }
}

双向系结 two-way binding

[(ngModel)] = 'property'

双向系结能同时做到属性系结()以及事件系结[],所以符号是[()]用他来系结某个property

HTML

<div class="container">
    <input type="text" value="" placeholder="请输入点什麽吧" [(ngModel)]="text">
    <p>您的输入: <span>{{text}}</span></p>
    <p>字数: <span>{{text.length}}</span></p>
</div>

TS

export class AppComponent {
    text = '';
}

回到浏览器上一看,就出现错误讯息了 Can't bind to 'ngModel' since it isn't a known property of 'input'.

看一下IDE,有出现一个提示
Can't bind to [(ngModel)] since it is not provided by any applicable directives

回到 app.module.ts 中,import FomrsModule 就可以正常使用了

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    AppComponent
  ],
    imports: [
        BrowserModule,
        FormsModule
    ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

看到这里时就纳闷了,为何使用到ngModel还要去import一个不相干的form模组?
原因是 NgModel
Creates a FormControl instance from a domain model and binds it to a form control element.

加上 按下ESC时清空输入框的效果

HTML

<div class="container">
    <input type="text" value="" placeholder="请输入点什麽吧" [(ngModel)]="text" (keyup.escape)="textReset()">
    <p>您的输入: <span>{{text}}</span></p>
    <p>字数: <span>{{text.length}}</span></p>
</div>

TS

export class AppComponent {
    text = '';
    textReset(){
        this.text = '';
    }
}

范本参考变数 Template reference variables

与前面几种系结方式不同。
预设情况下,范本参考变数只能在样板中(.html)使用

套在DOM上

直接取得DOM物件,可以在当前的template中使用
变数名称不要与template property冲突

<div class="container">
    <input type="text" value="" placeholder="请输入点什麽吧"
           #tText
           [(ngModel)]="text"
           (keyup.escape)="textReset()">
    <p>您的输入: <span>{{text}}</span></p>
    <p>字数: <span>{{tText.value.length}}</span></p>
</div>

套在Directive上

Directive 就是 <app-...>
就是直接套在别的component元件上、存取该元件底下的所有property

建立一个新的元件

> ng g c header

在该元件中写入一些HTML

<h1>Header!!</h1>

在app.component.html中加入该元件、引用进来

<div class="container">
    <app-header #tHeader></app-header>

    <input type="text" value="" placeholder="请输入点什麽吧"
           #tText
           [(ngModel)]="text"
           (keyup.escape)="textReset()">
    <p>您的输入: <span>{{text}}</span></p>
    <p>字数: <span>{{tText.value.length}}</span></p>

    <input type="button" value="更换标题" (click)="tHeader.title = '更改过後的标题'">
</div>

<<:  Day28 softirq, tasklet, workqueue

>>:  [Day28] 测试依赖外层 Context Provider 的 React 元件:客制化 render 函式

Day14 开发套件 - 范例程序码介绍03 iOS 端

最後来看Native 端(iOS): 补充:iOS 中的 .h 和.m 档 .h 为标头档,做为宣告...

Swift纯Code之旅 Day11. 「TableView(3) - 实作Delegate & DataSource」

前言 昨天已经将 addAlarmTableViewCell 在 addAlarmTableView...

安全意识,培训和教育(security awareness, training and education)

所有雇员(All employees) 总体上,“所有员工”是接受或参加意识介绍或活动的理想目标,...

Day29 Blazor 单元测试

开发一个系统最无聊的过程大概就是除错了,尤其是那种尝试对 null 物件取值的错误(Object r...

不做功课,爱听明牌,给你买到又如何?

疫情爆发,改变很多人的购物习惯,大多数的人买东西,尤其是高贵的3C产品,都会上网比价,再怎样都不能买...