Angular快速学习笔记(3) -- 组件与模板

1. 显示数据#

在 Angular 中最典型的数据显示方式,就是把 HTML 模板中的控件绑定到 Angular 组件的属性。

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

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

Copy
import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <h1>{{title}}</h1> <h2>My favorite hero is: {{myHero}}</h2> ` }) export class AppComponent { title = 'Tour of Heroes'; myHero = 'Windstorm'; }

注意

  • 这里直接使用的 template,而不是templateUrl,template可以直接书写html代码,简单的html推荐这种方式
  • @Component 装饰器中指定的 CSS 选择器 selector,它指定了一个叫 my-app 的元素。 该元素是 index.html 的 body 里的占位符。
Copy
#src/index.html (body) <body> <app-root></app-root> </body>

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

angular提供两种地方存放组件模板

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

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

为数据创建一个类#

使用angular提供的cli:

Copy
ng generate class hero

修改src/app/hero.ts

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

使用这个类:

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

Copy
heroes = [ new Hero(1, 'Windstorm'), new Hero(13, 'Bombasto'), new Hero(15, 'Magneta'), new Hero(20, 'Tornado') ]; myHero = this.heroes[0]; 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> `

在模板里可以自己使用.语法,访问对象属性

使用 ngFor 显示数组属性#

*ngFor 是 Angular 的“迭代”指令。 它将 <li> 元素及其子级标记为“迭代模板”.,同vue.js里的v-for

Copy
export class AppComponent { title = 'Tour of Heroes'; heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado']; myHero = this.heroes[0]; } template: ` <h1>{{title}}</h1> <h2>My favorite hero is: {{myHero}}</h2> <p>Heroes:</p> <ul> <li *ngFor="let hero of heroes"> {{ hero }} </li> </ul> ·

不要忘记 *ngFor 中的前导星号 (*)。它是语法中不可或缺的一部分

通过 NgIf 进行条件显示#

有时,应用需要只在特定情况下显示视图或视图的一部分,这个时候使用ngif,同vue.js里的v-if

Copy
<p *ngIf="heroes.length > 3">There are many heroes!</p>

小结#

  • 带有双花括号的插值表达式 (interpolation) 来显示一个组件属性
  • 用 ngFor 显示数组
  • 用一个 TypeScript 类来为你的组件描述模型数据并显示模型的属性
  • 用 ngIf 根据一个布尔表达式有条件地显示一段 HTML。

2. 模板语法#

Angular 应用管理着用户之所见和所为,并通过 Component 类的实例(组件)和面向用户的模板来与用户交互。

在 Angular 中,组件扮演着控制器视图模型的角色,模板则扮演视图的角色。

模板中的 HTML#

HTML 是 Angular 模板的语言。几乎所有的 HTML 语法都是有效的模板语法。 但值得注意的例外是 <script> 元素,它被禁用了,以阻止脚本注入攻击的风险。(实际上,<script> 只是被忽略了。)

插值表达式 ( {{...}} )#

插值表达式{{...}} 可以把计算后的字符串插入到 HTML 元素标签内的文本或对标签的属性进行赋值。

Copy
<h3> {{title}} <img src="{{heroImageUrl}}" style="height:30px"> </h3>

一般来说,括号间的素材是一个模板表达式,Angular 先对它求值,再把它转换成字符串。 下列插值表达式通过把括号中的两个数字相加说明了这一点:

Copy
<p>The sum of 1 + 1 is {{1 + 1}}</p>

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

Copy
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>

模板表达式#

模板表达式产生一个值。 Angular 执行这个表达式,并把它赋值给绑定目标的属性,这个绑定目标可能是 HTML 元素、组件或指令。

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

Copy
{{title}} <span [hidden]="isUnchanged">changed</span>

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

Copy
<div *ngFor="let hero of heroes">{{hero.name}}</div> <input #heroInput> {{heroInput.value}}

模板语句#

模板语句用来响应由绑定目标(如 HTML 元素、组件或指令)触发的事件。 它出现在 = 号右侧的引号中,就像这样:(event)="statement"。

Copy
<button (click)="deleteHero()">Delete hero</button>

和模板表达式一样,模板语句使用的语言也像 JavaScript。 模板语句解析器和模板表达式解析器有所不同,特别之处在于它支持基本赋值 (=) 和表达式链 (; 和 ,)

某些 JavaScript 语法仍然是不允许的:

  • new 运算符
  • 自增和自减运算符:++ 和 --
  • 操作并赋值,例如 += 和 -=
  • 位操作符 | 和 &
  • 模板表达式运算符

和表达式中一样,语句只能引用语句上下文中 —— 通常是正在绑定事件的那个组件实例。

典型的语句上下文就是当前组件的实例。 (click)="deleteHero()" 中的 deleteHero 就是这个数据绑定组件上的一个方法。

模板语句不能引用全局命名空间的任何东西。比如不能引用 window 或 document,也不能调用 console.log 或 Math.max。

绑定语法#

数据绑定是一种机制,用来协调用户所见和应用数据。绑定的类型可以根据数据流的方向分成三类: 从数据源到视图、从视图到数据源以及双向的从视图到数据源再到视图。

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

新的思维模型#

数据绑定的威力和允许用自定义标记扩展 HTML 词汇的能力,会让你把模板 HTML 当成 HTML+。

在正常的 HTML 开发过程中,你使用 HTML 元素来创建视觉结构, 通过把字符串常量设置到元素的 attribute 来修改那些元素。

Copy
<div class="special">Mental Model</div> <img src="assets/images/hero.png"> <button disabled>Save</button>

在 Angular 模板中,你仍使用同样的方式创建结构和初始化 attribute 值。然后,用封装了 HTML 的组件创建新元素,并把它们当作原生 HTML 元素在模板中使用。

Copy
<!-- Normal HTML --> <div class="special">Mental Model</div> <!-- Wow! A new element! --> <app-hero-detail></app-hero-detail>

改变定式思维;

Copy
<button [disabled]="isUnchanged">Save</button>

直觉告诉你,你正在绑定按钮的 disabled attribute。 并把它设置为组件的 isUnchanged 属性的当前值,但你的直觉是错的!isUnchanged为true时,button增加disabled属性。

模板绑定是通过 property 和事件来工作的,而不是 attribute.

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

属性绑定
Copy
<img [src]="heroImageUrl"> <app-hero-detail [hero]="currentHero"></app-hero-detail> <div [ngClass]="{'special': isSpecial}"></div>

注意需要加[]方括号。

下面的代码是等价的:

Copy
<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p> <p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>

在多数情况下,插值表达式是更方便的备选项。 实际上,在渲染视图之前,Angular 把这些插值表达式翻译成相应的属性绑定。

事件绑定

可以通过 Angular 事件绑定来声明对哪些用户动作感兴趣

圆括号中的名称 —— 比如 (click) —— 标记出目标事件。在下面例子中,目标是按钮的 click 事件。

Copy
<button (click)="onSave()">Save</button> <app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail> <div (myClick)="clicked=$event" clickable>click me</div>

也可以使用on-前缀方式:

Copy
<button on-click="onSave()">On Save</button>
$event 对象

在事件绑定中,Angular 会为目标事件设置事件处理器。绑定会通过名叫 $event 的事件对象传递关于此事件的信息。

Copy
<input [value]="currentHero.name" (input)="currentHero.name=$event.target.value" >
使用 EventEmitter 实现自定义事件

通常,指令使用 Angular EventEmitter 来触发自定义事件。 指令创建一个 EventEmitter 实例,并且把它作为属性暴露出来。 指令调用 EventEmitter.emit(payload) 来触发事件,可以传入任何东西作为消息载荷。 父指令通过绑定到这个属性来监听事件,并通过 $event 对象来访问载荷。

Copy
template: ` <div> <img src="{{heroImageUrl}}"> <span [style.text-decoration]="lineThrough"> {{prefix}} {{hero?.name}} </span> <button (click)="delete()">Delete</button> </div>` deleteRequest = new EventEmitter<Hero>(); delete() { this.deleteRequest.emit(this.hero); }

使用自定义事件:

Copy
<app-hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></app-hero-detail>
双向绑定

对于需要显示数据属性,并在用户作出更改时更新该属性如何处理呢?
在元素层面上,既要设置元素属性,又要监听元素事件变化。Angular 为此提供一种特殊的双向数据绑定语法:[(x)][(x)] 语法结合了属性绑定的方括号 [x] 和事件绑定的圆括号 (x)

Copy
<input [(ngModel)]="name">
Attribute

attribute 绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而是由attr前缀,一个点 (.) 和 attribute 的名字组成。

Copy
<button [attr.aria-label]="help">help</button>
CSS 类

借助 CSS 类绑定,可以从元素的 class attribute 上添加和移除 CSS 类名。

Copy
<div [class.special]="isSpecial">Special</div>
样式

样式绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而由style前缀,一个点 (.)和 CSS 样式的属性名组成。 形如:[style.style-property]。

Copy
<button [style.color]="isSpecial ? 'red' : 'green'">

模板引用变量 ( #var )#

模板引用变量通常用来引用模板中的某个 DOM 元素,它还可以引用 Angular 组件或指令或Web Component。
使用井号 (#) 来声明引用变量。 #phone 的意思就是声明一个名叫 phone 的变量来引用 <input> 元素。

Copy
<input #phone placeholder="phone number"> <!-- phone refers to the input element; pass its `value` to an event handler --> <button (click)="callPhone(phone.value)">Call</button>

输入和输出属性#

输入属性是一个带有 @Input 装饰器的可设置属性。当它通过属性绑定的形式被绑定时,值会“流入”这个属性。

输出属性是一个带有 @Output 装饰器的可观察对象型的属性。 这个属性几乎总是返回 Angular 的EventEmitter。 当它通过事件绑定的形式被绑定时,值会“流出”这个属性。

你只能通过它的输入和输出属性将其绑定到其它组件。

模板表达式操作符#

模板表达式语言使用了 JavaScript 语法的子集,并补充了几个用于特定场景的特殊操作符。 下面介绍其中的两个:管道和安全导航操作符

管道操作符 ( | )#

在绑定之前,表达式的结果可能需要一些转换。例如,可能希望把数字显示成金额、强制文本变成大写,或者过滤列表以及进行排序。

Angular 管道对像这样的小型转换来说是个明智的选择。 管道是一个简单的函数,它接受一个输入值,并返回转换结果。 它们很容易用于模板表达式中,只要使用管道操作符 (|) 就行了。

Copy
<div>Title through uppercase pipe: {{title | uppercase}}</div>

安全导航操作符 ( ?. ) 和空属性路径#

Angular 的安全导航操作符 (?.) 是一种流畅而便利的方式,用来保护出现在属性路径中 null 和 undefined 值。 下例中,当 currentHero 为空时,保护视图渲染器,让它免于失败。

Copy
The current hero's name is {{currentHero?.name}}

当绑定中 title 属性为空,仍然会继续渲染

非空断言操作符(!)#

在 TypeScript 2.0 中,你可以使用 --strictNullChecks 标志强制开启严格空值检查。TypeScript 就会确保不存在意料之外的 null 或 undefined。
在这种模式下,有类型的变量默认是不允许 null 或 undefined 值的,如果有未赋值的变量,或者试图把 null 或 undefined 赋值给不允许为空的变量,类型检查器就会抛出一个错误

Angular 模板中的**非空断言操作符(!)也是同样的用途。

Copy
<div *ngIf="hero"> The hero's name is {{hero!.name}} </div>

与安全导航操作符不同的是,非空断言操作符不会防止出现 null 或 undefined。 它只是告诉 TypeScript 的类型检查器对特定的属性表达式,不做 "严格空值检测"。

3. angular 声明周期钩子#

每个组件都有一个被 Angular 管理的生命周期,Angular 提供了生命周期钩子,把这些关键生命时刻暴露出来,你可以做一些自定义操作。

钩子 用途及时机
ngOnChanges() 当 Angular(重新)设置数据绑定输入属性时响应。 该方法接受当前和上一属性值的 [SimpleChanges](https://angular.cn/api/core/SimpleChanges) 对象当被绑定的输入属性的值发生变化时调用,首次调用一定会发生在 ngOnInit() 之前。
ngOnInit() 在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。Initialize the directive/component after Angular first displays the data-bound properties and sets the directive/component's input properties.在第一轮 ngOnChanges() 完成之后调用,只调用一次
ngDoCheck() 检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应。在每个 Angular 变更检测周期中调用,ngOnChanges() 和 ngOnInit() 之后。
ngAfterContentInit() 当把内容投影进组件之后调用。第一次 ngDoCheck() 之后调用,只调用一次。
ngAfterContentChecked() 每次完成被投影组件内容的变更检测之后调用。ngAfterContentInit() 和每次 ngDoCheck() 之后调用
ngAfterViewInit() 初始化完组件视图及其子视图之后调用。第一次 ngAfterContentChecked() 之后调用,只调用一次。
ngAfterViewChecked() 每次做完组件视图和子视图的变更检测之后调用。ngAfterViewInit() 和每次 ngAfterContentChecked() 之后调用。
ngOnDestroy() 当 Angular 每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。在 Angular 销毁指令/组件之前调用

OnInit钩子#

使用 ngOnInit() 有两个原因:

  • 在构造函数之后马上执行复杂的初始化逻辑
  • 在 Angular 设置完输入属性之后,对该组件进行准备。

OnDestroy()钩子#

一些清理逻辑必须在 Angular 销毁指令之前运行,把它们放在 ngOnDestroy() 中。

OnChanges() 钩子#

一旦检测到该组件(或指令)的输入属性发生了变化,Angular 就会调用它的 ngOnChanges() 方法

Copy
ngOnChanges(changes: SimpleChanges) { for (let propName in changes) { let chng = changes[propName]; let cur = JSON.stringify(chng.currentValue); let prev = JSON.stringify(chng.previousValue); this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`); } }

4.组件之间的交互#

通过输入型绑定把数据从父组件传到子组件#

HeroChildComponent 有两个输入型属性,它们通常带@Input 装饰器。

Copy
import { Component, Input } from '@angular/core'; import { Hero } from './hero'; @Component({ selector: 'app-hero-child', template: ` <h3>{{hero.name}} says:</h3> <p>I, {{hero.name}}, am at your service, {{masterName}}.</p> ` }) export class HeroChildComponent { @Input() hero: Hero; @Input('master') masterName: string; }

父组件 HeroParentComponent。

Copy
import { Component } from '@angular/core'; import { HEROES } from './hero'; @Component({ selector: 'app-hero-parent', template: ` <h2>{{master}} controls {{heroes.length}} heroes</h2> <app-hero-child *ngFor="let hero of heroes" [hero]="hero" [master]="master"> </app-hero-child> ` }) export class HeroParentComponent { heroes = HEROES; master = 'Master'; }

通过 setter 截听输入属性值的变化#

如果要对父组件赋值做修改,可以使用setter

Copy
import { Component, Input } from '@angular/core'; @Component({ selector: 'app-name-child', template: '<h3>"{{name}}"</h3>' }) export class NameChildComponent { private _name = ''; @Input() set name(name: string) { this._name = (name && name.trim()) || '<no name set>'; } get name(): string { return this._name; } }

通过ngOnChanges()来截听输入属性值的变化#

使用 OnChanges 生命周期钩子接口的 ngOnChanges() 方法来监测输入属性值的变化并做出回应

下面的 VersionChildComponent 会监测输入属性 major 和 minor 的变化,并把这些变化编写成日志以报告这些变化

Copy
import { Component, Input, OnChanges, SimpleChange } from '@angular/core'; @Component({ selector: 'app-version-child', template: ` <h3>Version {{major}}.{{minor}}</h3> <h4>Change log:</h4> <ul> <li *ngFor="let change of changeLog">{{change}}</li> </ul> ` }) export class VersionChildComponent implements OnChanges { @Input() major: number; @Input() minor: number; changeLog: string[] = []; ngOnChanges(changes: {[propKey: string]: SimpleChange}) { let log: string[] = []; for (let propName in changes) { let changedProp = changes[propName]; let to = JSON.stringify(changedProp.currentValue); if (changedProp.isFirstChange()) { log.push(`Initial value of ${propName} set to ${to}`); } else { let from = JSON.stringify(changedProp.previousValue); log.push(`${propName} changed from ${from} to ${to}`); } } this.changeLog.push(log.join(', ')); } }

父组件监听子组件的事件#

子组件暴露一个 EventEmitter 属性,当事件发生时,子组件利用该属性 emits(向上弹射)事件。

子组件的 EventEmitter 属性是一个输出属性,通常带有@Output 装饰器

Copy
import { Component, EventEmitter, Input, Output } from '@angular/core'; @Component({ selector: 'app-voter', template: ` <h4>{{name}}</h4> <button (click)="vote(true)" [disabled]="voted">Agree</button> <button (click)="vote(false)" [disabled]="voted">Disagree</button> ` }) export class VoterComponent { @Input() name: string; @Output() onVoted = new EventEmitter<boolean>(); voted = false; vote(agreed: boolean) { this.onVoted.emit(agreed); this.voted = true; } }

父组件,可以绑定onVoted事件,框架(Angular)把事件参数(用 $event 表示)传给事件处理方法:

Copy
import { Component } from '@angular/core'; @Component({ selector: 'app-vote-taker', template: ` <h2>Should mankind colonize the Universe?</h2> <h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3> <app-voter *ngFor="let voter of voters" [name]="voter" (onVoted)="onVoted($event)"> </app-voter> ` }) export class VoteTakerComponent { agreed = 0; disagreed = 0; voters = ['Mr. IQ', 'Ms. Universe', 'Bombasto']; onVoted(agreed: boolean) { agreed ? this.agreed++ : this.disagreed++; } }

父组件和子组件通过服务来通讯#

在之前讲服务时就提过,同一个module下的组件间,可以通过服务进行通讯。父组件和它的子组件共享同一个服务,利用该服务在家庭内部实现双向通讯。

5.组件样式#

Angular 应用使用标准的 CSS 来设置样式。这意味着你可以把关于 CSS 的那些知识和技能直接用于 Angular 程序中,例如:样式表、选择器、规则以及媒体查询等。

有几种方式把样式加入组件:

  • 设置 styles 或 styleUrls 元数据
  • 内联在模板的 HTML 中
  • 通过 CSS 文件导入

预编译css#

如果使用 CLI 进行构建,那么你可以用 sass、less 或 stylus 来编写样式,并使用相应的扩展名(.scss、.less、.styl)把它们指定到 @Component.styleUrls 元数据中。例子如下:

Copy
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] })

6.属性指令#

在 Angular 中有三种类型的指令:

  1. 组件 — 拥有模板的指令
  2. 结构型指令 — 通过添加和移除 DOM 元素改变 DOM 布局的指令
  3. 属性型指令 — 改变元素、组件或其它指令的外观和行为的指令。

创建属性指令:#

Copy
ng generate directive highlight

编写代码,这个指令实现高亮元素:

Copy
import { Directive, ElementRef } from '@angular/core'; @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { constructor(el: ElementRef) { el.nativeElement.style.backgroundColor = 'yellow'; } }

通过ElementRef访问目标元素。

使用属性型指令#

Copy
<p appHighlight>Highlight me!</p>

使用 @Input 数据绑定向指令传递值#

Copy
@Input() highlightColor: string; <p appHighlight highlightColor="yellow">Highlighted in yellow</p> <p appHighlight [highlightColor]="'orange'">Highlighted in orange</p>

绑定到 @Input 别名#

可以使用Input的别名

Copy
@Input('appHighlight') highlightColor: string; <p [appHighlight]="color">Highlight me!</p>

作者:Jadepeng
出处:jqpeng的技术记事本--http://www.cnblogs.com/xiaoqi
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

关注作者

欢迎关注作者微信公众号, 一起交流软件开发:欢迎关注作者微信公众号

posted @   JadePeng  阅读(3390)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示
CONTENTS