Angular 2 兄弟组件通信

IT技术 javascript angular typescript
2021-01-19 01:05:01

我有一个列表组件。当在 ListComponent 中单击某个项目时,该项目的详细信息应显示在 DetailComponent 中。两者同时出现在屏幕上,因此不涉及路由。

我如何告诉 DetailComponent ListComponent 中的哪个项目被点击?

我已经考虑向父级 (AppComponent) 发出一个事件,并让父级在 DetailComponent 上使用 @Input 设置 selectedItem.id。或者我可以使用具有可观察订阅的共享服务。


编辑:通过 event + @Input 设置所选项目不会触发 DetailComponent,不过,以防万一我需要执行额外的代码。所以我不确定这是一个可以接受的解决方案。


但是这两种方法似乎都比 Angular 1 的通过 $rootScope.$broadcast 或 $scope.$parent.$broadcast 做事的方式复杂得多。

由于 Angular 2 中的所有内容都是一个组件,我很惊讶没有更多关于组件通信的信息。

是否有另一种/更直接的方法来实现这一目标?

6个回答

更新到 rc.4: 当尝试在 angular 2 中的兄弟组件之间传递数据时,现在最简单的方法(angular.rc.4)是利用 angular2 的分层依赖注入并创建共享服务。

这将是服务:

import {Injectable} from '@angular/core';

@Injectable()
export class SharedService {
    dataArray: string[] = [];

    insertData(data: string){
        this.dataArray.unshift(data);
    }
}

现在,这里将是 PARENT 组件

import {Component} from '@angular/core';
import {SharedService} from './shared.service';
import {ChildComponent} from './child.component';
import {ChildSiblingComponent} from './child-sibling.component';
@Component({
    selector: 'parent-component',
    template: `
        <h1>Parent</h1>
        <div>
            <child-component></child-component>
            <child-sibling-component></child-sibling-component>
        </div>
    `,
    providers: [SharedService],
    directives: [ChildComponent, ChildSiblingComponent]
})
export class parentComponent{

} 

和它的两个孩子

孩子 1

import {Component, OnInit} from '@angular/core';
import {SharedService} from './shared.service'

@Component({
    selector: 'child-component',
    template: `
        <h1>I am a child</h1>
        <div>
            <ul *ngFor="#data in data">
                <li>{{data}}</li>
            </ul>
        </div>
    `
})
export class ChildComponent implements OnInit{
    data: string[] = [];
    constructor(
        private _sharedService: SharedService) { }
    ngOnInit():any {
        this.data = this._sharedService.dataArray;
    }
}

孩子2(这是兄弟姐妹)

import {Component} from 'angular2/core';
import {SharedService} from './shared.service'

@Component({
    selector: 'child-sibling-component',
    template: `
        <h1>I am a child</h1>
        <input type="text" [(ngModel)]="data"/>
        <button (click)="addData()"></button>
    `
})
export class ChildSiblingComponent{
    data: string = 'Testing data';
    constructor(
        private _sharedService: SharedService){}
    addData(){
        this._sharedService.insertData(this.data);
        this.data = '';
    }
}

现在:使用此方法时要注意的事项。

  1. 仅在 PARENT 组件中包含共享服务的服务提供者,而不是子组件。
  2. 您仍然必须包含构造函数并在子项中导入服务
  3. 这个答案最初是为早期的 angular 2 beta 版本回答的。不过,所有已更改的是导入语句,因此如果您偶然使用了原始版本,则只需更新这些语句。
这对 angular-rc1 仍然有效吗?
2021-03-12 01:05:01
这是过时的。directives不再在组件中声明。
2021-03-15 01:05:01
不过,我认为这不会通知兄弟姐妹共享服务中的某些内容已更新。如果 child-component1 做了一些 child-component2 需要响应的事情,这个方法就不会处理。我相信解决方法是使用 observables 吗?
2021-03-16 01:05:01
@Sufyan:我猜想将 provider 字段添加到孩子会导致 Angular 为每个人创建新的私有实例。当您不添加它们时,它们将使用父级的“单例”实例。
2021-03-16 01:05:01
看起来这不再适用于最新更新
2021-03-18 01:05:01

如果有 2 个不同的组件(不是嵌套组件, parent\child\grandchild ),我建议您这样做:

使命服务:

import { Injectable } from '@angular/core';
import { Subject }    from 'rxjs/Subject';

@Injectable()

export class MissionService {
  // Observable string sources
  private missionAnnouncedSource = new Subject<string>();
  private missionConfirmedSource = new Subject<string>();
  // Observable string streams
  missionAnnounced$ = this.missionAnnouncedSource.asObservable();
  missionConfirmed$ = this.missionConfirmedSource.asObservable();
  // Service message commands
  announceMission(mission: string) {
    this.missionAnnouncedSource.next(mission);
  }
  confirmMission(astronaut: string) {
    this.missionConfirmedSource.next(astronaut);
  }

}

宇航员组件:

import { Component, Input, OnDestroy } from '@angular/core';
import { MissionService } from './mission.service';
import { Subscription }   from 'rxjs/Subscription';
@Component({
  selector: 'my-astronaut',
  template: `
    <p>
      {{astronaut}}: <strong>{{mission}}</strong>
      <button
        (click)="confirm()"
        [disabled]="!announced || confirmed">
        Confirm
      </button>
    </p>
  `
})
export class AstronautComponent implements OnDestroy {
  @Input() astronaut: string;
  mission = '<no mission announced>';
  confirmed = false;
  announced = false;
  subscription: Subscription;
  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission => {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }
  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }
  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}

来源:父母和孩子通过服务进行交流

希望您在此答案中添加一些术语。我相信它符合 RxJS、Observable 模式等。不完全确定,但对其中一些添加描述对人们(比如我自己)是有益的。
2021-03-16 01:05:01

一种方法是使用共享服务

但是我发现以下解决方案更简单,它允许在 2 个兄弟姐妹之间共享数据。(我仅在Angular 5上测试过

在您的父组件模板中:

<!-- Assigns "AppSibling1Component" instance to variable "data" -->
<app-sibling1 #data></app-sibling1>
<!-- Passes the variable "data" to AppSibling2Component instance -->
<app-sibling2 [data]="data"></app-sibling2> 

app-sibling2.component.ts

import { AppSibling1Component } from '../app-sibling1/app-sibling1.component';
...

export class AppSibling2Component {
   ...
   @Input() data: AppSibling1Component;
   ...
}
@Robin 有时您的组件不能松散耦合。例如,在一个组件应该控制另一个组件的情况下,这是一个很好的功能,您可以在模板中决定哪个组件应该与另一个组件通信。如果您想连接组件,服务将无法工作。例如,一个组件提供了一个用户界面来选择显示的列,另一个组件负责呈现一个列表,但您希望在多个模板中重用这两个组件。
2021-03-28 01:05:01
任何人都知道这是一种干净还是肮脏的方式?在一个方向上共享数据似乎要简单得多,例如仅从 sibiling1 到 sibiling2,而不是相反
2021-03-29 01:05:01
这不是反对松散耦合的想法,因此也反对组件的想法吗?
2021-03-30 01:05:01

有一个关于它的讨论here。

https://github.com/angular/angular.io/issues/2663

Alex J 的回答很好,但截至 2017 年 7 月,它不再适用于当前的 Angular 4。

这个 plunker 链接将演示如何使用共享服务和 observable 在兄弟姐妹之间进行通信。

https://embed.plnkr.co/P8xCEwSKgcOg07pwDrlO/

在某些情况下,指令可以有意义地“连接”组件。事实上,被连接的东西甚至不需要是完整的组件,有时它更轻巧,如果不是的话,实际上更简单。

例如,我有一个Youtube Player组件(包装 Youtube API),我想要一些控制器按钮。按钮不是我的主要组件的一部分的唯一原因是它们位于 DOM 中的其他地方。

在这种情况下,它实际上只是一个“扩展”组件,只会与“父”组件一起使用。我说的是“父”,但在 DOM 中它是一个兄弟——所以你可以随意称呼它。

就像我说的,它甚至不需要是一个完整的组件,在我的情况下它只是一个<button>(但它可能是一个组件)。

@Directive({
    selector: '[ytPlayerPlayButton]'
})
export class YoutubePlayerPlayButtonDirective {

    _player: YoutubePlayerComponent; 

    @Input('ytPlayerVideo')
    private set player(value: YoutubePlayerComponent) {
       this._player = value;    
    }

    @HostListener('click') click() {
        this._player.play();
    }

   constructor(private elementRef: ElementRef) {
       // the button itself
   }
}

在用于 的 HTML 中ProductPage.componentyoutube-player很明显我的组件在哪里包装了 Youtube API。

<youtube-player #technologyVideo videoId='NuU74nesR5A'></youtube-player>

... lots more DOM ...

<button class="play-button"        
        ytPlayerPlayButton
        [ytPlayerVideo]="technologyVideo">Play</button>

该指令为我连接了一切,我不必在 HTML 中声明 (click) 事件。

因此,该指令可以很好地连接到视频播放器,而无需ProductPage作为中介参与

这是我第一次真正做到这一点,所以还不确定它在更复杂的情况下的可扩展性如何。为此,虽然我很高兴,但它使我的 HTML 变得简单,并且让我对所有事情的责任都不同。

要理解的最重要的 Angular 概念之一是组件只是带有模板的指令。一旦你真正理解了这意味着什么,那么指令就不是那么可怕了——你会意识到你可以将它们应用于任何元素以将行为附加到它。
2021-03-14 01:05:01
@KatharineOsborne 看起来在我的实际代码中我_player用于代表玩家的私有字段,所以是的,如果你完全复制它,你会得到一个错误。会更新。对不起!
2021-03-15 01:05:01
@Simon_Weaver 如果您有一个包含多个按钮的组件(不是单个按钮),您会怎么做。例如播放、暂停、停止倒带、前进等。
2021-03-30 01:05:01
我试过这个,但我得到一个重复的标识符错误,相当于player. 如果我不第一次提到玩家,我会得到一个 rangeError。我很难过这应该如何工作。
2021-03-31 01:05:01