Passaggio di dati a componenti figlio "router-presa"


100

Ho un componente genitore che va al server e recupera un oggetto:

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

E in uno dei tanti display per bambini:

export class ChildDisplay implements OnInit{

    @Input()
    node: Node;

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

}

Non sembra che io possa semplicemente iniettare dati nel file router-outlet. Sembra che riceva l'errore nella console web:

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

In qualche modo ha senso, ma come farei quanto segue:

  1. Prendi i dati del "nodo" dal server, dall'interno del componente principale?
  2. Passare i dati che ho recuperato dal server alla presa del router figlio?

Non sembra router-outletsfunzionare allo stesso modo.

Risposte:


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

è solo non valido. Il componente aggiunto dal router viene aggiunto come fratello <router-outlet>e non lo sostituisce.

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

Può essere utilizzato per fornire un documento d'identità al bambino? Ho provato a cambiare tutto il punto in nodecui è come un tipo stringe non sembra funzionare o sto facendo qualcosa di sbagliato
Wanjia

Puoi passare qualsiasi dato al componente figlio ma il componente figlio deve impostare l'id stesso. Puoi provare a ottenere un ElementRefda un evento dalla presa del router per usarlo per impostare l'id ma non so a memoria se o come farlo (solo sul mio telefono)
Günter Zöchbauer

46

La risposta di Günters è ottima, voglio solo sottolineare un altro modo senza usare Observables.

Qui però dobbiamo ricordare che questi oggetti sono passati per riferimento, quindi se vuoi fare del lavoro sull'oggetto nel bambino e non influenzare l'oggetto genitore, ti suggerirei di usare la soluzione di Günther. Ma se non importa, o in realtà è un comportamento desiderato , suggerirei quanto segue.

@Injectable()
export class SharedService {

    sharedNode = {
      // properties
    };
}

Nel tuo genitore puoi assegnare il valore:

this.sharedService.sharedNode = this.node;

E nei tuoi figli (E genitore), inserisci il servizio condiviso nel tuo costruttore. Ricordarsi di fornire il servizio a livello di modulo array di provider se si desidera un servizio singleton su tutti i componenti di quel modulo. In alternativa, aggiungi semplicemente il servizio nell'array dei provider solo nel genitore , quindi il genitore e il figlio condivideranno la stessa istanza di servizio.

node: Node;

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

E come Newman ha gentilmente sottolineato, puoi anche avere this.sharedService.sharedNodenel template html o un getter:

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

5
puoi / dovresti collegarti a sharedService.sharedNode in html direttamente. In questo modo, gli aggiornamenti in sharedNode verranno mantenuti sincronizzati.
newman

11

Sì, è possibile impostare gli ingressi dei componenti visualizzati tramite le prese del router . Purtroppo, devi farlo in modo programmatico, come menzionato in altre risposte. C'è un grande avvertimento a questo quando sono coinvolti gli osservabili (descritti di seguito).

Ecco come:

(1) Collegati activateall'evento della presa del router nel modello principale:

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

(2) Passa al file dattiloscritto del genitore e imposta gli input del componente figlio in modo programmatico ogni volta che vengono attivati:

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

La versione precedente di onOutletLoadedè semplificata per chiarezza, ma funziona solo se è possibile garantire che tutti i componenti figlio abbiano esattamente gli stessi input che si stanno assegnando. Se si dispone di componenti con ingressi diversi, utilizzare protezioni di tipo:

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

Perché questo metodo può essere preferito al metodo di servizio?

Né questo metodo né il metodo del servizio sono "il modo giusto" per comunicare con i componenti figli (entrambi i metodi si allontanano dal semplice binding di modelli), quindi devi solo decidere quale sia il modo più appropriato per il progetto.

Questo metodo, tuttavia, consente di evitare di creare l'oggetto intermediario (il servizio), che accoppia strettamente il servizio e le componenti figlio. In molti casi questo sembra più vicino alla "via angolare" perché puoi continuare a trasmettere dati ai tuoi componenti figlio tramite @Inputs. È anche adatto per componenti già esistenti o di terze parti che non vuoi o non puoi accoppiare strettamente con quel servizio. D'altra parte, potrebbe sembrare meno angolare quando ...

Avvertimento

L'avvertenza con questo metodo è che poiché stai passando i dati in modo programmatico, non hai più la possibilità di passare dati osservabili nel tuo modello (sorpresa!). Ciò significa che si perde l'enorme vantaggio della gestione angolare del ciclo di vita dell'osservabile quando si utilizza il modello pipe-async.

Invece, dovrai impostare qualcosa per ottenere i valori osservabili correnti ogni volta che onChildLoaded viene chiamata la funzione. Ciò richiederà probabilmente anche un certo smontaggio nella onDestroyfunzione del componente genitore . Non è niente di troppo insolito, ci sono altri casi in cui è necessario farlo, come quando si utilizza un osservabile che non arriva nemmeno al modello.


9

Servizio:

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

Componente:

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

Esistono 3 modi per trasferire i dati da genitore a figlio

  1. Attraverso un servizio condivisibile: dovresti memorizzare in un servizio i dati che vorresti condividere con i bambini
  2. Tramite Children Router Resolver se è necessario ricevere dati diversi

    this.data = this.route.snaphsot.data['dataFromResolver'];
    
  3. Tramite Parent Router Resolver se devi ricevere gli stessi dati dal genitore

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

Nota 1: puoi leggere informazioni sul resolver qui . C'è anche un esempio di resolver e su come registrare il resolver nel modulo e quindi recuperare i dati dal resolver nel componente. La registrazione del resolver è la stessa per genitore e figlio.

Nota 2: puoi leggere su ActivatedRoute qui per essere in grado di ottenere dati dal router


1

Seguendo questa domanda, in Angular 7.2 puoi passare i dati da genitore a figlio utilizzando lo stato della cronologia. Quindi puoi fare qualcosa di simile

Spedire:

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

Recuperare:

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

Ma fai attenzione a essere coerente. Ad esempio, supponi di voler visualizzare un elenco su una barra laterale sinistra e i dettagli dell'elemento selezionato sulla destra utilizzando una presa del router. Qualcosa di simile a:


Voce 1 (x) | ..............................................

Voce 2 (x) | ...... Dettagli elemento selezionato .......

Voce 3 (x) | ..............................................

Voce 4 (x) | ..............................................


Supponiamo ora di aver già cliccato su alcuni elementi. Facendo clic sui pulsanti Indietro del browser verranno visualizzati i dettagli dell'elemento precedente. Ma cosa succede se, nel frattempo, hai cliccato sulla (x) e cancellato dalla tua lista quell'elemento? Quindi, eseguendo il clic all'indietro, verranno visualizzati i dettagli di un elemento eliminato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.