Angular 2兄弟コンポーネント通信


118

ListComponentがあります。ListComponentでアイテムをクリックすると、そのアイテムの詳細がDetailComponentに表示されます。両方が同時に画面に表示されるため、ルーティングは必要ありません。

ListComponentのどのアイテムがクリックされたかをDetailComponentに通知するにはどうすればよいですか?

親(AppComponent)までイベントを発行することを検討し、@ Inputを使用して親にDetailComponentのselectedItem.idを設定させました。または、監視可能なサブスクリプションで共有サービスを使用することもできます。


編集:ただし、追加のコードを実行する必要がある場合に備えて、イベント+ @Inputを介して選択したアイテムを設定しても、DetailComponentはトリガーされません。したがって、これが許容できるソリューションであるかどうかはわかりません。


しかし、これらの方法はどちらも、$ rootScope。$ broadcastまたは$ scope。$ parent。$ broadcastを介したAngular 1の方法よりもはるかに複雑に見えます。

Angular 2のすべてがコンポーネントであるため、コンポーネントの通信についてこれ以上の情報がないことに驚きます。

これを達成する別の/より簡単な方法はありますか?


兄弟データ共有のための方法を見つけましたか?私は..観測可能として、それを必要とする
人間

回答:


65

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 = '';
    }
}

NOW:この方法を使用する場合の注意事項。

  1. 親コンポーネントには共有サービスのサービスプロバイダーのみを含め、子は含めないでください。
  2. コンストラクタを含めて、子にサービスをインポートする必要があります
  3. この回答は当初、初期の角2ベータ版で回答されていました。ただし、変更されたのはインポート文だけなので、元のバージョンをたまたま使用した場合はそれだけで更新する必要があります。

2
これはangular-rc1でも有効ですか?
セルジオ

4
しかし、これが共有サービスで何かが更新されたことを兄弟に通知するとは思わない。child-component1が、child-component2が応答する必要がある何かを行う場合、このメソッドはそれを処理しません。私はそれを回避する方法がオブザーバブルであると信じていますか?
dennis.sheppard 2016年

1
@Sufyan:プロバイダーフィールドを子に追加すると、Angularがそれぞれに新しいプライベートインスタンスを作成するようになると思います。追加しない場合は、親の「シングルトン」インスタンスを使用します。
ラルフ

1
これは、最新の更新ではもう機能していないようです
Sufyan Jabr '28

3
これは時代遅れです。directivesコンポーネントで宣言されなくなりました。
Nate Gardner

26

2つの異なるコンポーネント(ネストされたコンポーネントではなく、parent \ child \ grandchild)の場合、これをお勧めします:

MissionService:

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

}

AstronautComponent:

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
この回答に用語を追加してください。私はそれがRxJSやObservableパターンなどと一致するとは確信していませんが、これらのいくつかに説明を追加すると(私のような)人にとって有益になるでしょう。
2018年

13

これを行う1つの方法は、共有サービスを使用することですです。

ただし、次の解決策ははるかに簡単で、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;
   ...
}

これは疎結合、つまりコンポーネントの考えに反対しているのではないですか?
ロビン

誰もがこれがそれへのクリーンな方法であるか汚い方法であるかどうかを知っていますか?たとえばsibiling1からsibiling2へのデータ転送だけではなく、他の方法ではデータ転送を一方向で行う方がはるかに簡単です
サラ


7

ディレクティブは、特定の状況でコンポーネントを「接続」するのに意味があります。実際、接続されているものは完全なコンポーネントである必要はなく、そうでない場合は、より軽量で、実際にはより単純な場合もあります。

たとえば、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で(クリック)イベントを宣言する必要はありません。

したがって、ディレクティブはProductPageメディエーターとして関与する必要なく、ビデオプレーヤーに適切に接続できます。

これが私が実際にこれを行ったのは初めてなので、はるかに複雑な状況でどれほどスケーラブルになるかはまだわかりません。これについては私は満足していますが、HTMLは単純で、すべての責任が明確になっています。


理解すべき最も重要な角度の概念の1つは、コンポーネントはテンプレートを持つ単なるディレクティブであるということです。これが何を意味するのかを本当に理解したら、ディレクティブはそれほど怖くない-そして、それらを任意の要素に適用して、それに動作を付加できることに気付くでしょう。
Simon_Weaver 2018年

私はこれを試しましたが、と同等の識別子の重複エラーが発生しますplayer。プレーヤーについての最初の言及を省略すると、rangeErrorが発生します。これがどのように機能するかについて私は困惑しています。
キャサリンオズボーン、

@KatharineOsborne _playerプレーヤーを表すプライベートフィールドに使用する実際のコードのように見えるので、これを正確にコピーするとエラーが発生します。更新されます。ごめんなさい!
Simon_Weaver 2018

4

ここに簡単な実用的な説明があります:ここで簡単に説明されています

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

オブザーバブルを呼び出して、ボタンがクリックされたことを別のコンポーネントに通知するコンポーネント

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/


シンプルなソリューションに感謝します>うまく機能するstackblitzで試してみました。しかし、私のアプリケーションには遅延読み込みされたルート(providedin: 'root'を使用しています)とHTTP呼び出しが設定および取得されています。HTTP呼び出しについて教えてください。何度も試したが機能しなかった:
Kshri

4

共有サービスは、この問題の良い解決策です。一部のアクティビティ情報も保存する場合は、共有サービスをメインモジュール(app.module)プロバイダーリストに追加できます。

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

次に、コンポーネントに直接提供できます。

constructor(private sharedService: SharedService)

共有サービスでは、関数を使用するか、サブジェクトを作成して複数の場所を一度に更新できます。

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

リストコンポーネントでは、クリックされたアイテム情報を公開できます。

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

次に、この情報を詳細コンポーネントでフェッチできます。

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

明らかに、コンポーネントの共有をリストするデータは何でもかまいません。お役に立てれば。


これは、共有サービスのこの概念の最も簡単な(別名:簡潔)例であり、受け入れられた回答がないため、可視性を高めるために実際に賛成する必要があります。
iGanja

3

コンポーネント間に親子関係を設定する必要があります。問題は、単純に親コンポーネントのコンストラクターに子コンポーネントを挿入し、それをローカル変数に格納することです。代わりに、@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/


2

行動主体。それについてブログを書いた。

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

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

この例では、number型のnoid動作サブジェクトを宣言しています。また、観測可能です。そして、「何かが起こった」場合、これはnew(){}関数で変更されます。

したがって、兄弟のコンポーネントでは、1つは関数を呼び出して変更を行い、もう1つはその変更によって影響を受けるか、またはその逆になります。

たとえば、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']); 
              } 
          }) 
    }) 
}

1

これはまさにあなたが望むものではありませんが、確かにあなたを助けます

コンポーネントの通信に関する情報がこれ以上ないことに驚きます <=> 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によるこのチュートリアル検討してください。


0

私はバインディングを介して親から子の1つにセッターメソッドを渡し、子コンポーネントのデータでそのメソッドを呼び出しています。つまり、親コンポーネントが更新され、2番目の子コンポーネントを新しいデータで更新できます。ただし、「this」をバインドするか、矢印関数を使用する必要があります。

これには、特定の共有サービスを必要としないため、子同士があまり結合されないという利点があります。

これがベストプラクティスであることを完全には確信していません。これについて他の意見を聞くのは興味深いでしょう。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.