角度 2 同级组件通信

Angular 2 Sibling Component Communication

本文关键字:组件 通信 角度      更新时间:2023-09-26

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

如何告诉 DetailComponent 单击了 ListComponent 中的哪个项目?

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


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


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

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

有没有另一种/更直接的方法来实现这一点?

更新到 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);
    }
}

现在,这将是父组件

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. 仅在父组件中包含共享服务的服务提供者,而不包括子组件。
  2. 您仍然必须包含构造函数并将服务导入子项
  3. 这个答案最初是在早期的 angular 2 测试版中回答的。所有更改的都是导入语句,因此,如果您偶然使用了原始版本,则需要更新这些语句。

如果有 2 个不同的组件(不是嵌套组件,父''子''孙子),我建议你这样做:

使命服务:

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();
  }
}

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

一种方法是使用共享服务。

但是我发现以下内容解决方案更简单,它允许在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;
   ...
}

这里有一个关于它的讨论。

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

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

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

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 for ProductPage.component 中,youtube-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变得简单,并且所有内容的责任都与众不同。

共享服务是解决此问题的良好解决方案。如果还想存储一些活动信息,可以将共享服务添加到主模块 (app.module) 提供程序列表中。

@NgModule({
    imports: [
        ...
    ],
    bootstrap: [
        AppComponent
    ],
    declarations: [
        AppComponent,
    ],
    providers: [
        SharedService,
        ...
    ]
});

然后你可以直接把它提供给你的组件,

constructor(private sharedService: SharedService)
 

使用共享服务,您可以使用功能,也可以创建主题以一次更新多个位置。

@Injectable()
export class SharedService {
    public clickedItemInformation: Subject<string> = new Subject(); 
}

在列表组件中,您可以发布单击的项目信息,

this.sharedService.clikedItemInformation.next("something");

然后,您可以在详细信息组件中获取此信息:

this.sharedService.clikedItemInformation.subscribe((information) => {
    // do something
});

显然,列出组件共享的数据可以是任何内容。希望这有帮助。

您需要在组件之间设置父子关系。问题在于,您可能只是在父组件的构造函数中注入子组件并将其存储在局部变量中。相反,应使用 @ViewChild 属性声明符在父组件中声明子组件。这是父组件的外观:

import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { ListComponent } from './list.component';
import { DetailComponent } from './detail.component';
@Component({
  selector: 'app-component',
  template: '<list-component></list-component><detail-component></detail-component>',
  directives: [ListComponent, DetailComponent]
})
class AppComponent implements AfterViewInit {
  @ViewChild(ListComponent) listComponent:ListComponent;
  @ViewChild(DetailComponent) detailComponent: DetailComponent;
  ngAfterViewInit() {
    // afther this point the children are set, so you can use them
    this.detailComponent.doSomething();
  }
}

https://angular.io/docs/ts/latest/api/core/index/ViewChild-var.html

https://angular.io/docs/ts/latest/cookbook/component-communication.html#parent-to-view-child

请注意,在

调用ngAfterViewInit生命周期挂钩之后,子组件在父组件的构造函数中将不可用。要捕获此钩子,只需在父类中实现AfterViewInit接口,就像处理OnInit一样。

但是,还有其他属性声明符,如本博客说明中所述:http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/

这里有简单的实际解释:简单解释在这里

In call.service.ts

import { Observable } from 'rxjs';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class CallService {
 private subject = new Subject<any>();
 sendClickCall(message: string) {
    this.subject.next({ text: message });
 }
 getClickCall(): Observable<any> {
    return this.subject.asObservable();
 }
}

要从中调用 observable 以通知另一个组件单击按钮的组件

import { CallService } from "../../../services/call.service";
 
export class MarketplaceComponent implements OnInit, OnDestroy {
  constructor(public Util: CallService) {
 
  }
 
  buttonClickedToCallObservable() {
   this.Util.sendClickCall('Sending message to another comp that button is clicked');
  }
}

要对其执行操作的组件,单击了另一个组件

import { Subscription } from 'rxjs/Subscription';
import { CallService } from "../../../services/call.service";

ngOnInit() {
 this.subscription = this.Util.getClickCall().subscribe(message => {
 this.message = message;
 console.log('---button clicked at another component---');
 //call you action which need to execute in this component on button clicked
 });
}
import { Subscription } from 'rxjs/Subscription';
import { CallService } from "../../../services/call.service";
 
 
ngOnInit() {
 
 this.subscription = this.Util.getClickCall().subscribe(message => {
 
 this.message = message;
 
 console.log('---button clicked at another component---');
 
 //call you action which need to execute in this component on button clicked
 
});
 
}

通过阅读以下内容,我对组件通信的理解很清楚:http://musttoknows.com/angular-4-angular-5-communicate-two-components-using-observable-subject/

这不是你真正想要的,但肯定会帮助你

我很惊讶没有关于组件通信的更多信息 <=>考虑 Anguarr2 的本教程

对于兄弟组件通信,我建议使用 sharedService .不过,还有其他选项可用。

import {Component,bind} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {HTTP_PROVIDERS} from 'angular2/http';
import {NameService} from 'src/nameService';

import {TheContent} from 'src/content';
import {Navbar} from 'src/nav';

@Component({
  selector: 'app',
  directives: [TheContent,Navbar],
  providers: [NameService],
  template: '<navbar></navbar><thecontent></thecontent>'
})

export class App {
  constructor() {
    console.log('App started');
  }
}
bootstrap(App,[]);

有关更多代码,请参阅顶部的链接。

编辑:这是一个非常小的演示。您已经提到您已经尝试过sharedService.因此,请考虑 angualr2 的本教程以获取更多信息。

行为主题。我为此写了一篇博客。

import { BehaviorSubject } from 'rxjs/BehaviorSubject';
private noId = new BehaviorSubject<number>(0); 
  defaultId = this.noId.asObservable();
newId(urlId) {
 this.noId.next(urlId); 
 }

在此示例中,我声明了一个类型为编号的 noid 行为主体。这也是一个可观察的。如果"发生了一些事情",这将随着 new(){} 函数而改变。

因此,在同级组件中,一个将调用函数进行更改,另一个将受到该更改的影响,反之亦然。

例如,我从 URL 获取 id 并从行为主题更新 noid。

public getId () {
  const id = +this.route.snapshot.paramMap.get('id'); 
  return id; 
}
ngOnInit(): void { 
 const id = +this.getId ();
 this.taskService.newId(id) 
}

另一方面,我可以询问该ID是否是"我想要的",然后做出选择,就我而言,如果我想删除一个任务,并且该任务是当前的URL,它必须将我重定向到主页:

delete(task: Task): void { 
  //we save the id , cuz after the delete function, we  gonna lose it 
  const oldId = task.id; 
  this.taskService.deleteTask(task) 
      .subscribe(task => { //we call the defaultId function from task.service.
        this.taskService.defaultId //here we are subscribed to the urlId, which give us the id from the view task 
                 .subscribe(urlId => {
            this.urlId = urlId ;
                  if (oldId == urlId ) { 
                // Location.call('/home'); 
                this.router.navigate(['/home']); 
              } 
          }) 
    }) 
}

进行同级到同级通信的一种巧妙方法是在一个子项中使用@Output装饰器,在另一个子项上使用模板引用变量,让父项调用此子项的方法。这与使用@Ouput进行孩子与父母的沟通非常相似。

孩子-2 中执行this.emitSomething.emit(something);将触发子项 1 中的onEmitSomething()

子-1.组件.ts

onEmitSomething(event: any): void {
  // do something
}

子-2.组件.ts

@Output() emitSomething: EventEmitter<any> = new EventEmitter<any>();

父组件.html

<child-1 #child1></child-1>
<child-2 (emitSomething)="child1.onEmitSomething($event)"></child-2>
组件

交互的两种不同方式可以在这里找到 角度 - 组件交互

我一直通过绑定将 setter 方法从父级传递到它的一个子级,使用子组件中的数据调用该方法,这意味着父组件已更新,然后可以使用新数据更新其第二个子组件。不过,它确实需要绑定"this"或使用箭头函数。

这样做的好处是,孩子们不需要特定的共享服务。

我不完全确定这是最佳做法,听听其他人对此的看法会很有趣。

我也喜欢通过父组件通过输入和输出在 2 个兄弟姐妹之间进行通信。 它比使用通用服务更好地处理 OnPush 更改通知。或者只是使用NgRx商店。

例。

@Component({
    selector: 'parent',
    template: `<div><notes-grid 
            [Notes]="(NotesList$ | async)"
            (selectedNote)="ReceiveSelectedNote($event)"
        </notes-grid>
        <note-edit 
            [gridSelectedNote]="(SelectedNote$ | async)"
        </note-edit></div>`,
    styleUrls: ['./parent.component.scss']
})
export class ParentComponent {
    // create empty observable
    NotesList$: Observable<Note[]> = of<Note[]>([]);
    SelectedNote$: Observable<Note> = of<Note>();
    //passed from note-grid for selected note to edit.
    ReceiveSelectedNote(selectedNote: Note) {
    if (selectedNote !== null) {
        // change value direct subscribers or async pipe subscribers will get new value.
        this.SelectedNote$ = of<Note>(selectedNote);
    }
    }
    //used in subscribe next() to http call response.  Left out all that code for brevity.  This just shows how observable is populated.
    onNextData(n: Note[]): void {
    // Assign to Obeservable direct subscribers or async pipe subscribers will get new value.
    this.NotesList$ = of<Note[]>(n.NoteList);  //json from server
    }
}
//child 1 sibling
@Component({
  selector: 'note-edit',
  templateUrl: './note-edit.component.html', // just a textarea for noteText and submit and cancel buttons.
  styleUrls: ['./note-edit.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class NoteEditComponent implements OnChanges {
  @Input() gridSelectedNote: Note;
    constructor() {
    }
// used to capture @Input changes for new gridSelectedNote input
ngOnChanges(changes: SimpleChanges) {
     if (changes.gridSelectedNote && changes.gridSelectedNote.currentValue !== null) {      
      this.noteText = changes.gridSelectedNote.currentValue.noteText;
      this.noteCreateDtm = changes.gridSelectedNote.currentValue.noteCreateDtm;
      this.noteAuthorName = changes.gridSelectedNote.currentValue.noteAuthorName;
      }
  }
}
//child 2 sibling
@Component({
    selector: 'notes-grid',
    templateUrl: './notes-grid.component.html',  //just an html table with notetext, author, date
    styleUrls: ['./notes-grid.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class NotesGridComponent {
// the not currently selected fromt eh grid.
    CurrentSelectedNoteData: Note;
    // list for grid
    @Input() Notes: Note[];
    // selected note of grid sent out to the parent to send to sibling.
    @Output() readonly selectedNote: EventEmitter<Note> = new EventEmitter<Note>();
    constructor() {
    }
    // use when you need to send out the selected note to note-edit via parent using output-> input .
    EmitSelectedNote(){
    this.selectedNote.emit(this.CurrentSelectedNoteData);
    }
}

// here just so you can see what it looks like.
export interface Note {
    noteText: string;
    noteCreateDtm: string;
    noteAuthorName: string;
}