Передача данных в дочерние компоненты «роутер-розетка»

112

У меня есть родительский компонент, который отправляется на сервер и получает объект:

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

И в одном из нескольких детских дисплеев:

export class ChildDisplay implements OnInit{

    @Input()
    node: Node;

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

}

Не похоже, что я могу просто ввести данные в файл router-outlet. Похоже, я получаю ошибку в веб-консоли:

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

В этом есть смысл, но как бы я сделал следующее:

  1. Получить данные "узла" с сервера из родительского компонента?
  2. Передать данные, полученные с сервера, в дочерний маршрутизатор-выход?

Это не похоже на то, чтобы router-outletsработать так же.

Зак
источник

Ответы:

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

просто недействителен. Компонент, добавленный маршрутизатором, добавляется к нему как одноранговый <router-outlet>и не заменяет его.

См. Также 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);
    }
}
Гюнтер Цёхбауэр
источник
Можно ли это использовать для идентификации ребенка? Я пробовал изменить все, nodeгде это как тип, stringно похоже, что это не работает, или я делаю что-то не так
Wanjia
Вы можете передать любые данные дочернему компоненту, но дочерний компонент должен сам установить идентификатор. Вы можете попытаться получить ElementRefсобытие из розетки маршрутизатора, чтобы использовать его для установки идентификатора, но я не знаю наизусть, если и как это сделать (только на моем телефоне)
Гюнтер Цохбауэр
46

Ответ Гюнтерса великолепен, я просто хочу указать другой способ без использования Observables.

Здесь мы должны помнить, что эти объекты передаются по ссылке, поэтому, если вы хотите поработать с объектом в дочернем объекте и не влиять на родительский объект, я бы предложил использовать решение Гюнтера. Но если это не имеет значения или действительно желаемое поведение , я бы предложил следующее.

@Injectable()
export class SharedService {

    sharedNode = {
      // properties
    };
}

В родительском элементе вы можете присвоить значение:

this.sharedService.sharedNode = this.node;

И в ваших дочерних элементах (И родительском) вставьте общий сервис в свой конструктор. Не забудьте предоставить услугу в массиве поставщиков на уровне модуля, если вы хотите, чтобы одноэлементная служба использовалась для всех компонентов в этом модуле. В качестве альтернативы просто добавьте службу в массив поставщиков только в родительском элементе , тогда родительский и дочерний элементы будут использовать один и тот же экземпляр службы.

node: Node;

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

И, как любезно указал Ньюман, вы также можете иметь this.sharedService.sharedNodeв шаблоне html или геттере:

get sharedNode(){
  return this.sharedService.sharedNode;
}
AJT82
источник
5
вы можете / должны напрямую привязаться к sharedService.sharedNode в html. Таким образом, обновления в sharedNode будут синхронизироваться.
Newman
15

Да, вы можете установить входы компонентов, отображаемых через выходы маршрутизатора . К сожалению, вы должны делать это программно, как упоминалось в других ответах. Есть большая оговорка, когда задействованы наблюдаемые (описанные ниже).

Вот как:

(1) Подключитесь к activateсобытию роутера-розетки в родительском шаблоне:

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

(2) Переключитесь на родительский файл машинописного текста и программно установите входные данные дочернего компонента каждый раз, когда они активируются:

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

Выполнено.

Однако приведенная выше версия onOutletLoadedупрощена для ясности. Это работает только в том случае, если вы можете гарантировать, что все дочерние компоненты имеют точно такие же входы, которые вы назначаете. Если у вас есть компоненты с разными входами, используйте защиты типов:

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

Почему этот метод может быть предпочтительнее метода обслуживания?

Ни этот метод, ни метод службы не являются «правильным способом» взаимодействия с дочерними компонентами (оба метода отходят от чистой привязки к шаблону), поэтому вам просто нужно решить, какой способ больше подходит для проекта.

Этот метод, однако, позволяет избежать тесной связи, связанной с подходом «создать сервис для связи» (т. Е. Родитель нуждается в сервисе, а все потомки нуждаются в сервисе, что делает потомков непригодными для использования где-либо еще). Обычно предпочтение отдается развязке.

Во многих случаях этот метод также кажется более близким к «угловому способу», потому что вы можете продолжать передавать данные своим дочерним компонентам через @Inputs (это часть разделения - это позволяет повторно использовать в другом месте). Это также хорошо подходит для уже существующих или сторонних компонентов, которые вы не хотите или не можете тесно связать с вашим сервисом.

С другой стороны, это может показаться менее угловатым, когда ...

Предостережение

Предостережение с этим методом заключается в том, что, поскольку вы передаете данные в файл машинописного текста, у вас больше нет возможности использовать паттерн конвейерной асинхронности, используемый в шаблонах (например {{ myObservable$ | async }}) для автоматического использования и передачи наблюдаемых данных дочерним компонентам.

Вместо этого вам нужно настроить что-то, чтобы получать текущие наблюдаемые значения при каждом onChildLoaded вызове функции. Скорее всего, это также потребует некоторого разрушения функции родительского компонента onDestroy. В этом нет ничего необычного, часто бывают случаи, когда это необходимо сделать, например, при использовании наблюдаемого, который даже не попадает в шаблон.

Дон Вон
источник
9

Обслуживание:

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

Составная часть:

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
источник
8

Есть 3 способа передать данные от родителей к детям

  1. Через совместно используемый сервис: вы должны сохранить в сервисе данные, которыми хотите поделиться с детьми.
  2. Через Children Router Resolver, если вам нужно получать разные данные

    this.data = this.route.snaphsot.data['dataFromResolver'];
    
  3. Через Parent Router Resolver, если вам нужно получать те же данные от родительского

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

Примечание 1: Вы можете прочитать о резолвере здесь . Также есть пример преобразователя и того, как зарегистрировать преобразователь в модуле, а затем получить данные из преобразователя в компонент. Регистрация преобразователя одинакова для родителя и ребенка.

Заметка 2: Вы можете прочитать об ActivatedRoute здесь , чтобы получить данные с маршрутизатора.

Рзв Разван
источник
1

Следуя этому вопросу, в Angular 7.2 вы можете передавать данные от родителя к потомку, используя состояние истории. Итак, вы можете сделать что-то вроде

Послать:

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

Получить:

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

Но будьте осторожны, чтобы быть последовательным. Например, предположим, что вы хотите отобразить список на левой боковой панели, а подробную информацию о выбранном элементе справа, используя выход маршрутизатора. Что-то типа:


Пункт 1 (x) | ..............................................

Пункт 2 (x) | ...... Сведения о выбранном элементе .......

Пункт 3 (x) | ..............................................

Пункт 4 (x) | ..............................................


Теперь предположим, что вы уже щелкнули по некоторым элементам. Если щелкнуть кнопки возврата в браузере, отобразятся сведения о предыдущем элементе. Но что, если тем временем вы нажали (x) и удалили этот элемент из своего списка? Затем, выполняя обратный щелчок, вы увидите подробную информацию об удаленном элементе.

МТЗ
источник