首页IT科技angular $on(玩转Angular系列:组件间各种通信方式详解)

angular $on(玩转Angular系列:组件间各种通信方式详解)

时间2025-05-05 22:39:15分类IT科技浏览3321
导读:前言 在前端框架Angular中,组件之间的通信很基础也很重要,不同组件间的通信方式也不同,掌握组件间的通信方式会更加深刻的理解和使用Angular框架。...

前言

在前端框架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          ,一起学习进步!不定时会有资源和福利相送哦!

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
SEO优化的外链建设技巧(打造高质量的外链,提升网站排名)