前言
在前端框架Angular中,组件之间的通信很根底也很重要,不同组件间的通信形式也不同,把握组件间的通信形式会更加粗浅的了解和应用Angular框架。
本文解说不同类型组件间的不同通信形式,文中所有示例均提供源码,您能够 在线编辑预览 或 下载本地调试,置信通过本文您肯定能够把握组件通信这一知识点。
父组件传子组件
@Input形式
@Input()
装璜器容许父组件更新子组件中的数据,分为4步:
第一步:在父组件app.component.ts
中定义要传递给子组件的数据parentMsg
。
export class AppComponent {
parentMsg: string = 'parent component message!';
}
第二步:在父组件app.component.html
中的子组件标签<app-child>
中定义属性[childMsg]
(子组件接收数据变量)来绑定父组件的数据parentMsg
。
<app-child [childMsg]="parentMsg"></app-child>
第三步:在子组件child.component.ts
中引入@Input()
装璜器,润饰childMsg
接管父组件的传值。
import { Input } from '@angular/core';
export class ChildComponent {
@Input() childMsg: string = '';
}
第四步:在子组件child.component.html
中通过模板标签{{childMsg}}
展现数据。
<div>父组件传值内容:{{ childMsg }}</div>
最终展现成果如下,您能够通过 在线示例 预览成果和编辑调试:
阐明:这里要了解父组件html中通过[]
定义了一个子组件的属性,该值必须与子组件中所定义的变量名统一,而等号左边的值为父组件要传递的属性名,示例中是将父组件中parentMsg
的值绑定在子组件childMsg
属性上。
子组件传父组件
@Output()形式
@Output()
装璜器容许数据从子组件传给父组件,分为6步:
第一步:在子组件child.component.ts
中引入Output
和EventEmitter
,通过@Output()
来润饰一个EventEmitter
实例的变量newItemEvent
。
import { Component, Output, EventEmitter } from '@angular/core';
export class ChildComponent {
@Output() newItemEvent = new EventEmitter<string>();
}
第二步:在子组件child.component.html
中增加点击事件,获取输出内容,点击按钮触发addNewItem()
办法。
<label>输出我的项目名:<input type="text" #newItem /></label>
<button type="button" (click)="addNewItem(newItem.value)">
增加我的项目到父组件
</button>
第三步:在子组件child.component.ts
中通过newItemEvent
的emit()
办法,把数据发送到父组件。
export class ChildComponent {
@Output() newItemEvent = new EventEmitter<string>();
addNewItem(value: string) {
this.newItemEvent.emit(value);
}
}
第四步:在父组件app.component.html
中子组件标签<app-child>
中增加父组件办法addItem($event)
绑定到子组件的newItemEvent
发射器事件上,其中$event
为子组件的传递的值。
<app-child (newItemEvent)="addItem($event)"></app-child>
第五步:在父组件app.component.ts
中通过addItem($event)
办法获取解决数据。
export class AppComponent implements AfterViewInit {
items = ['item1', 'item2', 'item3'];
addItem(newItem: string) {
this.items.push(newItem);
}
}
第六步:在父组件app.component.html
中遍历items
展现数据。
<ul>
<li *ngFor="let item of items">{{ item }}</li>
</ul>
最终展现成果如下,您能够通过 在线示例 预览成果和编辑调试:
阐明:这里要了解要害的第四步事件连贯(newItemEvent)="addItem($event)"
含意,左侧是父组件监听子组件创立的一个发射器newItemEvent
,右侧是父组件的addItem($event)
办法。子组件通过发射器的emit(value)
办法播送传递值到父组件,父组件通过addItem($event)
中的$event
接管传值,实现通信。
本地变量形式
在父组件模板里,新建一个本地变量来代表子组件,能够利用这个变量来读取子组件的属性和调用子组件的办法。分为2步:
第一步:在子组件child.component.ts
中定义count
变量和addOne()
办法。
export class ChildComponent {
count: number = 0;
addOne() {
this.count++;
}
}
第二步:在父组件app.component.html
中子组件标签<app-child>
中增加本地变量#child
,点击按钮触发点击事件,通过本地变量调用子组件办法child.addOne()
。
<app-child #child></app-child>
<button type="button" (click)="child.addOne()">加1</button>
最终展现成果如下,您能够通过 在线示例 预览成果和编辑调试:
阐明:在子组件标签中通过#+变量名
的形式新建一个本地变量代表子组件的援用。本地变量形式应用简单明了,但也有局限性,只能在模板html中应用,无奈在ts文件中应用。
@ViewChild形式
通过@ViewChild
装璜器,将子组件注入到父组件。分为4步:
第一步:在子组件child.component.ts
中定义count
变量和add()
办法。
export class ChildComponent {
count: number = 0;
add(num: number) {
this.count = this.count + num;
}
}
第二步:在父组件app.component.html
中子组件标签<app-child>
中增加标签援用#child
,点击按钮触发点击事件,执行办法add()
。
<app-child #child></app-child>
<button type="button" (click)="add(2)">加2</button>
第三步:在父组件app.component.ts
中引入ViewChild
,@viewchild
传入标签援用字符child
,由变量child
接管。除了应用标签援用child
,你也能够通过间接传入子组件ChildComponent
实现子组件的援用。
import { Component, ViewChild } from '@angular/core';
import { ChildComponent } from './child/child.component';
export class AppComponent {
// 第一种办法:传入组件援用名child
@ViewChild('child') private child: any;
// 第二种办法:传入组件实例ChildComponent
@ViewChild(ChildComponent) private child: ChildComponent;
}
第四步:在父组件app.component.ts
中办法add()
中调用子组件的add()
办法。
export class AppComponent {
add(num: number) {
this.child.add(num);
}
}
最终展现成果如下,您能够通过 在线示例 预览成果和编辑调试:
阐明:@ViewChild
的作用是申明对子组件元素的实例援用,意思是通过注入的形式将子组件注入到@ViewChild
容器中,你能够设想成依赖注入的形式注入,只不过@ViewChild
不能在结构器constructor
中注入,因为@ViewChild()
会在ngAfterViewInit()
回调函数之前执行,咱们能够测试下:
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { ChildComponent } from './child/child.component';
export class AppComponent implements AfterViewInit {
@ViewChild('child') private child: any;
constructor() {
console.log('constructor func', this.child); // undefined
}
ngAfterViewInit() {
console.log('ngAfterViewInit func', this.child);
}
}
最终展现成果如下,您能够通过 在线示例 预览成果和编辑调试:
通过打印后果咱们能够看到在构造函数constructor()
中,this.child
的值为undefined
,并没有注入到父组件,但在ngAfterViewInit()
生命周期钩子中注入胜利了。
不相干组件
对于不相关联的组件,咱们会应用其余两头媒介的形式进行通信,以下不相干组件的通信形式仍实用于父子组件。
service服务形式
组件间共享一个service服务,那么组件之间就能够通过service实现通信。
示例中咱们应用rxjs
中的BehaviorSubject
,它是Subject
的一种变体,能够存储最初一条数据或者初始默认值,并会在订阅时发送其以后值。您能够通过RxJS官网进行理解,当然通过文中的阐明,您还是能够理解其具体实现的性能。
咱们创立两个不相干的组件A
和B
,组件A
公布数据,组件B
接收数据,通过服务文件data.service.ts
进行关联实现。
在公共文件目录下创立service
服务文件data.service.ts
,代码如下:
import { Injectable } from '@angular/core';
// 1.引入
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class DataService {
// 2.创立subject
subject: BehaviorSubject<any> = new BehaviorSubject<any>(0);
constructor() {}
}
引入BehaviorSubject
,创立一个BehaviorSubject
, 默认值设为0
。 记得在app.module.ts
文件中引入公共data.service.ts
文件,并申明。
import { DataService } from './data.service';
@NgModule({
providers: [DataService],
})
export class AppModule {}
创立组件A
,用于公布数据,a.component.ts
实现代码如下:
import { Component } from '@angular/core';
// 1. 引入
import { DataService } from '../data.service';
@Component({
selector: 'app-a',
templateUrl: './a.component.html',
})
export class AComponent {
// 2. 注册
constructor(public dataService: DataService) {}
inputValue: string = '';
send(): void {
// 3. 公布
this.dataService.subject.next(this.inputValue);
}
}
引入service
文件,在constructor()
中注入服务依赖dataService
,应用服务中subject
的next()
办法公布播送数据。
创立组件B
,用于接收数据,b.component.ts
实现代码如下:
import { Component } from '@angular/core';
// 1. 引入
import { Subscription } from 'rxjs';
import { DataService } from '../data.service';
@Component({
selector: 'app-b',
templateUrl: './b.component.html',
})
export class BComponent {
data: any;
// 2. subscription
subscription: Subscription;
constructor(public dataService: DataService) {
// 3. 订阅
this.subscription = this.dataService.subject.subscribe((data) => {
this.data = data;
});
}
ngOndestry(): void {
// 4. 勾销订阅
this.subscription.unsubscribe();
}
}
引入Subscription
,应用服务中subject
的subscribe()
办法创立一个订阅者,当组件A
数据被公布后就能够接管到数据。最初在销毁时记得勾销订阅,否则会导致泄露。
最终展现成果如下,您能够通过 在线示例 预览成果和编辑调试:
阐明:示例中组件A
和B
都引入了同一个服务service
,service
服务则奇妙利用BehaviorSubject
实现数据的公布和订阅,在两个组件中进行数据的通信,是不是没有设想的那么难~
路由传参形式
路由传参有多种形式,首先咱们新建一个路由模块app-routing.module.ts
,代码如下:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';
import { DetailComponent } from './detail/detail.component';
// 配置路由
const routes: Routes = [
{ path: 'detail', component: DetailComponent },
{ path: 'detail/:id', component: DetailComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
引入路由相干的RouterModule
和Routes
,引入跳转文章详情组件DetailComponent
,配置好路由routes
,当门路为detail
或detail/:id
时,会加载DetailComponent
组件,其中:id
为占位符,能够在组件ts
文件中获取id
值。
创立好路由模块咱们还须要在根模块app.module.ts
中导入。
import { AppRoutingModule } from './app-routing.module';
@NgModule({
declarations: [...],
imports: [AppRoutingModule],
providers: [DataService],
bootstrap: [AppComponent],
})
export class AppModule {}
在app.component.html
文件中增加<router-outlet></router-outlet>
路由占位符,Angular框架会依据以后的路由器状态将不同组件动静填充它。
配置完路由筹备工作,咱们来具体看下有哪些路由传参形式。
路由门路传参
路由门路中传参,链接模式为:https://ip/detail/1
。
在app.component.html
中应用路由指令routerLink
的形式在路由门路中传参。
<a [routerLink]="['/detail',1]">
1.文章1(路由门路中传参,链接:https://ip/detail/1)
</a>
在detail
组件detail.component.ts
中应用以后路由对象ActivatedRoute
获取路由传递的参数。
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
@Component({
selector: 'app-detail',
templateUrl: './detail.component.html',
})
export class DetailComponent implements OnInit {
id: any;
constructor(private routeInfo: ActivatedRoute) {}
ngOnInit() {
// 获取路由参数办法
this.routeInfo.params.subscribe((params: Params) => {
this.id = params['id'];
});
}
}
查问参数传参
查问参数中传参,链接模式为:https://ip/detail?id=2
。
在app.component.html
中同样应用路由指令routerLink
,在queryParams
查问参数中传递数据。
<a [routerLink]="['/detail']" [queryParams]="{ id: 2 }">
2. 文章2(查问参数中传参,链接:https://ip/detail?id=2)
</a>
在detail
组件detail.component.ts
中应用以后路由对象ActivatedRoute
获取路由传递的参数。
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
@Component({
selector: 'app-detail',
templateUrl: './detail.component.html',
})
export class DetailComponent implements OnInit {
id: any;
constructor(private routeInfo: ActivatedRoute) {}
ngOnInit() {
// 获取路由参数办法(params改为queryParams)
this.routeInfo.queryParams.subscribe((params: Params) => {
this.id = params['id'];
});
}
}
仔细观察会发现,第一种路由门路中传参应用的是this.routeInfo.queryParams
获取数据,而第二种查问参数中传参应用的是this.routeInfo.queryParams
,肯定要留神这个区别。
路由配置传参
除了在app.component.html
中应用路由指令routerLink
,咱们还能够在app.component.ts
文件中通过路由配置中传参。
在app.component.html
文件中绑定两个点击办法toArticle3()
和toArticle4()
。
<a (click)="toArticle3()">
3. 文章3(路由配置中传参,链接:https://ip/detail/3)</a>
<a (click)="toArticle4()">
4. 文章4(路由配置中传参,链接:https://ip/detail?id=4)</a>
在app.component.ts
文件中通过路由Router
的navigate()
办法实现跳转。
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
constructor(private router: Router) {}
toArticle3() {
// 路由跳转文章3
this.router.navigate(['/detail', 3]);
}
toArticle4() {
// 路由跳转文章4
this.router.navigate(['/detail'], { queryParams: { id: 4 } });
}
}
尽管是通过路由配置传参跳转,但咱们依然能够发现,文章3和文章1的跳转链接统一,文章4和文章2的跳转链接统一,实质上也是路由门路传参和查问参数传参。所以在detail.component.ts
中,接管路由参数的办法是统一的。
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
@Component({
selector: 'app-detail',
templateUrl: './detail.component.html',
})
export class DetailComponent implements OnInit {
id: any;
constructor(private routeInfo: ActivatedRoute) {}
ngOnInit() {
// 文章3路由参数获取(params)
this.routeInfo.params.subscribe((params: Params) => {
this.id = params['id'];
});
// 文章4路由参数获取(queryParams)
this.routeInfo.queryParams.subscribe((params: Params) => {
this.id = params['id'];
});
}
}
最终展现成果如下,您能够通过 在线示例 预览成果和编辑调试:
这里须要阐明下,在线示例中点击文章url
并未产生扭转,这是因为stackblitz工具机制的问题,你能够点击在线示例界面的Open in New Tab
按钮,在独自页面关上可躲避该问题。
延长:示例中的获取路由参数都是应用subscribe
参数订阅的形式,还有一种snapshot
参数快照的形式。如获取文章1路由参数写法为:this.id = this.routeInfo.snapshot.params['id'];
,获取文章2路由参数写法为:this.id = this.routeInfo.snapshot.queryParams['id'];
,能够看到同样是params
与queryParams
的区别。
snapshot
参数快照和subscribe
参数订阅两者的区别在于,当路由地址不变的状况下,若参数变动,snapshot
参数快照获取的参数值不变,subscribe
参数订阅获取的参数值会变动。
咱们应用snapshot
参数快照测试一下文章1和文章3,成果如下:
那么snapshot
参数快照获取的参数为什么不发生变化了呢?这是因为第一次点击文章1跳转detail
组件,constructor()
和ngOnInit()
会被调用一次,再次点击文章3,因为detail
组件页面曾经被创立了,ngOnInit()
办法不会再次被调用,所以路由参数id
仍然保留着第一次被创立时候的值1
。
LocalStorage形式
当然你也能够应用本地存储这种比拟通用的形式在组件间通信。
创立C
组件c.component.ts
将数据存储到key
为cValue
的localStorage
中,代码如下:
import { Component } from '@angular/core';
@Component({
selector: 'app-c',
templateUrl: './c.component.html',
})
export class CComponent {
constructor() {}
inputValue: string = '';
send(): void {
// 存储数据
window.localStorage.setItem('cValue', this.inputValue);
}
}
创立D
组件d.component.ts
获取localStorage
中cValue
的值。
import { Component } from '@angular/core';
@Component({
selector: 'app-d',
templateUrl: './d.component.html',
})
export class DComponent {
data: any;
constructor() {}
getValue() {
// 获取数据
this.data = window.localStorage.getItem('cValue');
}
}
最终展现成果如下,您能够通过 在线示例 预览成果和编辑调试:
注:这里没有应用sessionStorage
存储是因为localStorage
生命周期是永恒的,而sessionStorage
生命周期仅为以后标签页,如果两个组件别离在两个标签页,那么应用sessionStorage
是无奈实现通信的。
服务端通信形式
最初一种通信形式是借助后盾传输数据,如A
组件调接口发送数据data
存储到后盾,再由B
组件调接口获取数据data
,实现数据通信,这里就不做演示了。
总结
Angular组件间的通信形式是多种多样的,对于不同情景咱们能够采纳适合的形式进行通信。
本文每个示例的重点我都有具体的阐明,并延展一些相干常识。示例都是我本人一点点亲手敲的,从0到1钻研示例实现计划,尽管破费了很长时间,但加深坚固了常识,之前疏忽的一些常识细节也失去了补充,倡议大家在学习的同时最好也能入手实现。
好啦,以上就是Angular组件间各种通信形式的所有内容,心愿对你有所帮忙,如有问题可通过我的博客https://echeverra.cn或微信公众号echeverra分割我。
你学“废”了么?
(完)
文章首发于我的博客 https://echeverra.cn/component-communication,原创文章,转载请注明出处。
欢送关注我的微信公众号 echeverra,一起学习提高!不定时会有资源和福利相送哦!