Passando dados para componentes filhos “roteadores”


100

Eu tenho um componente pai que vai para o servidor e busca um 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);
                }
            );
    }

E em uma das várias telas infantis:

export class ChildDisplay implements OnInit{

    @Input()
    node: Node;

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

}

Não parece que posso simplesmente injetar dados no router-outlet. Parece que recebo o erro no console da web:

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

Isso faz sentido, mas como eu faria o seguinte:

  1. Pegue os dados do "nó" do servidor, de dentro do componente pai?
  2. Passar os dados que recuperei do servidor para a tomada do roteador filho?

Não parece router-outletsfuncionar da mesma maneira.

Respostas:


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

é apenas inválido. O componente adicionado pelo roteador é adicionado como irmão <router-outlet>e não o substitui.

Veja também 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);
    }
}

Isso pode ser usado para fornecer uma identificação para a criança? Eu tentei mudar tudo para nodeonde está como um tipo stringe não parece funcionar ou estou fazendo algo errado
Wanjia

Você pode passar quaisquer dados para o componente filho, mas o componente filho precisa definir o próprio id. Você pode tentar obter um ElementRefde um evento na saída do roteador para usá-lo para definir o id, mas não sei de cor se ou como fazer isso (apenas no meu telefone)
Günter Zöchbauer

46

A resposta de Günters é ótima, só quero apontar outra maneira sem usar Observáveis.

Aqui, porém, devemos lembrar que esses objetos são passados ​​por referência, então se você quiser fazer algum trabalho no objeto da criança e não afetar o objeto pai, eu sugeriria usar a solução de Günther. Mas se isso não importa ou realmente é o comportamento desejado , sugiro o seguinte.

@Injectable()
export class SharedService {

    sharedNode = {
      // properties
    };
}

Em seu pai, você pode atribuir o valor:

this.sharedService.sharedNode = this.node;

E em seus filhos (E pai), injete o serviço compartilhado em seu construtor. Lembre-se de fornecer o serviço na matriz de provedores de nível de módulo se desejar um serviço singleton em todos os componentes desse módulo. Como alternativa, basta adicionar o serviço na matriz de provedores apenas no pai , então o pai e o filho compartilharão a mesma instância de serviço.

node: Node;

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

E como newman gentilmente apontou, você também pode ter this.sharedService.sharedNodeno template html ou um getter:

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

5
você pode / deve vincular a sharedService.sharedNode em html diretamente. Dessa forma, as atualizações em sharedNode serão mantidas em sincronia.
newman

11

Sim, você pode definir entradas de componentes exibidos por meio de saídas do roteador . Infelizmente, você precisa fazer isso de forma programática, conforme mencionado em outras respostas. Há uma grande advertência quando os observáveis ​​estão envolvidos (descrito abaixo).

Veja como:

(1) Conecte-se ao activateevento da tomada do roteador no modelo pai:

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

(2) Mude para o arquivo de texto digitado do pai e defina as entradas do componente filho programaticamente sempre que forem ativados:

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

A versão acima do onOutletLoadedé simplificada para maior clareza, mas só funciona se você puder garantir que todos os componentes filhos tenham exatamente as mesmas entradas que você está atribuindo. Se você tiver componentes com entradas diferentes, use protetores de tipo:

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

Por que esse método pode ser preferido ao método de serviço?

Nem esse método nem o método de serviço são "a maneira certa" de se comunicar com os componentes filhos (ambos os métodos se distanciam da associação de modelo pura), portanto, você só precisa decidir qual forma parece mais apropriada para o projeto.

Esse método, entretanto, permite que você evite fazer o objeto intermediário (o serviço), que une fortemente o serviço e os componentes filho. Em muitos casos, isso parece mais "angular" porque você pode continuar passando dados para seus componentes filhos por meio de @Inputs. Também é uma boa opção para componentes já existentes ou de terceiros que você não deseja ou não pode unir fortemente a esse serviço. Por outro lado, pode parecer menos angular quando ...

Embargo

A ressalva com este método é que, uma vez que você está passando dados programaticamente, você não tem mais a opção de passar dados observáveis ​​em seu modelo (surpresa!). Isso significa que você perde a grande vantagem do gerenciamento angular do ciclo de vida do observável para você ao usar o padrão de pipe assíncrono.

Em vez disso, você precisará configurar algo para obter os valores observáveis ​​atuais sempre que a onChildLoaded função for chamada. Isso provavelmente também exigirá alguma desmontagem na onDestroyfunção do componente pai . Isso não é nada incomum, há outros casos em que isso precisa ser feito, como ao usar um observável que nem chega ao modelo.


9

Serviço:

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

Existem 3 maneiras de passar dados dos pais para os filhos

  1. Por meio de serviço compartilhável: você deve armazenar em um serviço os dados que gostaria de compartilhar com os filhos
  2. Através do Children Router Resolver se você tiver que receber dados diferentes

    this.data = this.route.snaphsot.data['dataFromResolver'];
    
  3. Por meio do Resolvedor do Roteador Pai, caso precise receber os mesmos dados dos pais

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

Nota 1: você pode ler sobre o resolvedor aqui . Também há um exemplo de resolvedor e como registrar o resolvedor no módulo e, em seguida, recuperar dados do resolvedor para o componente. O registro do resolvedor é o mesmo no pai e no filho.

Nota 2: Você pode ler sobre ActivatedRoute aqui para poder obter dados do roteador


1

Seguindo esta pergunta, no Angular 7.2 você pode passar dados de pai para filho usando o estado do histórico. Então você pode fazer algo como

Enviar:

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

Recuperar:

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

Mas tome cuidado para ser consistente. Por exemplo, suponha que você deseja exibir uma lista em uma barra do lado esquerdo e os detalhes do item selecionado à direita usando uma tomada de roteador. Algo como:


Item 1 (x) | ..............................................

Item 2 (x) | ...... Detalhes do item selecionado .......

Item 3 (x) | ..............................................

Item 4 (x) | ..............................................


Agora, suponha que você já tenha clicado em alguns itens. Clicar nos botões de voltar do navegador exibirá os detalhes do item anterior. Mas e se, enquanto isso, você clicar no (x) e excluir esse item da sua lista? Em seguida, clicar em voltar mostra os detalhes de um item excluído.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.