Transmission de données aux composants enfants «sortie du routeur»

102

J'ai un composant parent qui va au serveur et récupère un objet:

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

Et dans l'un des nombreux présentoirs pour enfants:

export class ChildDisplay implements OnInit{

    @Input()
    node: Node;

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

}

Il ne semble pas que je puisse simplement injecter des données dans le fichier router-outlet. Il semble que j'obtienne l'erreur dans la console Web:

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

Cela a un peu de sens, mais comment pourrais-je faire ce qui suit:

  1. Récupérer les données "noeud" du serveur, depuis le composant parent?
  2. Passer les données que j'ai récupérées du serveur dans la sortie du routeur enfant?

Cela ne semble pas router-outletsfonctionner de la même manière.

Zack
la source

Réponses:

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

est tout simplement invalide. Le composant ajouté par le routeur est ajouté en tant que frère <router-outlet>et ne le remplace pas.

Voir aussi 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);
    }
}
Günter Zöchbauer
la source
Cela peut-il être utilisé pour donner une pièce d'identité à l'enfant? J'ai essayé de changer tout l' nodeendroit où il est en tant que type stringet cela ne semble pas fonctionner ou je fais quelque chose de mal
Wanjia
Vous pouvez transmettre toutes les données au composant enfant, mais le composant enfant doit définir l'ID lui-même. Vous pouvez essayer d'obtenir un ElementRefévénement à partir de la prise du routeur pour l'utiliser pour définir l'identifiant, mais je ne sais pas par cœur si ou comment le faire (uniquement sur mon téléphone)
Günter Zöchbauer
46

La réponse de Günters est excellente, je veux juste souligner une autre façon sans utiliser Observables.

Ici, nous devons cependant nous rappeler que ces objets sont passés par référence, donc si vous voulez faire un travail sur l'objet dans l'enfant et ne pas affecter l'objet parent, je suggérerais d'utiliser la solution de Günther. Mais si cela n'a pas d'importance, ou s'il s'agit en fait d' un comportement souhaité , je suggérerais ce qui suit.

@Injectable()
export class SharedService {

    sharedNode = {
      // properties
    };
}

Dans votre parent, vous pouvez attribuer la valeur:

this.sharedService.sharedNode = this.node;

Et dans vos enfants (ET parent), injectez le service partagé dans votre constructeur. N'oubliez pas de fournir le service au niveau du tableau des fournisseurs de niveau module si vous voulez un service singleton sur tous les composants de ce module. Sinon, ajoutez simplement le service dans le tableau des fournisseurs dans le parent uniquement , puis le parent et l'enfant partageront la même instance de service.

node: Node;

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

Et comme newman l'a gentiment souligné, vous pouvez également avoir this.sharedService.sharedNodedans le modèle html ou un getter:

get sharedNode(){
  return this.sharedService.sharedNode;
}
AJT82
la source
5
vous pouvez / devez vous lier directement à sharedService.sharedNode en html. De cette façon, les mises à jour de sharedNode seront synchronisées.
newman
11

Oui, vous pouvez définir les entrées des composants affichés via les prises de routeur . Malheureusement, vous devez le faire par programme, comme mentionné dans d'autres réponses. Il y a une grande mise en garde à cela lorsque des observables sont impliqués (décrit ci-dessous).

Voici comment:

(1) Connectez-vous à l' activateévénement du routeur-sortie dans le modèle parent:

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

(2) Basculez vers le fichier dactylographié du parent et définissez les entrées du composant enfant par programme à chaque fois qu'ils sont activés:

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

La version ci-dessus de onOutletLoadedest simplifiée pour plus de clarté, mais ne fonctionne que si vous pouvez garantir que tous les composants enfants ont exactement les mêmes entrées que vous affectez. Si vous avez des composants avec des entrées différentes, utilisez des protections de type:

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

Pourquoi cette méthode peut-elle être préférée à la méthode de service?

Ni cette méthode ni la méthode de service ne sont «la bonne façon» de communiquer avec les composants enfants (les deux méthodes s'éloignent de la liaison de modèle pure), il vous suffit donc de décider de la manière la plus appropriée pour le projet.

Cependant, cette méthode vous permet d'éviter de créer l'objet intermédiaire (le service), qui couple étroitement le service et les composants enfants. Dans de nombreux cas, cela semble plus proche de la «voie angulaire» car vous pouvez continuer à transmettre des données à vos composants enfants via @Inputs. C'est également un bon choix pour les composants déjà existants ou tiers que vous ne voulez pas ou ne pouvez pas associer étroitement à ce service. D'un autre côté, cela peut ressembler moins à la manière angulaire lorsque ...

Caveat

La mise en garde avec cette méthode est que puisque vous passez des données par programme, vous n'avez plus la possibilité de transmettre des données observables dans votre modèle (surprise!). Cela signifie que vous perdez l'énorme avantage de la gestion angulaire du cycle de vie de l'observable pour vous lorsque vous utilisez le modèle pipe-async.

Au lieu de cela, vous devrez configurer quelque chose pour obtenir les valeurs observables actuelles chaque fois que la onChildLoaded fonction est appelée. Cela nécessitera probablement également un démontage de la onDestroyfonction du composant parent . Ce n'est rien de trop inhabituel, il y a d'autres cas où cela doit être fait, comme lors de l'utilisation d'un observable qui n'atteint même pas le modèle.

Don Vaughn
la source
9

Un service:

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

Composant:

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--);
  }
}
egor.xyz
la source
8

Il existe 3 façons de transmettre des données du parent aux enfants

  1. Grâce à un service partageable: vous devez stocker dans un service les données que vous souhaitez partager avec les enfants
  2. Via le résolveur de routeur pour enfants si vous devez recevoir des données différentes

    this.data = this.route.snaphsot.data['dataFromResolver'];
    
  3. Via le résolveur de routeur parent si vous devez recevoir les mêmes données du parent

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

Remarque 1: Vous pouvez lire sur le résolveur ici . Il existe également un exemple de résolveur et comment enregistrer le résolveur dans le module, puis récupérer les données du résolveur dans le composant. L'enregistrement du résolveur est le même sur le parent et l'enfant.

Remarque 2: vous pouvez lire sur ActivatedRoute ici pour pouvoir obtenir des données du routeur

Rzv Razvan
la source
1

Suite à cette question, dans Angular 7.2, vous pouvez transmettre des données de parent à enfant en utilisant l'état de l'historique. Donc tu peux faire quelque chose comme

Envoyer:

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

Récupérer:

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

Mais attention à être cohérent. Par exemple, supposons que vous souhaitiez afficher une liste sur une barre latérale gauche et les détails de l'élément sélectionné sur la droite en utilisant une sortie de routeur. Quelque chose comme:


Point 1 (x) | ..............................................

Point 2 (x) | ...... Détails de l'élément sélectionné .......

Point 3 (x) | ..............................................

Point 4 (x) | ..............................................


Maintenant, supposons que vous ayez déjà cliqué sur certains éléments. Cliquez sur les boutons de retour du navigateur pour afficher les détails de l'élément précédent. Mais que se passe-t-il si, entre-temps, vous avez cliqué sur le (x) et supprimez cet élément de votre liste? Ensuite, effectuez le clic arrière, vous montrera les détails d'un élément supprimé.

MTZ
la source