Angular快速学习笔记(3) -- 组件与模板
1. 显示数据
在 Angular 中最典型的数据显示方式,就是把 HTML 模板中的控件绑定到 Angular 组件的属性。
使用插值表达式显示组件属性
要显示组件的属性,最简单的方式就是通过插值表达式 (interpolation) 来绑定属性名。 要使用插值表达式,就把属性名包裹在双花括号里放进视图模板,如 {{myHero}}。
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 里的占位符。
#src/index.html (body)
<body>
<app-root></app-root>
</body>
内联 (inline) 模板还是模板文件?
angular提供两种地方存放组件模板
- 你可以使用 template 属性把它定义为内联的,
- 或者把模板定义在一个独立的 HTML 文件中, 再通过 @Component 装饰器中的 templateUrl 属性, 在组件元数据中把它链接到组件
到底选择内联 HTML 还是独立 HTML 取决于个人喜好、具体状况和组织级策略。 上面的应用选择内联 HTML ,是因为模板很小,而且没有额外的 HTML 文件显得这个演示简单些。
为数据创建一个类
使用angular提供的cli:
ng generate class hero
修改src/app/hero.ts
export class Hero {
constructor(
public id: number,
public name: string) { }
}
使用这个类:
src/app/app.component.ts (heroes):
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
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
<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 元素标签内的文本或对标签的属性进行赋值。
<h3>
{{title}}
<img src="{{heroImageUrl}}" style="height:30px">
</h3>
一般来说,括号间的素材是一个模板表达式,Angular 先对它求值,再把它转换成字符串。 下列插值表达式通过把括号中的两个数字相加说明了这一点:
<p>The sum of 1 + 1 is {{1 + 1}}</p>
表达式还可以调用宿主组件的方法,就像下面用的 getVal():
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>
模板表达式
模板表达式产生一个值。 Angular 执行这个表达式,并把它赋值给绑定目标的属性,这个绑定目标可能是 HTML 元素、组件或指令。
典型的表达式上下文就是这个组件实例,它是各种绑定值的来源。 在下面的代码片段中,双花括号中的 title 和引号中的 isUnchanged 所引用的都是 AppComponent 中的属性。
{{title}}
<span [hidden]="isUnchanged">changed</span>
表达式中的上下文变量是由模板变量、指令的上下文变量(如果有)和组件的成员叠加而成的。 如果你要引用的变量名存在于一个以上的命名空间中,那么,模板变量是最优先的,其次是指令的上下文变量,最后是组件的成员。
<div *ngFor="let hero of heroes">{{hero.name}}</div>
<input #heroInput> {{heroInput.value}}
模板语句
模板语句用来响应由绑定目标(如 HTML 元素、组件或指令)触发的事件。 它出现在 = 号右侧的引号中,就像这样:(event)="statement"。
<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 来修改那些元素。
<div class="special">Mental Model</div>
<img src="assets/images/hero.png">
<button disabled>Save</button>
在 Angular 模板中,你仍使用同样的方式创建结构和初始化 attribute 值。然后,用封装了 HTML 的组件创建新元素,并把它们当作原生 HTML 元素在模板中使用。
<!-- Normal HTML -->
<div class="special">Mental Model</div>
<!-- Wow! A new element! -->
<app-hero-detail></app-hero-detail>
改变定式思维;
<button [disabled]="isUnchanged">Save</button>
直觉告诉你,你正在绑定按钮的 disabled attribute。 并把它设置为组件的 isUnchanged 属性的当前值,但你的直觉是错的!isUnchanged为true时,button增加disabled属性。
模板绑定是通过 property 和事件来工作的,而不是 attribute.
数据绑定的目标是 DOM 中的某些东西。 这个目标可能是(元素 | 组件 | 指令的)property、(元素 | 组件 | 指令的)事件,或(极少数情况下) attribute 名。
属性绑定
<img [src]="heroImageUrl">
<app-hero-detail [hero]="currentHero"></app-hero-detail>
<div [ngClass]="{'special': isSpecial}"></div>
注意需要加[]
方括号。
下面的代码是等价的:
<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 事件。
<button (click)="onSave()">Save</button>
<app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail>
<div (myClick)="clicked=$event" clickable>click me</div>
也可以使用on-前缀方式:
<button on-click="onSave()">On Save</button>
$event 对象
在事件绑定中,Angular 会为目标事件设置事件处理器。绑定会通过名叫 $event 的事件对象传递关于此事件的信息。
<input [value]="currentHero.name"
(input)="currentHero.name=$event.target.value" >
使用 EventEmitter 实现自定义事件
通常,指令使用 Angular EventEmitter 来触发自定义事件。 指令创建一个 EventEmitter 实例,并且把它作为属性暴露出来。 指令调用 EventEmitter.emit(payload) 来触发事件,可以传入任何东西作为消息载荷。 父指令通过绑定到这个属性来监听事件,并通过 $event 对象来访问载荷。
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);
}
使用自定义事件:
<app-hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></app-hero-detail>
双向绑定
对于需要显示数据属性,并在用户作出更改时更新该属性如何处理呢?
在元素层面上,既要设置元素属性,又要监听元素事件变化。Angular 为此提供一种特殊的双向数据绑定语法:[(x)]
。 [(x)]
语法结合了属性绑定的方括号 [x]
和事件绑定的圆括号 (x)
。
<input [(ngModel)]="name">
Attribute
attribute 绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而是由attr前缀,一个点 (.) 和 attribute 的名字组成。
<button [attr.aria-label]="help">help</button>
CSS 类
借助 CSS 类绑定,可以从元素的 class attribute 上添加和移除 CSS 类名。
<div [class.special]="isSpecial">Special</div>
样式
样式绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而由style前缀,一个点 (.)和 CSS 样式的属性名组成。 形如:[style.style-property]。
<button [style.color]="isSpecial ? 'red' : 'green'">
模板引用变量 ( #var )
模板引用变量通常用来引用模板中的某个 DOM 元素,它还可以引用 Angular 组件或指令或Web Component。
使用井号 (#
) 来声明引用变量。 #phone
的意思就是声明一个名叫 phone 的变量来引用 <input>
元素。
<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 管道对像这样的小型转换来说是个明智的选择。 管道是一个简单的函数,它接受一个输入值,并返回转换结果。 它们很容易用于模板表达式中,只要使用管道操作符 (|) 就行了。
<div>Title through uppercase pipe: {{title | uppercase}}</div>
安全导航操作符 ( ?. )
和空属性路径
Angular 的安全导航操作符 (?.) 是一种流畅而便利的方式,用来保护出现在属性路径中 null 和 undefined 值。 下例中,当 currentHero 为空时,保护视图渲染器,让它免于失败。
The current hero's name is {{currentHero?.name}}
当绑定中 title 属性为空,仍然会继续渲染
非空断言操作符(!)
在 TypeScript 2.0 中,你可以使用 --strictNullChecks 标志强制开启严格空值检查。TypeScript 就会确保不存在意料之外的 null 或 undefined。
在这种模式下,有类型的变量默认是不允许 null 或 undefined 值的,如果有未赋值的变量,或者试图把 null 或 undefined 赋值给不允许为空的变量,类型检查器就会抛出一个错误
Angular 模板中的**
非空断言操作符(!)
也是同样的用途。
<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() 方法
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 装饰器。
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。
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
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 的变化,并把这些变化编写成日志以报告这些变化
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 装饰器
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 表示)传给事件处理方法:
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 元数据中。例子如下:
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
6.属性指令
在 Angular 中有三种类型的指令:
- 组件 — 拥有模板的指令
- 结构型指令 — 通过添加和移除 DOM 元素改变 DOM 布局的指令
- 属性型指令 — 改变元素、组件或其它指令的外观和行为的指令。
创建属性指令:
ng generate directive highlight
编写代码,这个指令实现高亮元素:
import { Directive, ElementRef } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'yellow';
}
}
通过ElementRef访问目标元素。
使用属性型指令
<p appHighlight>Highlight me!</p>
使用 @Input 数据绑定向指令传递值
@Input() highlightColor: string;
<p appHighlight highlightColor="yellow">Highlighted in yellow</p>
<p appHighlight [highlightColor]="'orange'">Highlighted in orange</p>
绑定到 @Input 别名
可以使用Input的别名
@Input('appHighlight') highlightColor: string;
<p [appHighlight]="color">Highlight me!</p>
作者:Jadepeng
出处:jqpeng的技术记事本--http://www.cnblogs.com/xiaoqi
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。