角度2兄弟元件通信

我有一个 ListComponent。当在 ListComponent 中单击某个项时,该项的详细信息应该显示在 DetailComponent 中。两者同时显示在屏幕上,所以不需要路由。

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

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


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


但是这两种方法似乎都比通过 $rootScope 实现的 Angular 1方法复杂得多。$广播或 $范围。$父母。$广播。

角度2中的所有东西都是一个组件,我很惊讶没有更多关于组件通信的信息。

还有其他/更直接的方法来实现这一点吗?

127659 次浏览

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

我很惊讶没有更多关于组件通信的信息 请考虑 angualr2编写的本教程

对于兄弟组件的通信,我建议使用 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的这个教程了解更多信息。

更新为 rc.4: 当尝试在 angular2的兄弟组件之间传递数据时,目前最简单的方法(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. 只在 PARENT 组件中包含共享服务的服务提供者,而不包含子组件。
  2. 您仍然必须在子代中包含构造函数并导入服务
  3. 这个问题的答案最初是在一个早期的角度2测试版本中得到的。但是所有改变的都是 import 语句,所以如果您偶然使用了原始版本,那么这就是您需要更新的全部内容。

您需要在组件之间建立父子关系。问题在于,您可以简单地将子组件注入到父组件的构造函数中,并将其存储在一个局部变量中。 相反,您应该使用 @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/

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

任务服务:

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

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

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

这样做的好处是,孩子们不需要特定的共享服务,因此彼此之间没有那么紧密的联系。

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

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

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

Alex J 的回答很好,但是到2017年7月,它不再适用于当前的角度4。

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

Https://embed.plnkr.co/p8xcewskgcog07pwdrlo/

一种方法是使用 共享服务

不管我发现什么 解决方案要简单得多,它允许在两个兄弟之间共享数据(我只在 角度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;
...
}

行为实验对象。关于这一点,我写了一份 博客

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


newId(urlId) {
this.noId.next(urlId);
}

在这个例子中,我声明了一个类型为 number 的 noid 行为主体。它也是可以观察到的。如果发生了什么事情,这个会随着新的(){}函数而改变。

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

例如,我从 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,它必须将我重定向到 home:

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']);
}
})
})
}

在某些情况下,指令可以用来“连接”组件。事实上,被连接的东西甚至不需要是完整的组件,有时候如果它们不是完整的组件,它会更轻量级,实际上更简单。

例如,我有一个 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
}
}

ProductPage.component的 HTML 中,显然 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 简单和责任的一切不同。

这里有一个简单实用的解释: 简单解释 给你

电话服务

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

组件,您希望从这里调用观察组件,以通知另一个组件单击了按钮

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://musttoknow.com/angular-4-angular-5-communicate-two-components-using-observable-subject/

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

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

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

constructor(private sharedService: SharedService)
 

使用共享服务,您可以使用函数,也可以创建一个 Subject 来同时更新多个位置。

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

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

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

然后你可以在你的细节部分获取这些信息:

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

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

我还喜欢通过输入和输出在两个兄弟姐妹之间通过一个父组件进行通信。它比使用普通服务更好地处理 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;
}

做兄弟姐妹通信的一个简单的方法是在一个孩子中使用 @ 输出修饰器,在另一个孩子中使用 模板引用变量让父母 调用这个孩子的方法。这非常类似于使用“输出”来进行孩子与父母之间的交流。

孩子2中执行 this.emitSomething.emit(something);将在 儿童1中触发 onEmitSomething()

Child-1. Component. ts

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

Child-2. Component. ts

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

组件

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

这里可以找到两种不同的组件交互方式 角分量相互作用