Pasar datos a componentes secundarios "enrutador-salida"

103

Tengo un componente principal que va al servidor y busca un objeto:

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

Y en una de varias exhibiciones para niños:

export class ChildDisplay implements OnInit{

    @Input()
    node: Node;

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

}

No parece que pueda simplemente inyectar datos en router-outlet. Parece que recibo el error en la consola web:

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

Esto tiene algo de sentido, pero ¿cómo haría lo siguiente?

  1. ¿Tomar los datos del "nodo" del servidor, desde el componente principal?
  2. ¿Pasar los datos que he recuperado del servidor a la salida del enrutador secundario?

No parece router-outletsfuncionar de la misma manera.

Zack
fuente

Respuestas:

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

es simplemente inválido. El componente agregado por el enrutador se agrega como hermano <router-outlet>y no lo reemplaza.

Consulte también 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
fuente
¿Se puede usar para darle una identificación al niño? He intentado cambiar todo el lugar nodedonde es como un tipostring y no parece funcionar o estoy haciendo algo mal
Wanjia
Puede pasar cualquier dato al componente secundario, pero el componente secundario debe establecer la identificación. Puede intentar obtener un ElementRefevento de la salida del enrutador para usarlo para configurar la identificación, pero no sé de memoria si o cómo hacerlo (solo en mi teléfono)
Günter Zöchbauer
46

La respuesta de Günter es excelente, solo quiero señalar otra forma sin usar Observables.

Sin embargo, aquí debemos recordar que estos objetos se pasan por referencia, por lo que si desea trabajar un poco en el objeto en el niño y no afectar el objeto principal, le sugiero que use la solución de Günther. Pero si no importa, o si en realidad es un comportamiento deseado , sugeriría lo siguiente.

@Injectable()
export class SharedService {

    sharedNode = {
      // properties
    };
}

En su padre puede asignar el valor:

this.sharedService.sharedNode = this.node;

Y en sus hijos (Y padre), inyecte el Servicio compartido en su constructor. Recuerde proporcionar el servicio en la matriz de proveedores de nivel de módulo si desea un servicio singleton en todos los componentes de ese módulo. Por otra parte, sólo tiene que añadir el servicio en la matriz de proveedores en el padre solamente , entonces el padre e hijo comparten la misma instancia de servicio.

node: Node;

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

Y como Newman señaló amablemente, también puede tener this.sharedService.sharedNode en la plantilla html o un getter:

get sharedNode(){
  return this.sharedService.sharedNode;
}
AJT82
fuente
5
puede / debe enlazar a sharedService.sharedNode en html directamente. De esta forma, las actualizaciones en sharedNode se mantendrán sincronizadas.
Newman
12

Sí, puede configurar las entradas de los componentes que se muestran a través de las salidas del enrutador . Lamentablemente, debe hacerlo mediante programación, como se menciona en otras respuestas. Hay una gran advertencia sobre eso cuando se trata de observables (que se describen a continuación).

Así es cómo:

(1) Conéctese al activateevento del router-outlet en la plantilla principal:

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

(2) Cambie al archivo de texto mecanografiado del padre y configure las entradas del componente secundario mediante programación cada vez que se activen:

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

La versión anterior de onOutletLoadedestá simplificada para mayor claridad, pero solo funciona si puede garantizar que todos los componentes secundarios tengan exactamente las mismas entradas que está asignando. Si tiene componentes con diferentes entradas, use protectores de tipo:

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

¿Por qué se puede preferir este método al método de servicio?

Ni este método ni el método de servicio son "la forma correcta" de comunicarse con los componentes secundarios (ambos métodos se alejan del enlace de plantilla puro), por lo que solo tiene que decidir qué forma se siente más apropiada para el proyecto.

Sin embargo, este método le permite evitar crear el objeto intermediario (el servicio), que combina estrechamente los componentes de servicio y secundarios. En muchos casos, esto se parece más al "modo angular" porque puede continuar pasando datos a sus componentes secundarios a través de @Inputs. También es una buena opción para componentes ya existentes o de terceros que no desea o no puede acoplar estrechamente con ese servicio. Por otro lado, puede sentirse menos como la forma angular cuando ...

Consideración

La advertencia con este método es que, dado que está pasando datos de forma programática, ya no tiene la opción de pasar datos observables en su plantilla (¡sorpresa!). Eso significa que pierde la gran ventaja de administrar angularmente el ciclo de vida del observable cuando usa el patrón pipe-async.

En su lugar, deberá configurar algo para obtener los valores observables actuales cada vez onChildLoaded que se llame a la función. Es probable que esto también requiera algún desmontaje en la onDestroyfunción del componente principal . Esto no es nada inusual, hay otros casos en los que es necesario hacer esto, como cuando se usa un observable que ni siquiera llega a la plantilla.

Don Vaughn
fuente
9

Servicio:

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

Hay 3 formas de transmitir datos de padres a hijos

  1. A través del servicio compartible: debe almacenar en un servicio los datos que le gustaría compartir con los niños.
  2. A través de Children Router Resolver si tiene que recibir datos diferentes

    this.data = this.route.snaphsot.data['dataFromResolver'];
    
  3. A través de Parent Router Resolver si tiene que recibir los mismos datos del padre

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

Note1: Puede leer sobre el resolutor aquí . También hay un ejemplo de resolutor y cómo registrar el resolutor en el módulo y luego recuperar los datos del resolutor en el componente. El registro del resolutor es el mismo para el padre y el hijo.

Nota 2: Puede leer sobre ActivatedRoute aquí para poder obtener datos del enrutador

Rzv Razvan
fuente
1

Siguiendo esta pregunta, en Angular 7.2 puede pasar datos de padres a hijos usando el estado del historial. Entonces puedes hacer algo como

Enviar:

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

Recuperar:

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

Pero tenga cuidado de ser coherente. Por ejemplo, suponga que desea mostrar una lista en una barra lateral izquierda y los detalles del elemento seleccionado a la derecha utilizando un enrutador. Algo como:


Elemento 1 (x) | ..............................................

Elemento 2 (x) | ...... Detalles del artículo seleccionado .......

Tema 3 (x) | ..............................................

Ítem ​​4 (x) | ..............................................


Ahora, suponga que ya ha hecho clic en algunos elementos. Al hacer clic en los botones de retroceso del navegador, se mostrarán los detalles del elemento anterior. Pero, ¿qué pasa si, mientras tanto, ha hecho clic en la (x) y ha eliminado de su lista ese elemento? Luego, al realizar el clic de retroceso, se le mostrarán los detalles de un elemento eliminado.

MTZ
fuente