web-dev-qa-db-ja.com

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

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

ListComponentのどの項目がクリックされたかをDetailComponentに伝えるにはどうすればよいですか?

私は親(AppComponent)までイベントを発行することを検討し、@InputでDetailComponentのselectedItem.idを親に設定させます。または私は観察可能な購読と共有サービスを使用することができます。


EDIT: event + @Inputで選択した項目を設定してもDetailComponentは起動しませんが、追加のコードを実行する必要がある場合に備えてです。だから私はこれが許容できる解決策であるかどうかわからない。


しかし、これらの方法はどちらも、$ [rootScope]、[$ broadcast]または[$ scope]、[$ parent]、[$ broadcast]のいずれかを使用したAngular 1の方法よりもはるかに複雑です。

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

これを達成するための別の/より直接的な方法はありますか?

101
dennis.sheppard

rc.4:に更新 Angular 2で兄弟コンポーネント間でデータをやり取りしようとしているときの最も簡単な方法(angular.rc.4)は、angle2の階層依存性注入を利用して共有サービス.

これがサービスです。

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. この答えはもともと初期の角度2のベータ版のために答えられました。ただし、変更されたのはimport文だけなので、偶然に元のバージョンを使用した場合は更新する必要があるのはこれだけです。
62
Alex J

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

}

宇宙飛行士のコンポーネント:

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

ソース: 親と子はサービスを介して通信します

24
Dudi

これを行う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;
   ...
}
11
Caner

それについての議論がここにあります。

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

Alex Jの答えは良いですが、2017年7月の時点で現在のAngular 4では動作しなくなりました。

そして、この急増するリンクは、共有サービスと観測可能なものを使用して兄弟間で通信する方法を示します。

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

9
João Silva

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

例えば、私は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をシンプルにし、すべての責任を区別します。

4
Simon_Weaver

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

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

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
Vereb

行動の主題。それについて blog と書きました。

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

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

この例では、私はタイプnumberの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が「今までに欲しいもの」であるかどうかを尋ねることができます。その後、選択してください。家に:

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

これはあなたが望むものではありませんが、確かに助けになるでしょう

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

1
micronyks

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

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

それからあなたは直接あなたのコンポーネントにそれを提供することができます、

constructor(private sharedService: SharedService)

Shared Serviceを使用すると、関数を使用することも、件名を作成して一度に複数の場所を更新することもできます。

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

リストコンポーネントでは、クリックした商品情報を公開できます。

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

そして、あなたはあなたの詳細コンポーネントでこの情報を取得することができます:

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

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

1
Nick Greaves

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

これは、彼らが特定の共有サービスを必要としないので、子供たちが互いにあまり結びつきがないという利点を有する。

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

0
user3711899