「ルーターアウトレット」の子コンポーネントにデータを渡す


99

サーバーに行き、オブジェクトをフェッチする親コンポーネントを持っています:

// parent component

@Component({
    selector : 'node-display',
    template : `
        <router-outlet [node]="node"></router-outlet>
    `
})

export class NodeDisplayComponent implements OnInit {

    node: Node;

    ngOnInit(): void {
        this.nodeService.getNode(path)
            .subscribe(
                node => {
                    this.node = node;
                },
                err => {
                    console.log(err);
                }
            );
    }

そして、いくつかの子供たちのディスプレイの1つ:

export class ChildDisplay implements OnInit{

    @Input()
    node: Node;

    ngOnInit(): void {
        console.log(this.node);
    }

}

にデータを挿入するだけではうまくいかないようですrouter-outlet。Webコンソールでエラーが表示されるようです:

Can't bind to 'node' since it isn't a known property of 'router-outlet'.

これはある程度理にかなっていますが、次のようにするにはどうすればよいですか。

  1. 親コンポーネント内からサーバーから「ノード」データを取得しますか?
  2. サーバーから取得したデータを子ルーターアウトレットに渡しますか?

router-outlets同じように動作するようには見えません。

回答:


83
<router-outlet [node]="..."></router-outlet> 

無効です。ルーターによって追加されたコンポーネントは兄弟として追加され、<router-outlet>置き換えられません。

https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-serviceも参照してください。

@Injectable() 
export class NodeService {
  private node:Subject<Node> = new BehaviorSubject<Node>([]);

  get node$(){
    return this.node.asObservable().filter(node => !!node);
  }

  addNode(data:Node) {
    this.node.next(data);
  }
}
@Component({
    selector : 'node-display',
    providers: [NodeService],
    template : `
        <router-outlet></router-outlet>
    `
})
export class NodeDisplayComponent implements OnInit {
    constructor(private nodeService:NodeService) {}
    node: Node;
    ngOnInit(): void {
        this.nodeService.getNode(path)
            .subscribe(
                node => {
                    this.nodeService.addNode(node);
                },
                err => {
                    console.log(err);
                }
            );
    }
}
export class ChildDisplay implements OnInit{
    constructor(nodeService:NodeService) {
      nodeService.node$.subscribe(n => this.node = n);
    }
}

これは子供にIDを与えるために使用できますか?私はすべての変更を試みたnodeところ、それはへの型としてだstringし、それが動作するようには思えないか、私は何かを間違ってやっている
Wanjia

子コンポーネントに任意のデータを渡すことができますが、子コンポーネントはID自体を設定する必要があります。あなたが取得しようとすることができElementRef、IDを設定するためにそれを使用するルータのコンセントからイベントからではなく、私は心で知っていない場合や(のみ私の携帯電話上で)それを行う方法
ギュンターZöchbauer

46

ギュンタースの回答は素晴らしいです。Observablesを使用しない別の方法を指摘したいだけです。

ただし、これらのオブジェクトは参照によって渡されることを覚えておく必要があります。そのため、子のオブジェクトで何らかの作業を行い、親オブジェクトに影響を与えたくない場合は、ギュンターのソリューションを使用することをお勧めします。しかし、それが問題ではない場合、または実際に望ましい動作である場合は、次のことをお勧めします。

@Injectable()
export class SharedService {

    sharedNode = {
      // properties
    };
}

親で値を割り当てることができます:

this.sharedService.sharedNode = this.node;

子(および親)で、コンストラクターに共有サービスを注入します。モジュール内のコンポーネント全体にシングルトンサービスが必要な場合は、モジュールレベルのプロバイダー配列でサービスを提供することを忘れないでください。あるいは、親のみのプロバイダー配列にサービスを追加するだけで、親と子は同じサービスのインスタンスを共有します。

node: Node;

ngOnInit() {
    this.node = this.sharedService.sharedNode;    
}

そして、ニューマンが親切に指摘したようthis.sharedService.sharedNodeに、htmlテンプレートまたはゲッターにも含めることができます。

get sharedNode(){
  return this.sharedService.sharedNode;
}

5
htmlのsharedService.sharedNodeに直接バインドできる/すべきです。このようにして、sharedNodeの更新は常に同期されます。
ニューマン2017

11

はい、ルーターアウトレットを介して表示されるコンポーネントの入力を設定できます。悲しいことに、他の回答で述べたように、プログラムで行う必要があります。オブザーバブルが関与している場合は、大きな注意点があります(以下で説明)。

方法は次のとおりです。

(1)activate親テンプレートのルーターアウトレットのイベントに接続します。

<router-outlet (activate)="onOutletLoaded($event)"></router-outlet>

(2)親のtypescriptファイルに切り替え、子コンポーネントの入力がアクティブになるたびにプログラムで設定します。

onOutletLoaded(component) {
    component.node = 'someValue';
} 

上記のバージョンのonOutletLoadedはわかりやすくするために簡略化されていますが、すべての子コンポーネントに、割り当てている入力とまったく同じ入力があることを保証できる場合にのみ機能します。入力が異なるコンポーネントがある場合は、タイプガードを使用します。

onChildLoaded(component: MyComponent1 | MyComponent2) {
  if (component instanceof MyComponent1) {
    component.someInput = 123;
  } else if (component instanceof MyComponent2) {
    component.anotherInput = 456;
  }
}

この方法が保守方法よりも好まれるのはなぜですか?

このメソッドもサービスメソッドも子コンポーネントと通信するための「正しい方法」ではありません(どちらのメソッドも純粋なテンプレートバインディングから離れます)ので、どちらの方法がプロジェクトに適しているかを判断する必要があります。

ただし、この方法では、サービスと子コンポーネントを密結合する仲介者オブジェクト(サービス)を作成することを回避できます。@Inputsを介して子コンポーネントにデータを渡し続けることができるため、多くの場合、これは「角度のある方法」に近づいています。また、既存のコンポーネントやサードパーティのコンポーネントに適していないか、そのサービスと密接に結合できない場合にも適しています。一方、次のような場合は、角度の付いた方法のように感じられなくなる可能性があります...

警告

この方法の注意点は、プログラムでデータを渡すため、観測可能なデータをテンプレートで渡すオプションがなくなったことです(驚き!)。つまり、パイプ非同期パターンを使用すると、オブザーバブルのライフサイクルを角度管理する大きな利点が失われます。

代わりに、onChildLoaded 関数が呼び出されるたびに現在の観測可能な値を取得するように何かを設定する必要があります。これには、親コンポーネントのonDestroy機能の分解も必要になる可能性があります。これは珍しいことではなく、テンプレートに到達しないオブザーバブルを使用する場合など、これを実行する必要がある他のケースがあります。


9

サービス:

import {Injectable, EventEmitter} from "@angular/core";    

@Injectable()
export class DataService {
onGetData: EventEmitter = new EventEmitter();

getData() {
  this.http.post(...params).map(res => {
    this.onGetData.emit(res.json());
  })
}

成分:

import {Component} from '@angular/core';    
import {DataService} from "../services/data.service";       
    
@Component()
export class MyComponent {
  constructor(private DataService:DataService) {
    this.DataService.onGetData.subscribe(res => {
      (from service on .emit() )
    })
  }

  //To send data to all subscribers from current component
  sendData() {
    this.DataService.onGetData.emit(--NEW DATA--);
  }
}

8

親から子にデータを渡す方法は3つあります

  1. 共有可能なサービスを通じて、子供と共有したいデータをサービスに保存する必要があります
  2. 異なるデータを受信する必要がある場合は、Children Router Resolverを介して

    this.data = this.route.snaphsot.data['dataFromResolver'];
    
  3. 親から同じデータを受信する必要がある場合は、親ルーターリゾルバーを使用

    this.data = this.route.parent.snaphsot.data['dataFromResolver'];
    

注1:リゾルバーについては、こちらをご覧ください。リゾルバーの例と、リゾルバーをモジュールに登録し、リゾルバーからコンポーネントにデータを取得する方法もあります。リゾルバの登録は、親と子で同じです。

注2:ルーターからデータを取得できるように、ここでActivatedRoute について読むことができます。


1

この質問に続い、Angular 7.2では、履歴状態を使用して親から子にデータを渡すことができます。だからあなたは次のようなことができます

送信:

this.router.navigate(['action-selection'], { state: { example: 'bar' } });

取得:

constructor(private router: Router) {
  console.log(this.router.getCurrentNavigation().extras.state.example);
}

ただし、一貫性を保つように注意してください。たとえば、ルーターアウトレットを使用して、左側のバーにリストを表示し、右側に選択したアイテムの詳細を表示するとします。何かのようなもの:


アイテム1(x)| .................................................................

アイテム2(x)| ......選択したアイテムの詳細.......

アイテム3(x)| .................................................................

アイテム4(x)| .................................................................


ここで、すでにいくつかのアイテムをクリックしたとします。ブラウザの戻るボタンをクリックすると、前のアイテムの詳細が表示されます。しかし、その間に(x)をクリックしてリストからそのアイテムを削除した場合はどうなるでしょうか。次に、バッククリックを実行すると、削除されたアイテムの詳細が表示されます。

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