玩转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中引入OutputEventEmitter,通过@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中通过newItemEventemit()方法,把数据发送到父组件。

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官网进行了解,当然通过文中的说明,您还是可以了解其具体实现的功能。

我们创建两个不相关的组件AB,组件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,使用服务中subjectnext()方法发布广播数据。

创建组件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,使用服务中subjectsubscribe()方法创建一个订阅者,当组件A数据被发布后就可以接收到数据。最后在销毁时记得取消订阅,否则会导致泄露。

最终展示效果如下,您可以通过 在线示例 预览效果和编辑调试:

说明:示例中组件AB都引入了同一个服务serviceservice服务则巧妙利用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 {}

引入路由相关的RouterModuleRoutes,引入跳转文章详情组件DetailComponent,配置好路由routes,当路径为detaildetail/: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文件中通过路由Routernavigate()方法实现跳转。

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'];,可以看到同样是paramsqueryParams的区别。

snapshot参数快照和subscribe参数订阅两者的区别在于,当路由地址不变的情况下,若参数变化,snapshot参数快照获取的参数值不变,subscribe参数订阅获取的参数值会变化。

我们使用snapshot参数快照测试一下文章1和文章3,效果如下:

那么snapshot参数快照获取的参数为什么不发生变化了呢?这是由于第一次点击文章1跳转detail组件,constructor()ngOnInit()会被调用一次,再次点击文章3,由于detail组件页面已经被创建了,ngOnInit()方法不会再次被调用,所以路由参数id依然保存着第一次被创建时候的值1

LocalStorage方式

当然你也可以使用本地存储这种比较通用的方式在组件间通信。

创建C组件c.component.ts将数据存储到keycValuelocalStorage中,代码如下:

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获取localStoragecValue的值。

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,一起学习进步!不定时会有资源和福利相送哦!文章来源地址https://www.toymoban.com/news/detail-807363.html


到了这里,关于玩转Angular系列:组件间各种通信方式详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【干货】Vue2.x 组件通信方式详解,这篇讲全了

    vue是数据驱动视图更新的框架, 我们平时开发,都会把页面不同模块拆分成一个一个vue组件, 所以对于vue来说组件间的数据通信非常重要,那么组件之间如何进行数据通信的呢? 首先我们需要知道在vue中组件之间存在什么样的关系, 才更容易理解他们的通信方式。 一般我们分

    2023年04月27日
    浏览(46)
  • 【react从入门到精通】React父子组件通信方式详解(有示例)

    【分享几个国内免费可用的ChatGPT镜像】 【10几个类ChatGPT国内AI大模型】 【用《文心一言》1分钟写一篇博客简直yyds】 【用讯飞星火大模型1分钟写一个精美的PPT】 在上一篇文章《JSX详解》中我们了解了什么是jsx以及jsx的语法规则。 本文中我们将详细了解React父子组件通信方式

    2024年02月05日
    浏览(97)
  • Angular系列教程之组件

    在Angular中,组件是构建Web应用程序的核心单元。它们允许我们将UI划分为独立且可重用的部分,并通过数据绑定和事件处理等机制来实现交互性。本文将介绍Angular组件的基本概念,并说明组件和指令的关系。 组件是一个由HTML模板、样式和逻辑代码组成的独立单元。它可以看

    2024年01月17日
    浏览(37)
  • Angular组件生命周期详解

    当 Angular 实例化组件类 并渲染组件视图及其子视图时,组件实例的生命周期就开始了。生命周期一直伴随着变更检测,Angular 会检查数据绑定属性何时发生变化,并按需更新视图和组件实例。当 Angular 销毁组件实例并从 DOM 中移除它渲染的模板时,生命周期就结束了。当 Ang

    2024年02月05日
    浏览(45)
  • 面试系列-各种组件问一下(二)

    欢迎大家对答案进行补充、勘误,可以私信或者文章底部评论 1、spark宽窄依赖区分     宽依赖:是指一个父RDD分区对应多个子RDD的分区,比如map、filter等算子     窄依赖:是指多个父RDD分区对应一个子RDD分区,比如groupByKey,reduceByKey等算子,会产生shuffler操作     区分:可以

    2024年02月01日
    浏览(33)
  • 由浅入深掌握各种 Python 进程间通信方式(建议收藏)

    转载本文请注明 CSDN 链接处: https://blog.csdn.net/captain5339/article/details/129099833 Python代码效率由于受制于GIL全局锁限制,多线程不能利用多核CPU来加速,而 多进程 方式却可以绕过GIL限制, 发挥多CPU加速的优势,达到提高程序的性能的目的。 然而进程间通信却是不得不考虑的问题。

    2024年02月02日
    浏览(50)
  • Angular系列教程之依赖注入详解

    Angular作为一款流行的前端框架,提供了许多优秀的功能和特性,其中之一就是依赖注入(Dependency Injection)。依赖注入是一种设计模式,它允许我们在代码中以一种可扩展和可测试的方式添加和配置依赖关系。在Angular中,依赖注入被广泛应用于组件、服务、指令等场景,本文

    2024年01月17日
    浏览(43)
  • 玩转Mysql系列 - 第15篇:详解视图

    这是Mysql系列第15篇。 环境:mysql5.7.25,cmd命令中进行演示。 需求背景 电商公司领导说:给我统计一下:当月订单总金额、订单量、男女订单占比等信息,我们啪啦啪啦写了一堆很复杂的sql,然后发给领导。 这样一大片sql,发给领导,你们觉得好么? 如果领导只想看其中某

    2024年02月09日
    浏览(37)
  • 玩转Mysql系列 - 第19篇:游标详解

    这是Mysql系列第19篇。 环境:mysql5.7.25,cmd命令中进行演示。 代码中被[]包含的表示可选,|符号分开的表示可选其一。 需求背景 当我们需要对一个select的查询结果进行遍历处理的时候,如何实现呢? 此时我们需要使用游标,通过游标的方式来遍历select查询的结果集,然后对

    2024年02月09日
    浏览(41)
  • 玩转Mysql系列 - 第16篇:变量详解

    这是Mysql系列第16篇。 环境:mysql5.7.25,cmd命令中进行演示。 代码中被[]包含的表示可选,|符号分开的表示可选其一。 我们在使用mysql的过程中,变量也会经常用到,比如查询系统的配置,可以通过查看系统变量来了解,当我们需要修改系统的一些配置的时候,也可以通过修

    2024年02月09日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包