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


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

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

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) и удалили этот элемент из своего списка? Затем, выполняя обратный щелчок, вы увидите подробную информацию об удаленном элементе.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.