玩转 Angular 系列:组件间各种通信方式详解
前言
在前端框架 Angular 中,组件之间的通信很基础也很重要,不同组件间的通信方式也不同,掌握组件间的通信方式会更加深刻的理解和使用 Angular 框架。
本文讲解不同类型组件间的不同通信方式,文中所有示例均提供源码,您可以 在线编辑预览 或 下载本地调试,相信通过本文您一定可以掌握组件通信这一知识点。
父组件传子组件
@Input 方式
@Input()
装饰器允许父组件更新子组件中的数据,分为 4 步:
第一步:在父组件app.component.ts
中定义要传递给子组件的数据parentMsg
。
第二步:在父组件app.component.html
中的子组件标签<app-child>
中定义属性[childMsg]
(子组件接收数据变量)来绑定父组件的数据parentMsg
。
第三步:在子组件child.component.ts
中引入@Input()
装饰器,修饰childMsg
接收父组件的传值。
第四步:在子组件child.component.html
中通过模板标签{{childMsg}}
展示数据。
最终展示效果如下,您可以通过 在线示例 预览效果和编辑调试:
说明:这里要理解父组件 html 中通过[]
定义了一个子组件的属性,该值必须与子组件中所定义的变量名一致,而等号右边的值为父组件要传递的属性名,示例中是将父组件中parentMsg
的值绑定在子组件childMsg
属性上。
子组件传父组件
@Output()方式
@Output()
装饰器允许数据从子组件传给父组件,分为 6 步:
第一步:在子组件child.component.ts
中引入Output
和EventEmitter
,通过@Output()
来修饰一个EventEmitter
实例的变量newItemEvent
。
第二步:在子组件child.component.html
中添加点击事件,获取输入内容,点击按钮触发addNewItem()
方法。
第三步:在子组件child.component.ts
中通过newItemEvent
的emit()
方法,把数据发送到父组件。
第四步:在父组件app.component.html
中子组件标签<app-child>
中添加父组件方法addItem($event)
绑定到子组件的newItemEvent
发射器事件上,其中$event
为子组件的传递的值。
第五步:在父组件app.component.ts
中通过addItem($event)
方法获取处理数据。
第六步:在父组件app.component.html
中遍历items
展示数据。
最终展示效果如下,您可以通过 在线示例 预览效果和编辑调试:
说明:这里要理解关键的第四步事件连接(newItemEvent)="addItem($event)"
含义,左侧是父组件监听子组件创建的一个发射器newItemEvent
,右侧是父组件的addItem($event)
方法。子组件通过发射器的emit(value)
方法广播传递值到父组件,父组件通过addItem($event)
中的$event
接收传值,完成通信。
本地变量方式
在父组件模板里,新建一个本地变量来代表子组件,可以利用这个变量来读取子组件的属性和调用子组件的方法。分为 2 步:
第一步:在子组件child.component.ts
中定义count
变量和addOne()
方法。
第二步:在父组件app.component.html
中子组件标签<app-child>
中添加本地变量#child
,点击按钮触发点击事件,通过本地变量调用子组件方法child.addOne()
。
最终展示效果如下,您可以通过 在线示例 预览效果和编辑调试:
说明:在子组件标签中通过#+变量名
的方式新建一个本地变量代表子组件的引用。本地变量方式使用简单明了,但也有局限性,只能在模板 html 中使用,无法在 ts 文件中使用。
@ViewChild 方式
通过@ViewChild
装饰器,将子组件注入到父组件。分为 4 步:
第一步:在子组件child.component.ts
中定义count
变量和add()
方法。
第二步:在父组件app.component.html
中子组件标签<app-child>
中添加标签引用#child
,点击按钮触发点击事件,执行方法add()
。
第三步:在父组件app.component.ts
中引入ViewChild
,@viewchild
传入标签引用字符child
,由变量child
接收。除了使用标签引用child
,你也可以通过直接传入子组件ChildComponent
实现子组件的引用。
第四步:在父组件app.component.ts
中方法add()
中调用子组件的add()
方法。
最终展示效果如下,您可以通过 在线示例 预览效果和编辑调试:
说明:@ViewChild
的作用是声明对子组件元素的实例引用,意思是通过注入的方式将子组件注入到@ViewChild
容器中,你可以想象成依赖注入的方式注入,只不过@ViewChild
不能在构造器constructor
中注入,因为@ViewChild()
会在ngAfterViewInit()
回调函数之前执行,我们可以测试下:
最终展示效果如下,您可以通过 在线示例 预览效果和编辑调试:
通过打印结果我们可以看到在构造函数constructor()
中,this.child
的值为undefined
,并没有注入到父组件,但在ngAfterViewInit()
生命周期钩子中注入成功了。
不相关组件
对于不相关联的组件,我们会使用其他中间媒介的方式进行通信,以下不相关组件的通信方式仍适用于父子组件。
service 服务方式
组件间共享一个 service 服务,那么组件之间就可以通过 service 实现通信。
示例中我们使用rxjs
中的BehaviorSubject
,它是Subject
的一种变体,可以存储最后一条数据或者初始默认值,并会在订阅时发送其当前值。您可以通过RxJS官网进行了解,当然通过文中的说明,您还是可以了解其具体实现的功能。
我们创建两个不相关的组件A
和B
,组件A
发布数据,组件B
接收数据,通过服务文件data.service.ts
进行关联实现。
在公共文件目录下创建service
服务文件data.service.ts
,代码如下:
引入BehaviorSubject
,创建一个BehaviorSubject
, 默认值设为0
。 记得在app.module.ts
文件中引入公共data.service.ts
文件,并声明。
创建组件A
,用于发布数据,a.component.ts
实现代码如下:
引入service
文件,在constructor()
中注入服务依赖dataService
,使用服务中subject
的next()
方法发布广播数据。
创建组件B
,用于接收数据,b.component.ts
实现代码如下:
引入Subscription
,使用服务中subject
的subscribe()
方法创建一个订阅者,当组件A
数据被发布后就可以接收到数据。最后在销毁时记得取消订阅,否则会导致泄露。
最终展示效果如下,您可以通过 在线示例 预览效果和编辑调试:
说明:示例中组件A
和B
都引入了同一个服务service
,service
服务则巧妙利用BehaviorSubject
实现数据的发布和订阅,在两个组件中进行数据的通信,是不是没有想象的那么难~
路由传参方式
路由传参有多种方式,首先我们新建一个路由模块app-routing.module.ts
,代码如下:
引入路由相关的RouterModule
和Routes
,引入跳转文章详情组件DetailComponent
,配置好路由routes
,当路径为detail
或detail/:id
时,会加载DetailComponent
组件,其中:id
为占位符,可以在组件ts
文件中获取id
值。
创建好路由模块我们还需要在根模块app.module.ts
中导入。
在app.component.html
文件中添加<router-outlet></router-outlet>
路由占位符,Angular 框架会根据当前的路由器状态将不同组件动态填充它。
配置完路由准备工作,我们来具体看下有哪些路由传参方式。
路由路径传参
路由路径中传参,链接形式为:https://ip/detail/1
。
在app.component.html
中使用路由指令routerLink
的方式在路由路径中传参。
在detail
组件detail.component.ts
中使用当前路由对象ActivatedRoute
获取路由传递的参数。
查询参数传参
查询参数中传参,链接形式为:https://ip/detail?id=2
。
在app.component.html
中同样使用路由指令routerLink
,在queryParams
查询参数中传递数据。
在detail
组件detail.component.ts
中使用当前路由对象ActivatedRoute
获取路由传递的参数。
仔细观察会发现,第一种路由路径中传参使用的是this.routeInfo.queryParams
获取数据,而第二种查询参数中传参使用的是this.routeInfo.queryParams
,一定要注意这个区别。
路由配置传参
除了在app.component.html
中使用路由指令routerLink
,我们还可以在app.component.ts
文件中通过路由配置中传参。
在app.component.html
文件中绑定两个点击方法toArticle3()
和toArticle4()
。
在app.component.ts
文件中通过路由Router
的navigate()
方法实现跳转。
虽然是通过路由配置传参跳转,但我们仍然可以发现,文章 3 和文章 1 的跳转链接一致,文章 4 和文章 2 的跳转链接一致,本质上也是路由路径传参和查询参数传参。所以在detail.component.ts
中,接收路由参数的方法是一致的。
最终展示效果如下,您可以通过 在线示例 预览效果和编辑调试:
这里需要说明下,在线示例中点击文章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
中,代码如下:
创建D
组件d.component.ts
获取localStorage
中cValue
的值。
最终展示效果如下,您可以通过 在线示例 预览效果和编辑调试:
注:这里没有使用sessionStorage
存储是因为localStorage
生命周期是永久的,而sessionStorage
生命周期仅为当前标签页,如果两个组件分别在两个标签页,那么使用sessionStorage
是无法实现通信的。
服务端通信方式
最后一种通信方式是借助后台传输数据,如A
组件调接口发送数据data
存储到后台,再由B
组件调接口获取数据data
,实现数据通信,这里就不做演示了。
总结
Angular 组件间的通信方式是多种多样的,对于不同情景我们可以采用合适的方式进行通信。
本文每个示例的重点我都有详细的说明,并延展一些相关知识。示例都是我自己一点点亲手敲的,从 0 到 1 研究示例实现方案,虽然花费了很长时间,但加深巩固了知识,之前忽略的一些知识细节也得到了补充,建议大家在学习的同时最好也能动手实现。
好啦,以上就是 Angular 组件间各种通信方式的所有内容,希望对你有所帮助,如有问题可通过我的博客https://echeverra.cn或微信公众号echeverra联系我。
你学“废”了么?
(完)
文章首发于我的博客 https://echeverra.cn/component-communication,原创文章,转载请注明出处。
欢迎关注我的微信公众号 echeverra,一起学习进步!不定时会有资源和福利相送哦!
版权声明: 本文为 InfoQ 作者【echeverra】的原创文章。
原文链接:【http://xie.infoq.cn/article/35b1307e6e0f1a4907b4e07cf】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论