目录
组件
typora-copy-images-to: media
组件的定义
组件的模板
组件通信
父子通信:Input Down
父子通信:Output Up
父子通信:父组件直接访问子组件 public 成员
没有直接关系通信:Service 单例
利用 Session 进行通信
小结
组件生命周期
动态组件
组件内容分发
第三方组件库
模板语法
插值
文本绑定
属性绑定
使用 JavaScript 表达式
列表渲染
条件渲染
NgIf
NgSwitch
事件处理
表单输入绑定
文本
多行文本
复选框
Class 与 Style 绑定
Class
NgClass 指令
Style
NgStyle 指令
Angular 中的计算属性
模板引用变量
过滤器
总结
几乎所有前端框架都在玩“组件化”,而且最近都不约而同地选择了“标签化”这种思路,Angular 也不例外。
对新版本的 Angular 来说,一切都是围绕着“组件化”展开的,组件是 Angular 的核心概念模型。
以下是一个最简单的 Angular 组件定义:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'itcast';
}
@Component
:这是一个 Decorator(装饰器),其作用类似于 Java 里面的注解。Decorator 这个语言特性目前(2017-10)处于 Stage 2(草稿)状态,还不是 ECMA 的正式规范。selector
:组件的标签名,外部使用者可以这样来使用这个组件:。默认情况下,ng 命令生成出来的组件都会带上一个 app 前缀,如果你不喜欢,可以在 angular-cli.json 里面修改 prefix 配置项,设置为空字符串将会不带任何前缀。templateUrl
:引用外部的 HTML 模板。如果你想直接编写内联模板,可以使用 template,支持 ES6 引入的“模板字符串”写法。styleUrls
:引用外部 CSS 样式文件,这是一个数组,也就意味着可以引用多份 CSS 文件。export class AppComponent
:这是 ES6 里面引入的模块和 class 定义方式。你可以在两种地方存放组件模板。 你可以使用template
属性把它定义为内联的,或者把模板定义在一个独立的 HTML 文件中, 再通过@Component
装饰器中的templateUrl
属性, 在组件元数据中把它链接到组件。
无论用哪种风格,模板数据绑定在访问组件属性方面都是完全一样的。
具体模板语法请参考:模板语法
参考官方文档:https://angular.io/guide/component-interaction
组件就像零散的积木,我们需要把这些积木按照一定的规则拼装起来,而且要让它们互相之间能进行通讯,这样才能构成一个有机的完整系统。
在真实的应用中,组件最终会构成树形结构,就像人类社会中的家族树一样:
在树形结构里面,组件之间有几种典型的关系:父子关系、兄弟关系、没有直接关系。
相应地,组件之间有以下几种典型的通讯方案:
无论你使用什么前端框架,组件之间的通讯都离开不以上几种方案,这些方案与具体框架无关。
参考文档:https://angular.io/guide/component-interaction#pass-data-from-parent-to-child-with-input-binding
子组件在内部声明 @Input
接收
Input 是单向的
下面是一个示例:
子组件:
import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: 'app-hero-child',
template: `
{{hero.name}} says:
I, {{hero.name}}, am at your service, {{masterName}}.
`
})
export class HeroChildComponent {
// 声明接收父组件传递的数据
@Input() hero: Hero;
@Input('master') masterName: string; // 接收 master 重命名为 masterName
}
父组件:
import { Component } from '@angular/core';
import { HEROES } from './hero';
@Component({
selector: 'app-hero-parent',
template: `
{{master}} controls {{heroes.length}} heroes
`
})
export class HeroParentComponent {
heroes = HEROES;
master = 'Master';
}
参考文档:https://angular.io/guide/component-interaction#parent-listens-for-child-event
@Output
的本质是事件机制,我们可以利用它来订阅子组件上发布的事件,子组件上这样写:
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-voter',
template: `
{{name}}
`
})
export class VoterComponent {
@Input() name: string;
@Output() onVoted = new EventEmitter();
voted = false;
vote(agreed: boolean) {
this.onVoted.emit(agreed); // 传递的数据就是事件对象
this.voted = true;
}
}
在父组件中订阅处理:
import { Component } from '@angular/core';
@Component({
selector: 'app-vote-taker',
template: `
Should mankind colonize the Universe?
Agree: {{agreed}}, Disagree: {{disagreed}}
`
})
export class VoteTakerComponent {
agreed = 0;
disagreed = 0;
voters = ['Mr. IQ', 'Ms. Universe', 'Bombasto'];
onVoted(agreed: boolean) {
agreed ? this.agreed++ : this.disagreed++;
}
}
参考文档:https://angular.io/guide/component-interaction#parent-interacts-with-child-via-local-variable
对于有直接父子关系的组件,父组件可以直接访问子组件里面 public 型的属性和方法,示例代码片段如下:
显然,子组件里面必须暴露一个 public 型的 childFn 方法,就像这样:
export class FooComponent implements OnInit {
public message: string = 'foo message'
public count: number = 0
constructor() { }
public increment (): void {
this.count++
}
ngOnInit() {
}
}
以上是通过在模板里面定义局部变量的方式来直接调用子组件里面的 public 型方法。在父组件的内部也可以访问到子组件的实例,需要利用到 @ViewChild 装饰器,示例如下:
@ViewChild(ChildComponent)
private childComponent: ChildComponent;
关于 @ViewChild 在后面的内容里面会有更详细的解释。
很明显,如果父组件直接访问子组件,那么两个组件之间的关系就被固定死了。父子两个组件紧密依赖,谁也离不开谁,也就都不能单独使用了。所以,除非你知道自己在做什么,最好不要直接在父组件里面直接访问子组件上的属性和方法,以免未来一改一大片。
参考文档:https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service
组件间的通讯方案是通用的,无论你使用什么样的前端框架,都会面临这个问题,而解决的方案无外乎本文所列出的几种。
参考文档:https://angular.io/guide/lifecycle-hooks
参考文档:https://angular.io/guide/dynamic-component-loader
注意:用代码动态创建组件这种方式在一般的业务开发里面不常用,而且可能存在一些隐藏的坑,如果你一定要用,请小心避雷。
Message: {{ msg }}
在布尔特性的情况下,它们的存在即暗示为 true
,属性绑定工作起来略有不同,在这个例子中:
如果 isButtonDisabled
的值是 null
、undefined
或 false
,则 disabled
特性甚至不会被包含在渲染出来的 元素中。
1 + 1 = {{ 1 + 1 }}
{{ num + 1 }}
{{ isDone ? '完了' : '没完' }}
{{ title.split('').reverse().join('') }}
{{ title }}
-
{{ t.title }}
编写模板表达式所用的语言看起来很像 JavaScript。 很多 JavaScript 表达式也是合法的模板表达式,但不是全部。
Angular 遵循轻逻辑的设计思路,所以在模板引擎中不能编写非常复杂的 JavaScript 表达式,这里有一些约定:
=
, +=
, -=
, ...)new
运算符;
或 ,
的链式表达式++
和--
)基本用法:
export class AppComponent {
heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
}
Heroes:
-
{{ hero }}
也可以获取 index 索引:
{{i + 1}} - {{hero.name}}
3">There are many heroes!
ngIf
和
...
NgSwitch 的语法比较啰嗦,使用频率小一些。
事件绑定只需要用圆括号把事件名包起来即可:
可以把事件对象传递到事件处理函数中:
也可以传递额外的参数(取决于你的业务):
当事件处理语句比较简单的时候,我们可以内联事件处理语句:
我们可以利用 属性绑定 + 事件处理 的方式实现表单文本框双向绑定:
事件绑定的另一种写法,使用 on-
前缀的方式:
{{ message }}
运行之后你会发现报错了,原因是使用 ngModel
必须导入 FormsModule
并把它添加到 Angular 模块的 imports
列表中。
导入 FormsModule
并让 [(ngModel)]
可用的代码如下:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
+++ import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
+++ FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
通过以上的配置之后,你就可以开心的在 Angular 中使用双向数据绑定了?。
单选框
###单选按钮
男
女
选中了:{{ gender }}
###下拉列表
选中的爱好:{{ hobby }}
Bad curly special
Bad curly
The class binding is special
This one is not so special
NgClass
指令接收一个对象,对象的 key
指定 css 类名,value 给定一个布尔值,当布尔值为真则作用该类名,当布尔值为假则移除给类名。
语法规则:
测试文本
基本示例:
.saveable{
font-size: 18px;
}
.modified {
font-weight: bold;
}
.special{
background-color: #ff3300;
}
currentClasses: {};
setCurrentClasses() {
// CSS classes: added/removed per current state of component properties
this.currentClasses = {
'saveable': this.canSave,
'modified': !this.isUnchanged,
'special': this.isSpecial
};
}
This div is initially saveable, unchanged, and special
通过样式绑定,可以设置内联样式。
样式绑定的语法与属性绑定类似。 但方括号中的部分不是元素的属性名,而由style前缀,一个点 (.
)和 CSS 样式的属性名组成。 形如:[style.style-property]
。
有些样式绑定中的样式带有单位。在这里,以根据条件用 “em” 和 “%” 来设置字体大小的单位。
提示:样式属性命名方法可以用中线命名法,像上面的一样 也可以用驼峰式命名法,如
fontSize
。
虽然这是设置单一样式的好办法,但我们通常更喜欢使用 NgStyle指令 来同时设置多个内联样式。
currentStyles: {};
setCurrentStyles() {
// CSS styles: set per current state of component properties
this.currentStyles = {
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '24px' : '12px'
};
}
This div is initially italic, normal weight, and extra large (24px).
ngStyle 这种方式相当于在代码里面写 CSS 样式,比较丑陋,违反了注意点分离的原则,而且将来不太好修改,非常不建议这样写(足够简单的情况除外)。
模板引用变量通常用来引用模板中的某个DOM元素,它还可以引用Angular组件或指令或Web Component。
使用井号 (#) 来声明引用变量。 #phone
的意思就是声明一个名叫phone
的变量来引用元素。
我们可以在模板中的任何地方引用模板引用变量。 比如声明在上的
phone
变量就是在模板另一侧的上使用的。
大多数情况下,Angular会把模板引用变量的值设置为声明它的那个元素。在上面例子中,
phone
引用的是表示电话号码的框。 "拨号"按钮的点击事件处理器把这个input值传给了组件的
callPhone
方法。 不过,指令也可以修改这种行为,让这个值引用到别处,比如它自身。NgForm
指令就是这么做的。
模板引用变量使用注意:
模板局部变量 > 指令中的同名变量 > 组件中的同名属性
ref-
前缀代替#
。 下面的例子中就用把fax
变量声明成了ref-fax
而不是#fax
。
在 Angular 中,过滤器也叫管道。它最重要的作用就是用来格式化数据的输出。
举个简单例子:
public currentTime: Date = new Date();
{{currentTime | date:'yyyy-MM-dd HH:mm:ss'}}
Angular 一共内置了16个过滤器:https://angular.io/api?type=pipe。
在复杂业务场景中,内置的过滤器肯定是不够用的,所有 Angular 也支持自定义过滤器。
管道还有另外一个重要的作用就是做国际化。