web-dev-qa-db-ja.com

Angular 2-親の初期化後に子コンポーネントにデータを渡す

サービスを介してサーバーからデータを取得する親コンポーネントがあります。このデータの一部をChildコンポーネントに渡す必要があります。

私はこのデータを通常の@Input()で渡そうとしましたが、Childコンポーネントで取得するデータはundefinedであると予想していました。

親コンポーネント

@Component({
    selector: 'test-parent',
    template: `
        <test-child [childData]="data"></test-child>
    `
})

export class ParentComponent implements OnInit {
    data: any;

    ngOnInit() {
        this.getData();
    }

    getData() {
        // calling service and getting data
        this.testService.getData()
            .subscribe(res => {
                this.data = res;
            });
    }
}

子コンポーネント

@Component({
    selector: 'test-child',
    template: `
        <!-- Content for child.... -->
    `
})

export class ChildComponent implements OnInit {
    @Input() childData: any;

    ngOnInit() {
        console.log(childData); // logs undefined
    }
}

私がやろうとしていることを示すために、この例は単純にしています。私の質問は、初期化後にデータを子に渡すことができるかどうかです。よくわかりませんが、この場合は双方向のデータバインディングが役立ちますか?

さらなる調査

投稿された回答に従って、ngOnChangesライフサイクルフックを使用してみました。私が抱えている問題は、データが更新されたときにngOnChanges関数が起動されないことです。データはオブジェクトであるため、変更が検出されないと考えています。

子コンポーネントの更新されたコード

@Component({
    selector: 'test-child',
    template: `
        <!-- Content for child.... -->
    `
})

export class ChildComponent implements OnChanges {
    @Input() childData: any;

    ngOnChanges() {
        console.log(childData); // logs undefined
    }
}

ライフサイクルフックを紹介するAngularチームのPlunkerのLive Examplesでテストを試みました。テストでは、OnChangesComponentを更新してオブジェクトを渡しました。オブジェクトを更新すると、ngOnChangesは変更を検出しません。 (これはこれにも示されています question

https://plnkr.co/edit/KkqwpsYKXmRAx5DK4cnV

ngDoCheckがこの問題の解決に役立つ可能性がありますが、このライフサイクルフックによってすべての変更が検出されるため、長期的にはパフォーマンスが向上しないようです。

また、おそらく@ViewChild()を使用して子コンポーネントにアクセスし、必要な変数を設定できることを知っていますが、このユースケースではあまり意味がないと思います。

より良い説明に役立つコードを投稿する

親コンポーネント

@Component({
    selector: 'test-parent',
    template: `
        <test-child [childType]="numbers" [childData]="data" (pickItem)="pickNumber($event)">
            <template let-number>
                <span class="number" [class.is-picked]="number.isPicked">
                    {{ number.number }}
                </span>
            </template>
        </test-child>
        <test-child [childType]="letters" [childData]="data" (pickItem)="pickLetter($event)">
            <template let-letter>
                <span class="letter" [class.is-picked]="letter.isPicked">
                    {{ letter.displayName }}
                </span>
            </template>
        </test-child>
        <button (click)="submitSelections()">Submit Selection</button>
    `
})

export class ParentComponent implements OnInit {
    data: any;
    numbersData: any;
    lettersData: any;

    ngOnInit() {
        this.getData();
    }

    getData() {
        // calling service and getting data for the game selections
        this.testService.getData()
            .subscribe(res => {
                this.data = res;
                setChildData(this.data);
            });
    }

    setChildData(data: any) {
        for(let i = 0; i < data.sections.length; i++) {
            if(data.sections[i].type === 'numbers') {
                this.numbersData = data.sections[i];
            }

            if(data.sections[i].type === 'letters') {
                this.lettersData = data.sections[i];
            }
        }
    }

    pickNumber(pickedNumbers: any[]) {
        this.pickedNumbers = pickedNumbers;
    }

    pickLetter(pickedLetters: any[]) {
        this.pickedLetters = pickedLetters;
    }

    submitSelections() {
        // call service to submit selections
    }
}

子コンポーネント

@Component({
    selector: 'test-child',
    template: `
        <!--
            Content for child...
            Showing list of items for selection, on click item is selected
        -->
    `
})

export class ChildComponent implements OnInit {
    @Input() childData: any;
    @Output() onSelection = new EventEmitter<Selection>;

    pickedItems: any[];

    // used for click event
    pickItem(item: any) {
        this.pickedItems.Push(item.number);

        this.onSelection.emit(this.pickedItems);
    }
}

それは多かれ少なかれ私が持っているコードです。基本的に、子コンポーネントからの選択を処理する親があり、これらの選択をサービスに送信します。サービスが期待する形式で子が返すオブジェクトを取得しようとしているため、特定のデータを子に渡す必要があります。これは、親コンポーネントのサービスが期待するオブジェクト全体を作成しないようにするのに役立ちます。また、サービスが期待するものを送り返すという意味で、子を再利用可能にします。

更新

ユーザーがこの質問に対する回答をまだ投稿していることに感謝します。上記で投稿されたコードは私のために働いたことに注意してください。私が抱えていた問題は、特定のテンプレートにデータがundefinedになるタイプミスがあったことです。

それが役立つことを証明していることを願っています:)

33
Daniel Grima

データは開始時に未定義なので、* ngIf = 'data'で延期できます。

<div *ngIf='data'>
   <test-child [childData]="data"></test-child>
</div>

または、コンポーネントにControlValueAccessorを実装し、ngModelChangeでngModelに渡すことができます

<test-child [ngModel]="data?" (ngModelChange)="data? ? data= $event : null"></test-child>
29
Mopa

ngOnChangesを使用して、以下のようなデータを取得できます。

 ngOnChanges() {
   if(!!childData){         
        console.log(childData);         
   }
 }

OnChanges

Angularは、コンポーネント(またはディレクティブ)の入力プロパティへの変更を検出するたびに、ngOnChangesメソッドを呼び出します。

詳細については、こちらをご覧ください こちら

更新

ngOnChangesは、オブジェクト全体を変更した場合にのみ起動します。2つのオプションがあるオブジェクト全体ではなく、バインドされたオブジェクトのプロパティを更新する場合、

  1. プロパティをテンプレートに直接バインドするか、?のような{{childData?.propertyname}}を使用してください

  2. または、childdataオブジェクトに応じてgetプロパティを作成できます。

    get prop2(){
      return this.childData.prop2;
    }
    

Copmleteの例、

import { Component, Input } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `<h1>Hello {{name}}</h1>
  <child-component [childData]='childData' ></child-component>
  `
})
export class AppComponent { 
  name = 'Angular'; 
  childData = {};
  constructor(){
    // set childdata after 2 second
    setTimeout(() => {
      this.childData = {
        prop1 : 'value of prop1',
        prop2 : 'value of prop2'
      }
    }, 2000)
  }
}

@Component({
  selector: 'child-component',
  template: `
    prop1 : {{childData?.prop1}}
    <br />
    prop2 : {{prop2}}
  `
})
export class ChildComponent { 
  @Input() childData: {prop1: string, prop2: string};

  get prop2(){
    return this.childData.prop2;
  }
}

Plunker

お役に立てれば!!

16
Madhu Ranjan

ChildComponent(ParentComponentとも呼ばれますが、タイプミスだと思います)が初期化され、ngOnInitを実行する時点で、サービスがデータを取得しているため、親にデータはありません。データがそこにあると、子にプッシュされます。

できることは、ユースケースによって異なります。

ChildComponenttemplateにデータを表示するだけであれば、elvis演算子(?)を使用できます。次のようなもの:{{ childData?}}または{{ childData?.myKeyName }}...

他のことをしたい場合は、ChildComponentでsetterを使用できます。入力の変更をインターセプトし、ChildComponentで他の操作を行う前に、変更/テスト/「必要な操作を行う」機会を与えます。例:

@Component({
    selector: 'test-child',
    template: `
        <!-- Content for child.... -->
    `
})

export class ChildComponent implements OnInit {
    private _childData: any;
    @Input()
    set childData(parentData: any) {
        // every time the data from the parent changes this will run
        console.log(parnetData);
        this._childData = parentData; // ...or do some other crazy stuff
    }
    get childData(): any { return this._childData; }

    ngOnInit() {
      // We don't need it for that...
    }
}

または、Madhuが述べたngOnChangesを使用します。

公式ドキュメントの Component Communication のクックブックエントリもご覧ください。

3
benny_boe
 
 
 `@Component({
 selector: 'test-child'、
 template:` 
 
 `
})
 
 exportクラスChildComponentはOnChangesを実装します{
 @Input()childData:any; 
 
 ngOnChanges( ){
 console.log(childData); //未定義のログ
} 
} `

これはこのようなものではないはずですか?これが問題になることを願っています


`ngOnChanges(){ console.log(this.childData); //これで参照します }`
2
Nambi N Rajan

私は、子が親データのサブセットを、ユーザーアクション(ユーザーがグリッド行をクリックしたときにフォームをロードするなど)から潜在的に取得していると想定しています。この場合、イベントを使用します。

親にイベント(おそらくdataSelected)を発生させ、イベントの内容は選択されたデータになります。

次に、子にそれらのイベントをリッスンさせ、発生時にイベントからモデルにデータをロードします。

これはコンポーネントを分離するのに役立ちますが、これは常に良い考えです。

0
R. McIntosh