web-dev-qa-db-fra.com

Angular 2 - Transfert de données au composant enfant après l'initialisation du parent

J'ai un composant parent qui récupère les données d'un serveur via un service. J'ai besoin que certaines de ces données soient transmises au composant Child.

J'ai essayé de transmettre ces données avec l'habituel @Input(), mais comme je m'y attendais, les données que je reçois dans le composant Child sont undefined.

Exemple

Composant parent

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

Composant enfant

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

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

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

J'ai gardé l'exemple simple afin de montrer ce que j'essaie de faire. Ma question est de savoir s'il est possible de transmettre des données à l'enfant après l'initialisation. Je ne suis pas sûr, mais une liaison de données bidirectionnelle aiderait-elle dans ce cas?

Plus d’investigation

Après les réponses postées, j'ai essayé d'utiliser le hook ngOnChanges lifecycle. Le problème que je rencontre est que la fonction ngOnChanges n'est pas activée lorsque les données sont mises à jour. Les données sont un objet, c'est pourquoi je pense que les modifications ne sont pas détectées.

Code mis à jour dans le composant enfant

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

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

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

J'ai essayé un test avec les exemples en direct sur Plunker de l'équipe Angular présentant les crochets du cycle de vie. Dans le test, j'ai mis à jour le composant OnChangesComponent afin qu'il transmette un objet pouvant être vu dans le fichier. Par exemple, lors de la mise à jour d'un objet, ngOnChanges ne détecte pas le changement (ceci est également montré dans ceci question )

https://plnkr.co/edit/KkqwpsYKXmRAx5DK4cnV

Il semble que ngDoCheck puisse aider à résoudre ce problème, mais il me semble que cela n’aidera pas les performances à long terme, car chaque changement est détecté par ce crochet de cycle de vie.

De plus, je sais que je pourrais probablement utiliser la @ViewChild() pour accéder au composant enfant et définir les variables dont j'ai besoin, mais je ne pense pas que cela ait beaucoup de sens pour ce cas d'utilisation.

Publier plus de code pour mieux expliquer

Composant parent

@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
    }
}

Composant enfant

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

C'est plus ou moins le code que j'ai. En gros, j'ai un parent qui gère les sélections à partir de composants enfants, puis je soumets ces sélections au service. Je dois transmettre certaines données à l'enfant, car j'essaie d'avoir l'objet renvoyé par l'enfant dans le format attendu par le service. Cela m'aiderait à ne pas créer l'objet entier attendu par le service dans le composant parent. De plus, cela rendrait l'enfant réutilisable dans le sens où il renvoie ce que le service attend.

Mettre à jour

J'apprécie que les utilisateurs postent toujours des réponses à cette question. Notez que le code qui a été posté ci-dessus a fonctionné pour moi. Le problème que j'avais était que dans un modèle particulier, j'avais une faute de frappe qui faisait en sorte que les données soient undefined.

J'espère que cela s'avère utile :)

33
Daniel Grima

Comme les données ne sont pas définies au début, vous pouvez les reporter avec * ngIf = 'data'

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

Ou vous pouvez implémenter ControlValueAccessor sur votre composant et le transmettre par ngModel avec ngModelChange

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

vous pouvez utiliser ngOnChanges pour obtenir les données comme ci-dessous,

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

OnChanges

Angular appelle sa méthode ngOnChanges chaque fois qu'elle détecte des modifications des propriétés d'entrée du composant (ou de la directive).

En savoir plus à ce sujet ici .

Mettre à jour

ngOnChanges ne se déclenche que si vous modifiez l'objet entier; si vous souhaitez simplement mettre à jour la propriété de l'objet lié, l'objet non entier, vous avez deux options,

  1. soit lier la propriété directement dans le modèle, assurez-vous d’utiliser ? comme {{childData?.propertyname}}

  2. ou vous pouvez créer une propriété get en fonction de l'objet childdata,

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

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

Voici le Plunker !

J'espère que cela t'aides!!

16
Madhu Ranjan

Au moment où votre ChildComponent (vous l'appelez aussi ParentComponent, mais je suppose que c'est une faute de frappe) est initialisé et exécute son ngOnInit, il n'y a pas de données disponibles dans votre parent car votre service sera toujours en train de récupérer vos données. Une fois que vos données sont là, elles seront transmises à l’enfant.

Ce que vous pouvez faire dépend de votre cas d'utilisation.

Si vous souhaitez simplement afficher les données dans votre modèle ChildComponent , vous pouvez utiliser l'opérateur elvis (?). Quelque chose comme: {{ childData?}} ou {{ childData?.myKeyName }}...

Si vous voulez faire d'autres choses, vous pouvez utiliser un setter dans votre ChildComponent. Il interceptera le changement d’entrée et vous donnera l’opportunité de modifier/tester/"faire ce que vous voudrez" avant d’en faire d’autres choses dans votre 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...
    }
}

ou utilisez ngOnChanges comme l'a mentionné Madhu.

Vous voudrez peut-être aussi jeter un coup d'œil à l'entrée du livre de recettes pour Communication entre composants dans la documentation officielle.

3
benny_boe
 
 
 `@Composant ({
 Sélecteur: 'enfant test', 
 Modèle: '
 
 `
}) 
 
 La classe d'exportation ChildComponent implémente OnChanges {
 @Input () childData: any; 
 
 ngOnChanges ( ) {
 console.log (childData); // enregistre les indéfinis 
} 
} `

Ceci est censé être comme ça non? J'espère que cela pourrait être le problème


`ngOnChanges () { console.log (this.childData); // référence avec ceci }`
2
Nambi N Rajan

Je suppose que l'enfant obtient un sous-ensemble des données relatives aux parents, éventuellement d'une action de l'utilisateur (comme le chargement d'un formulaire lorsqu'un utilisateur clique sur une ligne de la grille). Dans ce cas, j'utiliserais un événement.

Demandez au parent de déclencher un événement, peut-être dataSelected, et le contenu de l'événement correspondra aux données sélectionnées.

Ensuite, demandez à l'enfant d'écouter ces événements et, une fois activé, chargez les données de l'événement dans le modèle.

Cela aidera à découpler vos composants, ce qui est toujours une bonne idée.

0
R. McIntosh