Przekazywanie danych do komponentów potomnych „router-outlet”

111

Mam komponent nadrzędny, który trafia na serwer i pobiera obiekt:

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

I na jednym z kilku ekranów dla dzieci:

export class ChildDisplay implements OnInit{

    @Input()
    node: Node;

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

}

Nie wygląda na to, że mogę po prostu wstrzyknąć dane do router-outlet. Wygląda na to, że pojawia się błąd w konsoli internetowej:

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

To trochę ma sens, ale jak mam to zrobić:

  1. Czy pobrać dane „węzła” z serwera z komponentu nadrzędnego?
  2. Czy przekazać dane pobrane z serwera do gniazda routera podrzędnego?

Wygląda na to, że nie router-outletsdziała w ten sam sposób.

Zack
źródło

Odpowiedzi:

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

jest po prostu nieprawidłowy. Komponent dodany przez router jest dodawany jako element siostrzany <router-outlet>i nie zastępuje go.

Zobacz też 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
źródło
Czy można to wykorzystać do nadania dziecku identyfikatora? Próbowałem zmienić wszystko, nodegdzie jest jako typ, stringi wydaje się, że nie działa lub robię coś źle
Wanjia,
Możesz przekazać dowolne dane do komponentu podrzędnego, ale komponent potomny musi sam ustawić identyfikator. Możesz spróbować uzyskać sygnał ElementRefze zdarzenia z gniazda routera, aby użyć go do ustawienia identyfikatora, ale nie wiem na pamięć, czy i jak to zrobić (tylko na moim telefonie)
Günter Zöchbauer
46

Odpowiedź Günters jest świetna, chcę tylko wskazać inny sposób bez używania Observables.

Tutaj jednak musimy pamiętać, że te obiekty są przekazywane przez referencje, więc jeśli chcesz wykonać jakąś pracę na obiekcie w dziecku i nie wpływać na obiekt nadrzędny, sugerowałbym użycie rozwiązania Günthera. Ale jeśli nie ma to znaczenia, a nawet jest pożądanym zachowaniem , sugerowałbym następujące.

@Injectable()
export class SharedService {

    sharedNode = {
      // properties
    };
}

W swoim rodzicu możesz przypisać wartość:

this.sharedService.sharedNode = this.node;

A w swoich dzieciach (ORAZ rodzicach) wstrzyknij udostępnioną usługę do konstruktora. Pamiętaj, aby zapewnić usługę na poziomie tablicy dostawców, jeśli chcesz, aby usługa pojedyncza obejmowała wszystkie składniki w tym module. Alternatywnie, dodaj usługę w tablicy agentow w dominującej jedynie , wtedy rodzic i dziecko będą dzielić tę samą instancję usługi.

node: Node;

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

I jak uprzejmie wskazał newman, this.sharedService.sharedNodew szablonie html lub getterze możesz też mieć :

get sharedNode(){
  return this.sharedService.sharedNode;
}
AJT82
źródło
5
możesz / powinieneś powiązać się z sharedService.sharedNode bezpośrednio w html. W ten sposób aktualizacje w sharedNode będą zsynchronizowane.
newman
15

Tak, możesz ustawić wejścia komponentów wyświetlanych przez gniazda routera . Niestety, musisz to zrobić programowo, jak wspomniano w innych odpowiedziach. Jest to duże zastrzeżenie, gdy w grę wchodzą obserwowalne (opisane poniżej).

Oto jak:

(1) Podłącz do activatezdarzenia gniazda routera w szablonie nadrzędnym:

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

(2) Przełącz się do pliku maszynopisu rodzica i ustaw programowo dane wejściowe komponentu podrzędnego za każdym razem, gdy są aktywowane:

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

Gotowe.

Jednak powyższa wersja onOutletLoadedjest uproszczona dla przejrzystości. Działa tylko wtedy, gdy możesz zagwarantować, że wszystkie komponenty podrzędne mają dokładnie te same dane wejściowe, które przypisujesz. Jeśli masz komponenty z różnymi danymi wejściowymi, użyj osłon typów:

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

Dlaczego ta metoda może być preferowana w stosunku do metody serwisowej?

Ani ta metoda, ani metoda usługi nie są „właściwą drogą” do komunikacji z komponentami potomnymi (obie metody odchodzą od czystego wiązania szablonu), więc musisz tylko zdecydować, który sposób jest bardziej odpowiedni dla projektu.

Jednak ta metoda pozwala uniknąć ścisłego powiązania związanego z podejściem „stwórz usługę dla komunikacji” (tj. Rodzic potrzebuje usługi, a wszystkie dzieci potrzebują usługi, co sprawia, że ​​dzieci nie nadają się do użytku gdzie indziej). Zwykle preferowane jest odsprzęganie.

W wielu przypadkach ta metoda wydaje się również bliższa „drodze kątowej”, ponieważ możesz kontynuować przekazywanie danych do komponentów podrzędnych przez @Inputs (to jest część odsprzęgająca - umożliwia to ponowne wykorzystanie w innym miejscu). Jest to również dobre rozwiązanie dla już istniejących lub komponentów innych firm, których nie chcesz lub nie możesz ściśle połączyć z twoją usługą.

Z drugiej strony może wydawać się mniej kątowy, gdy ...

Caveat

Ograniczeniem związanym z tą metodą jest to, że ponieważ przekazujesz dane w pliku maszynopisu, nie masz już możliwości używania wzorca potoku asynchronicznego używanego w szablonach (np. {{ myObservable$ | async }}) Do automagicznego używania i przekazywania obserwowalnych danych do komponentów potomnych.

Zamiast tego musisz skonfigurować coś, aby uzyskać bieżące obserwowalne wartości za każdym razem, gdy onChildLoaded wywoływana jest funkcja. Prawdopodobnie będzie to również wymagało porzucenia funkcji komponentu nadrzędnego onDestroy. Nie jest to nic niezwykłego, często zdarzają się przypadki, w których trzeba to zrobić, na przykład gdy używasz obserwowalnego, który nawet nie dociera do szablonu.

Don Vaughn
źródło
9

Usługa:

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

Składnik:

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
źródło
8

Istnieją 3 sposoby przekazywania danych od rodzica do dzieci

  1. Dzięki usłudze udostępniania: należy przechowywać w usłudze dane, którymi chciałbyś się podzielić z dziećmi
  2. Przez Children Router Resolver, jeśli musisz odbierać różne dane

    this.data = this.route.snaphsot.data['dataFromResolver'];
    
  3. Poprzez Parent Router Resolver, jeśli musisz otrzymać te same dane od rodzica

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

Uwaga 1: Możesz przeczytać o resolwerze tutaj . Jest też przykład resolwera i jak zarejestrować przelicznik w module, a następnie pobrać dane z resolwera do komponentu. Rejestracja resolvera jest taka sama dla rodzica i dziecka.

Uwaga 2: Możesz przeczytać o ActivatedRoute tutaj, aby móc pobrać dane z routera

Rzv Razvan
źródło
1

Podążając za tym pytaniem, w Angular 7.2 możesz przekazywać dane od rodzica do dziecka przy użyciu stanu historii. Więc możesz zrobić coś takiego

Wysłać:

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

Odzyskać:

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

Ale uważaj, aby być konsekwentnym. Na przykład, przypuśćmy, że chcesz wyświetlić listę na lewym pasku bocznym, a szczegóły wybranego elementu po prawej stronie za pomocą gniazda routera. Coś jak:


Pozycja 1 (x) | ..............................................

Pozycja 2 (x) | ...... Szczegóły wybranego elementu .......

Pozycja 3 (x) | ..............................................

Pozycja 4 (x) | ..............................................


Teraz załóżmy, że już kliknąłeś niektóre elementy. Kliknięcie przycisków Wstecz przeglądarki spowoduje wyświetlenie szczegółów z poprzedniej pozycji. Ale co, jeśli w międzyczasie kliknąłeś (x) i usunąłeś ten element z listy? Następnie wykonanie kliknięcia wstecz spowoduje wyświetlenie szczegółów usuniętej pozycji.

MTZ
źródło