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 pantallas infantiles:

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 aparece el error en la consola web:

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

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

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

No parece que router-outlets funcione de la misma manera.

92
Zack 3 ene. 2017 a las 22:58

6 respuestas

La mejor respuesta
<router-outlet [node]="..."></router-outlet> 

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

Consulte también https://angular.io/guide / componente-interacción # padre-e-hijos-comunicarse-a través de un servicio

@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);
    }
}
79
DonVaughn 5 nov. 2019 a las 23:12

Hay 3 formas de pasar datos de padres a hijos

  1. A través del servicio que se puede compartir: debe almacenar en un servicio los datos que desea 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 de padre

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

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

Nota2: Puede leer acerca de ActivatedRoute aquí para poder obtener datos del enrutador

8
Rzv Razvan 22 nov. 2018 a las 08:41

Sí, puede configurar las entradas de los componentes que se muestran en las salidas de su enrutador , pero, como se menciona en otras respuestas, no puede pasar datos a través de una salida de enrutador a través del enlace de plantilla. Debe hacerlo mediante programación, lo que tendrá un costo (descrito en la advertencia a continuación).

Así es cómo:

(1) En el archivo de plantilla del padre, conéctese al evento activate del enrutador:

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

(2) En el archivo de mecanografía del padre, configure las entradas del componente hijo mediante programación:

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

La versión anterior de onOutletLoaded se simplifica 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.someValue = 123;
  } else if (component instanceof MyComponent2) {
    component.anotherValue = 456;
  }
}

¿Por qué puede preferirse este método sobre el 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 hacer el objeto intermediario (el servicio) y en muchos casos está más cerca de la "forma angular" porque puede continuar usando @Input en sus componentes secundarios. También es una buena opción para componentes ya existentes o de terceros que no desea o que no puede vincular estrechamente con ese servicio. Por otra parte...

Advertencia

La advertencia con este método es que dado que está pasando datos mediante programación, ya no tiene la opción de pasar datos observables a través del enlace de entrada de plantilla (¡sorpresa!). Eso significa que pierde la gran ventaja de administrar angularmente el ciclo de vida del observable cuando utiliza el patrón de tubería asíncrona.

En su lugar, deberá configurar algo para obtener los valores observables actuales siempre que se llame a la función onChildLoaded. Es probable que esto también requiera un desmantelamiento en la función onDestroy del componente principal. Esto no es nada inusual, hay otros casos en los que esto debe hacerse, pero esto aumentará la cantidad de código repetitivo en el componente principal.

9
DonVaughn 31 may. 2020 a las 12:10

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--);
  }
}
9
Community 20 jun. 2020 a las 09:12

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

Aquí, sin embargo, debemos recordar que estos objetos se pasan por referencia, por lo que si desea realizar un trabajo en el objeto en el elemento secundario y no afectar el objeto principal, sugeriría utilizar la solución de Günther. Pero si no importa, o en realidad es 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 (AND padre), inyecte el Servicio compartido en su constructor. Recuerde proporcionar el servicio en la matriz de proveedores a nivel de módulo si desea un servicio singleton en todos los componentes de ese módulo. Alternativamente, solo agregue el servicio en la matriz de proveedores en el padre solo , luego el padre y el niño compartirán 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;
}
43
AJT82 19 ago. 2017 a las 15:38

Siguiendo esta pregunta, en Angular 7.2 puede pasar datos de padre a hijo utilizando 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 consistente. Por ejemplo, suponga que desea mostrar una lista en una barra lateral izquierda y los detalles del elemento seleccionado a la derecha utilizando una salida de enrutador. Algo como:


Artículo 1 (x) | ..............................................

Artículo 2 (x) | ...... Detalles del elemento seleccionado .......

Artículo 3 (x) | ..............................................

Artículo 4 (x) | ..............................................


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

1
MTZ 13 mar. 2019 a las 09:06