1.1 组件的输入属性
父子组件
父组件–>子组件
1
1.1
生成子组件
–> ng g component order
order.component.ts
import {Component, Input, OnInit} from '@angular/core';
@Component({
selector: 'app-order',
templateUrl: './order.component.html',
styleUrls: ['./order.component.css']
})
export class OrderComponent implements OnInit {
/*输入属性*/
@Input()/*装饰器*/
stockCode: string;
@Input()
amount: number;
constructor() {
setInterval(() => {
this.stockCode = 'Apple';
}, 3000);
}
ngOnInit() {
}
}
order.component.html
<div>
我是子组件
</div>
<div>
买{{amount}}手{{stockCode}}股票
</div>
1.2
父组件
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
stock = '';
}
app.component.html
<div>
我是父组件
</div>
<div>
<input type="text" placeholder="请输入股票代码"
[(ngModel)]="stock">
<app-order [stockCode]="stock" [amount]="100"></app-order>
</div>
1.2 组件的输出属性
1
1.1
生成子组件
–> ng g component price-quote
price-quote.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-price-quote',
templateUrl: './price-quote.component.html',
styleUrls: ['./price-quote.component.css']
})
export class PriceQuoteComponent implements OnInit {
stockCode = 'IBM';
price: number;
constructor() {
setInterval(() => {
const priceQuote: PriceQuote = new PriceQuote(this.stockCode, 100 * Math.random());
this.price = priceQuote.lastPrice;
}, 1000);
}
ngOnInit() {
}
}
export class PriceQuote {
constructor(public stockCode: string,
public lastPrice: number) {
}
}
price-quote.component.html
<div>
我是报价组件
</div>
<div>
股票代码是:{{stockCode}},股票价格是:{{price | number:'2.2-2'}}
</div>
app.component.html
<app-price-quote></app-price-quote>
1.2
price-quote.component.ts
import {Component, EventEmitter, OnInit, Output} from '@angular/core';
...
export class PriceQuoteComponent implements OnInit {
stockCode = 'IBM';
price: number;
/*输出属性*/
@Output('priceChange')
/*<PriceQuote>:发射数据的事件类型*/
lastPrice: EventEmitter<PriceQuote> = new EventEmitter();
constructor() {
setInterval(() => {
const priceQuote: PriceQuote = new PriceQuote(this.stockCode, 100 * Math.random());
this.price = priceQuote.lastPrice;
this.lastPrice.emit(priceQuote);
}, 1000);
}
ngOnInit() {
}
}
export class PriceQuote {
constructor(public stockCode: string,
public lastPrice: number) {
}
}
app.component.ts
import { Component } from '@angular/core';
import {PriceQuote} from './price-quote/price-quote.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
stock = '';
/*接收子组件*/
priceQuote: PriceQuote = new PriceQuote('', 0);
priceQuoteHandler(event: PriceQuote) {
this.priceQuote = event;
}
}
app.component.html
<app-price-quote (priceChange)="priceQuoteHandler($event)" ></app-price-quote>
<div>
这是在报价组件外部
</div>
<div>
股票代码是:{{priceQuote.stockCode}},
股票价格是{{priceQuote.lastPrice}}
</div>
2 使用中间人模式传递数据
1
price-quote.component.html
<div>
我是报价组件
</div>
<div>
股票代码是:{{stockCode}},股票价格是:{{price | number:'2.2-2'}}
</div>
<div>
<input type="button" value="立即购买" (click)="buyStock($event)">
</div>
price-quote.component.ts
@Output()
buy: EventEmitter<PriceQuote> = new EventEmitter();
buyStock(event) {
this.buy.emit(new PriceQuote(this.stockCode, this.price));
}
2
order.component.html
<div>
我是下单组件
</div>
<div>
买100手{{priceQuote.stockCode}}股票,买入价格是:{{priceQuote.lastPrice | number:'2.2-2'}}
</div>
order.component.ts
@Input()
priceQuote: PriceQuote;
3
app.component.html
<!--监听–>获得购买信息–>传给下单组件-->
<app-price-quote (buy)="buyHandler($event)" ></app-price-quote>
<app-order [priceQuote]="priceQuote"></app-order>
app.component.ts
export class AppComponent {
stock = '';
/*接收子组件*/
priceQuote: PriceQuote = new PriceQuote('', 0);
buyHandler(event: PriceQuote) {
this.priceQuote = event;
}
}
3 组件生命周期以及Angular的变化发现机制
钩子调用机制、使用场景
1
生成组件
–> ng g component life
2
life.component.ts
import {
AfterContentChecked,
AfterContentInit,
AfterViewChecked,
AfterViewInit,
Component,
DoCheck,
Input,
OnChanges,
OnDestroy,
OnInit, SimpleChanges
} from '@angular/core';
let logIndex = 1;
@Component({
selector: 'app-life',
templateUrl: './life.component.html',
styleUrls: ['./life.component.css']
})
/*实现所有的钩子接口*/
export class LifeComponent implements OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked,
AfterViewInit, AfterViewChecked, OnDestroy {
@Input()
name: string;
logIt (msg: string) {
console.log(`#${ logIndex++ } ${ msg }`);
}
constructor() {
this.logIt('name属性在constructor里的值是:' + name);
}
ngOnChanges(changes: SimpleChanges): void {
const name = changes['name'].currentValue;
this.logIt('name属性在ngOnChanges里的值是:' + name);
}
ngOnInit() {
this.logIt('ngOnInit');
}
ngDoCheck(): void {
this.logIt('ngDoCheck');
}
ngAfterContentInit(): void {
this.logIt('ngAfterContentInit');
}
ngAfterContentChecked(): void {
this.logIt('ngAfterContentChecked');
}
ngAfterViewInit(): void {
this.logIt('ngAfterViewInit');
}
ngAfterViewChecked(): void {
this.logIt('ngAfterViewChecked');
}
ngOnDestroy(): void {
this.logIt('ngOnDestroy');
}
}
app.component.ts
export class AppComponent {
title = 'Tom';
}
app.component.html
<app-life [name]="title"></app-life>
ngOnChanges
父组件初始化、修改子组件参数时调用
可变:对象.属性—内容
var user = {name: “Tom”};
user.name = “Jerry”;
不可变:字符串—地址
var greeting = “Hello”;
greeting = “HelloWorld”;
1.生成组件
–> ng g component child
2.子组件
child.component.ts
import {Component, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit, OnChanges{
@Input()
/**/
greeting: string;
@Input()
user: {name: string};
message = '初始化信息';
constructor() { }
ngOnInit() {
}
ngOnChanges(changes: SimpleChanges): void {
console.log(JSON.stringify(changes, null, 2));
}
}
child.component.html
<div class="child">
<h2>我是子组件</h2>
<div>问候语:{{greeting}}</div>
<div>姓名:{{user.name}}</div>
<div>消息:<input [(ngModel)]="message"></div>
</div>
3.父组件
app.component.ts
export class AppComponent {
greeting = 'Hello';
user = {name: 'Tome'};
}
app.component.html
<div class="parent">
<h2>我是父组件</h2>
<div>
问候语:<input type="text" [(ngModel)]="greeting">
</div>
<div>
姓名:<input type="text" [(ngModel)]="user.name">
</div>
<app-child [greeting]="greeting" [user]="user"></app-child>
</div>
4.效果图
console.log:父组件初始化、修改输入装饰器装饰的属性
修改父组件问候语输入框
修改父组件姓名输入框
修改子组件消息输入框
变更检测机制
package.json–>dependencies–>zone.js
zone.js
:保证组件的属性的变化和页面的变化同步
浏览器中发生异步事件触发检测:点击、输入、、、、
变更检测策略:
子组件变化,其父组件下所有,除OnPush
DoCheck
child.component.ts
import {Component, DoCheck, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit, OnChanges, DoCheck {
@Input()
/**/
greeting: string;
@Input()
user: {name: string};
message = '初始化信息';
oldUsername: string;
changeDetected = false;
noChangeCount = 0;
constructor() { }
ngOnInit() {
}
ngOnChanges(changes: SimpleChanges): void {
console.log(JSON.stringify(changes, null, 2));
}
ngDoCheck(): void {
if (this.user.name !== this.oldUsername) {
this.changeDetected = true;
console.log('DoCheck:user.name Before:' + this.oldUsername + ';After:' + this.user.name);
this.oldUsername = this.user.name;
}
if (this.changeDetected) {
this.noChangeCount = 0;
} else {
this.noChangeCount = this.noChangeCount + 1;
console.log('DoCheck:user.name没变化时,ngDoCheck方法已经被调用' + this.noChangeCount);
}
this.changeDetected = false;
}
}
view
1.–> ng g component childTwo
child-two.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-child-two',
templateUrl: './child-two.component.html',
styleUrls: ['./child-two.component.css']
})
export class ChildTwoComponent implements OnInit {
constructor() { }
ngOnInit() {
}
greeting (name: string) {
console.log('hello:' + name);
}
}
1.在父组件控制器用typescript代码,装饰器 @ViewChild('child1')
app.component.html
<app-child-two #child1></app-child-two>
app.component.ts
import {Component, OnInit, ViewChild} from '@angular/core';
import {ChildTwoComponent} from './child-two/child-two.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
/*装饰器,获得子组件的引用*/
@ViewChild('child1')
child1: ChildTwoComponent;
constructor () {}
ngOnInit (): void {
this.child1.greeting('Tom');
}
}
2.在父组件的模板中调用子组件的方法
app.component.html
<app-child-two #child2></app-child-two>
<button (click)="child2.greeting('Jerry')">调用child2的greeting方法</button>
3.观察
app.component.ts
import {AfterViewChecked, AfterViewInit, Component, OnInit, ViewChild} from '@angular/core';
import {ChildTwoComponent} from './child-two/child-two.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, AfterViewInit, AfterViewChecked {
/*装饰器,获得子组件的引用*/
@ViewChild('child1')
child1: ChildTwoComponent;
constructor () {}
ngOnInit (): void {
setInterval(() => {
this.child1.greeting('Tom');
}, 5000);
}
ngAfterViewInit(): void {
console.log('父组件的视图初始化完毕');
}
ngAfterViewChecked(): void {
console.log('父组件的视图变更检测完毕');
}
}
child-two.component.ts
import {AfterViewChecked, AfterViewInit, Component, OnInit} from '@angular/core';
@Component({
selector: 'app-child-two',
templateUrl: './child-two.component.html',
styleUrls: ['./child-two.component.css']
})
export class ChildTwoComponent implements OnInit, AfterViewInit, AfterViewChecked {
constructor() { }
ngOnInit() {
}
greeting (name: string) {
console.log('hello:' + name);
}
ngAfterViewInit(): void {
console.log('子组件的视图初始化完毕');
}
ngAfterViewChecked(): void {
console.log('子组件的视图变更检测完毕');
}
}
AppComponent.html:36 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'null: undefined'. Current value: 'null: SYS'.
问题:禁止视图被组装好后再次更新视图
app.component.ts
import {AfterViewChecked, AfterViewInit, Component, OnInit, ViewChild} from '@angular/core';
import {ChildTwoComponent} from './child-two/child-two.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, AfterViewInit, AfterViewChecked {
message: string;
constructor () {}
ngAfterViewInit(): void {
console.log('父组件的视图初始化完毕');
this.message = 'SYS';
}
ngAfterViewChecked(): void {
console.log('父组件的视图变更检测完毕');
}
}
app.component.html
{{message}}
解决:换个javascript运行周期中运行
修改app.component.ts
import {AfterViewChecked, AfterViewInit, Component, OnInit, ViewChild} from '@angular/core';
import {ChildTwoComponent} from './child-two/child-two.component';
...
ngAfterViewInit(): void {
console.log('父组件的视图初始化完毕');
setTimeout(() => {
this.message = 'SYS';
}, 0);
}
...
投影
- –> ng g component projection
- projection.component.html
<div class="wrapper">
<h2>我是子组件</h2>
<div>这个div定义在子组件中</div>
<!--ng-content指令:标记投影点-->
<ng-content></ng-content>
</div>
- app.component.html
<div class="wrapper">
<h2>我是父组件</h2>
<div>这个div定义在父组件中</div>
<app-projection>
<div>这个div是父组件投影到子组件的</div>
</app-projection>
</div>
2. projection.component.html
<div class="wrapper">
<h2>我是子组件</h2>
<ng-content select=".header"></ng-content>
<div>这个div定义在子组件中</div>
<ng-content select=".footer"></ng-content>
</div>
- app.component.html
<div class="wrapper">
<h2>我是父组件</h2>
<div>这个div定义在父组件中</div>
<app-projection>
/*注:{{插值表达式只能用父组件属性}}*/
<div class="header">页头,这个div是父组件投影到子组件的,title是{{title}}</div>
<div class="footer">页脚,这个div是父组件投影到子组件的</div>
</app-projection>
</div>
app.component.html
<!--innerHTML:浏览器特定API,只能在浏览器用-->
<div [innerHTML]="divContent"></div>
app.component.ts
divContent = '<div>慕课网</div>';
ngContent
projection.component.ts
import {AfterContentChecked, AfterContentInit, Component, OnInit} from '@angular/core';
@Component({
selector: 'app-projection',
templateUrl: './projection.component.html',
styleUrls: ['./projection.component.css']
})
export class ProjectionComponent implements OnInit, AfterContentInit, AfterContentChecked {
constructor() { }
ngOnInit() {
}
ngAfterContentInit(): void {
console.log('子组件的投影内容初始化完毕');
}
ngAfterContentChecked(): void {
console.log('子组件的投影内容变更检测完毕');
}
}
app.component.ts
import {
AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, Component, OnInit,
ViewChild
} from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, AfterContentInit, AfterContentChecked, AfterViewInit {
ngAfterContentInit(): void {
console.log('父组件的投影内容初始化完毕');
}
ngAfterContentChecked(): void {
console.log('父组件的投影内容变更检测完毕');
}
ngAfterViewInit(): void {
console.log('父组件的视图初始化完毕');
}
}
ngOnDestory
–> ng g component destory
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
...
/*1.路由配置*/
const routerConfig: Routes = [
{path: '', component: ProjectionComponent},
{path: 'destory', component: DestoryComponent}
]
@NgModule({
...
imports: [
BrowserModule,
FormsModule,
/*2.声明,把路由配置导入模块*/
RouterModule.forRoot(routerConfig)
],
...
})
export class AppModule { }
app.component.html
<a [routerLink]="['/']">projection</a>
<a [routerLink]="['/destory']">destory</a>
<!--插座-->
<router-outlet></router-outlet>
projection.component.html
<div>projection</div>
projection.component.ts
import {AfterContentChecked, AfterContentInit, Component, OnDestroy, OnInit} from '@angular/core';
@Component({
selector: 'app-projection',
templateUrl: './projection.component.html',
styleUrls: ['./projection.component.css']
})
export class ProjectionComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
console.log('projection组件被销毁');
}
constructor() { }
ngOnInit() {
}
}
destory.component.ts
<div>destory</div>
destory.component.ts
import {Component, OnDestroy, OnInit} from '@angular/core';
@Component(
selector: 'app-destory',
templateUrl: './destory.component.html',
styleUrls: ['./destory.component.css']
})
export class DestoryComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
console.log('destory组件被销毁');
}
constructor() { }
ngOnInit() {
}
}