学习任务
Angular基础学习。深入组件。
学习目标
理解组件生命周期、理解父子组件通信和运行过程、理解变更检测机制和相关应用
1.基础组件的学习
input实现双向数据绑定:
<li>姓 名:<input type="text" id="username" [(ngModel)]="people.username"></li>
在TS文件中创建一个对象people,其中有一个属性叫做username
比如这里的利用ngmodel实现对对象属性的绑定,而后将在MVVM过程中保持属性内容的一致
radio单选实现双向数据绑定:
<input type="radio" value="1" name='sex' id="sex1" [(ngModel)]="people.sex"><label for="sex1">男</label>
<input type="radio" value="2" name='sex' id="sex2" [(ngModel)]="people.sex"><label for="sex2">女</label>
其中input选择的是radio单选的情况下,要注意设置不同选项对应的id,便于属性的区分,在这里首先会获取设置的sex值,但是当你点击也就是值改变之后就会通过ngmode自动的更新到对象的属性
select实现双向数据绑定:
<select name="city" id="city" [(ngModel)]="people.city">
<!-- 用select进行双向数据的绑定 -->
<option value="{{item}}" *ngFor="let item of people.cityList">{{item}}</option>
</select>
注意这里的ngModel是在select中进行的绑定,绑定到people.city属性
option通过ngfor展示所有的选项,比较方便.
checkbox多选实现双向数据绑定:
<span *ngFor="let item of people.hobby; let key = index">
<input type='checkbox' [id]="'check'+key" [(ngModel)] = 'item.check'><label [for]="'check'+key">{{item.title}}</label>
</span>
对应着的input中的checkbox要进行id的绑定比较困难,通过拼接的方式加上它的下标
textarea实现双向数据绑定:
<textarea name="mark" id="mark" cols="30" rows="4" [(ngModel)]="people.mark"></textarea>
testarea的双向绑定,类似于普通text,绑定对象的属性名称就可以
数据绑定的例子运行结果展示
2.组件生命周期
生命周期函数通俗的将就是组件创建、组件更新、组件销毁的时候会触发的一系列方法。
当angular使用构造函数创建一个组件指令后,就会按下面的顺序在特定的时刻调用这些生命周期钩子方法。
constructor | 构造函数除了使用简单的对局部变量进行初始化之外,不进行其他工作 |
ngOnchanges() | 当angular(重新)设置数据绑定输入属性时相应。该方法接受当前和上一属性值的simpleChanges对象,当被绑定的属性的值发生变化时调用,首次调用一定发生在ngInit()之前 |
ngOninit() | 在angular第一次显示数据绑定和是指指令/组件的输入属性之后,初始化指令/组件。 在第一轮onchanges()完成之后调用,只调用一次 使用原因: 1.在构造函数之后马上执行复杂的初始化逻辑 2.在angular设置完输入属性之后,对该组件重新设置输入属性 |
ngDoCheck() | 检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应 |
ngAfterContentInit() | 当 Angular 把外部内容投影进组件视图或指令所在的视图之后调用。 |
ngAfterContentChecked() | 每当 Angular 检查完被投影到组件或指令中的内容之后调用。 |
ngAfterViewInit() | 当 Angular 初始化完组件视图及其子视图或包含该指令的视图之后调用。 |
ngAfterViewChecked() | 每当 Angular 做完组件视图和子视图或包含该指令的视图的变更检测之后调用 |
ngOnDestroy() | 每当 Angular 每次销毁指令/组件之前调用并清扫。在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。 |
一般情况下(默认)执行的生命周期函数:
用一个测试用例,只是实现了机几个常见的生命周期函数,通过控制台观察他们的输出来检测生命周期函数执行的顺序。
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-lifycycle',
templateUrl: './lifycycle.component.html',
styleUrls: ['./lifycycle.component.css']
})
export class LifycycleComponent implements OnInit {
public msg:string = "声明周期演示";
constructor() {
console.log("00构造函数执行了--除了使用简单的值对局部变量进行初始化之外,什么都不做");
}
ngOnChanges() {
console.log("01ngOnchange执行了--当被绑定的输入属性的值发生变化的时候会执行(父子组件传值的时候会触发)");
}
ngOnInit() {
console.log("02ngOnInit执行了--请求数据一般放在这里");
}
ngDoCheck() {
//Called every time that the input properties of a component or a directive are checked. Use it to extend change detection by performing a custom check.
//Add 'implements DoCheck' to the class.
console.log("03ngDoCheck执行了--检测,并在发生angular无法或不愿意自己检测的变化时做出反应");
}
ngAfterContentInit(): void {
console.log("04ngAfterContentInit执行了--当把内同投影进组件之后调用");
}
ngAfterContentChecked(): void {
//Called after every check of the component's or directive's content.
//Add 'implements AfterContentChecked' to the class.
console.log("05ngAfterContentChecked执行了--每次完成被投影组件内容的变化之后调用");
}
ngAfterViewInit(): void {
//Called after ngAfterContentInit when the component's view has been initialized. Applies to components only.
//Add 'implements AfterViewInit' to the class.
console.log("06ngafterView执行了--咋初始化完成组件视图及其子视图之后调用");
console.log("DOM 操作在此");
}
ngAfterViewChecked(): void {
//Called after every check of the component's view. Applies to components only.
//Add 'implements AfterViewChecked' to the class.
console.log("07ngafteViewchecked执行了---每次昨晚组件视图和子视图的变更检测之后调用");
}
ngOnDestroy(): void {
//Called once, before the instance is destroyed.
//Add 'implements OnDestroy' to the class.
console.log("08ngOnDestory执行了--");
}
}
按照先构造函数,然后初始化,检测,内容投影后初始化,内筒投影后检测,初始化组件视图后,组件视图变更后检测的顺序。
添加一个自定义的bottom去调用自定义的函数getMsg(),会执行的生命周期函数:
同样的生命周期函数执行顺序会发生一个双向数据绑定的时候
TIPS:在双向数据绑定的时候,一定要在app.module中引入FormsModule,在import中也要引入
如果改变父组件的数据,就会额外的触发一个生命周期函数
ngOnchange()
卸载组件的生命周期函数 destory(),将在删除一个组件的时候执行
3.父子组件的通信
特点:
父组件给子组件传值
1.父组件给子组件传值 [title] = 'title'
2.子组件可以执行父组件的方法
子组件给父组件传值
1.父组件可以获取子组件的数据($event)
2.父组件可以获取子组件的方法
非父子组件:
1.组件之间传值
2.共享方法
为什么要使用父子组件:
比如这里的首页和新闻,他们有一个共同的头部,但是被两个不同的子组件引用之后就会呈现不同的效果,这样就是父组件给子组件传值,从而实现不同的效果
如果有一个商品组件,它拥有一个子组件购物车,那么在你购物的时候就需要自动的调用子组件。
父组件给子组件传值 @input
父组件不仅可以给子组件传递简单的数据,还可以把自己的方法以及整的父组件传给子组件
步骤:
1.父组件调用子组件的时候传入数据
<app-header [msg]='msg'> </app-header>
2.子组件引入input模块
import { component,Oninit,Input } from "@angulatr/core'
3.子组件中@input接收父组件传过来的数据
@input () msg:string
TIPS:父组件还可以直接将整个组件都传输给子组件。采取的步骤是
1.父子间调用子组件的时候传输数据
2.在子组件中引入input模块
3.父组件的HTML中进行数据的传输。
<app-header [title]="title" [msg]="msg" [run]="run" [home]='this'></app-header>
这里的this就指代了整个home组件,像传数据一样的方式传输给子组件
子组件给父组件传输值:(通过output和eventemit广播)
1.在子组件中引入output和eventemit(事件驱动) ,因为是从子组件中传出去,所以理解这个主从关系
2.在子组件中通过@output装饰器声明一个 public或者private访问类型的变量,
@output public outer =new eventemit();
3.在子组件中编写函数,用于传输数据
4.在父组件中的HTML中
<app-footer #footer (outer)=run()></app-footer>
当父组件中监听到outer的数据时候,就会触发自己的run()方法
如果run($event),这样就可以接收到子组件给父组件广播的数据
Angular变更检测;
1.什么是变更检测?
答:变更检测的基本任务是获得程序的内部状态并使其在用户界面可见,这个状态可以是任何的对象、数据类型、也可以是任意的JavaScript数据结构
这个状态在用户界面上最终可能成为段落、表格、连接或者按钮等,并且特别对于web而言,会成为DOM,所以基本上我们将数据结构作为输入,并生成DOM作为输出展现给用户,此过程称之为渲染。但是党变更发生在runtime的时候,也就是DOM渲染已经完成的时候,饿哦们要知道model中发生了什么改变以及哪里的DOM发生了更新是十分困难的。所以就有了检测变更的许多方法
2.什么引起了变更检测?
比如点击了HTML渲染后界面上的一个按钮,而这个按钮关联的函数改变了其他属性或数据,这个按钮被点击的时候就是程序状态发生改变的时候,因为它改变了这个组件的属性,这就是需要更新视图(view)的时候。
3,谁去通知angular更新?
当我们知道会导致程序状态的改变的时候,又是谁去通知angular做出改变的呢?事实上,angular有着自己的zone,称之为Ngzone。在这地方,有一个东西叫做ApplicationRef,它监听NgZone的onTurnDone事件,只要这个事件发生了,它就执行tick()函数,这个函数执行变更检测。
TIPS:这一段参考了[译] 深入分析 Angular 变更检测_weixin_34148456的博客-CSDN博客
的译文,原作者是Pascal Precht。
变更检测:
每一个组件都有它自己的change detector(变更检测器,这可以让我们单独的控制每个组件的变更检测何时发生以及如何执行。
更聪明的变更检测
从上面的介绍我们知道,一旦event发生,angular必须每次都检测所有的组件,所以为了尽量少的检测变更,可以通过Immutables和Observables来减少变更检测次数。
变更检测的应用:
1.减少检测的次数以提高性能
当一些输入属性没有发生改变的时候可以跳过整个组件子树的变更检测,从而减少资源的浪费,加快渲染的速度。