关于angular:玩转Angular系列组件间各种通信方式详解

35次阅读

共计 11198 个字符,预计需要花费 28 分钟才能阅读完成。

前言

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


正文完
 0