Übergeben von Daten an untergeordnete Komponenten von "Router-Outlet"


98

Ich habe eine übergeordnete Komponente, die zum Server geht und ein Objekt abruft:

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

Und in einer von mehreren Kinderanzeigen:

export class ChildDisplay implements OnInit{

    @Input()
    node: Node;

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

}

Es scheint nicht, dass ich einfach Daten in die einspeisen kann router-outlet. Es sieht so aus, als würde ich den Fehler in der Webkonsole erhalten:

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

Das macht etwas Sinn, aber wie würde ich Folgendes tun:

  1. Die "Knoten" -Daten vom Server aus der übergeordneten Komponente abrufen?
  2. Übergeben Sie die Daten, die ich vom Server abgerufen habe, an den untergeordneten Router-Ausgang?

Es scheint nicht so zu router-outletsfunktionieren.

Antworten:


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

ist einfach ungültig. Die vom Router hinzugefügte Komponente wird als Geschwister hinzugefügt <router-outlet>und ersetzt sie nicht.

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

Kann dies verwendet werden, um dem Kind einen Ausweis zu geben? Ich habe versucht, all das zu ändern, nodewo es als Typ ist stringund es scheint nicht zu funktionieren oder ich mache etwas falsch
Wanjia

Sie können beliebige Daten an die untergeordnete Komponente übergeben, die untergeordnete Komponente muss jedoch die ID selbst festlegen. Sie können versuchen, eine ElementRefvon einem Ereignis aus dem Router-Ausgang zu erhalten, um damit die ID festzulegen, aber ich weiß nicht auswendig, ob oder wie das geht (nur auf meinem Telefon)
Günter Zöchbauer

46

Günters Antwort ist großartig, ich möchte nur einen anderen Weg aufzeigen, ohne Observables zu verwenden.

Hier müssen wir uns jedoch daran erinnern, dass diese Objekte als Referenz übergeben werden. Wenn Sie also etwas an dem Objekt im untergeordneten Objekt arbeiten möchten und das übergeordnete Objekt nicht beeinflussen möchten, würde ich die Verwendung der Günther-Lösung vorschlagen. Aber wenn es keine Rolle spielt oder tatsächlich das gewünschte Verhalten ist , würde ich Folgendes vorschlagen.

@Injectable()
export class SharedService {

    sharedNode = {
      // properties
    };
}

In Ihrem Elternteil können Sie den Wert zuweisen:

this.sharedService.sharedNode = this.node;

Fügen Sie bei Ihren Kindern (UND Eltern) den gemeinsam genutzten Dienst in Ihren Konstruktor ein. Denken Sie daran, den Dienst auf Array-Ebene des Anbieters bereitzustellen, wenn Sie einen Singleton-Dienst für alle Komponenten in diesem Modul wünschen. Alternativ fügen Sie einfach den Dienst in dem Provider - Array in den Eltern nur , dann den Eltern und Kind die gleiche Instanz des Dienstes teilen.

node: Node;

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

Und wie Newman freundlich darauf hingewiesen hat, können Sie auch this.sharedService.sharedNodein der HTML-Vorlage oder einem Getter haben:

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

5
Sie können / sollten direkt an sharedService.sharedNode in HTML binden. Auf diese Weise werden Aktualisierungen in sharedNode synchron gehalten.
Newman

11

Ja, Sie können Eingaben von Komponenten festlegen, die über Router-Ausgänge angezeigt werden . Leider müssen Sie dies programmgesteuert tun, wie in anderen Antworten erwähnt. Es gibt eine große Einschränkung, wenn Observable beteiligt sind (siehe unten).

Hier ist wie:

(1) Schließen Sie das activateEreignis des Router-Outlets in der übergeordneten Vorlage an:

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

(2) Wechseln Sie zur Typoskriptdatei des übergeordneten Elements und stellen Sie die Eingaben der untergeordneten Komponente bei jeder Aktivierung programmgesteuert ein:

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

Die obige Version von onOutletLoadedist aus Gründen der Übersichtlichkeit vereinfacht, funktioniert jedoch nur, wenn Sie sicherstellen können, dass alle untergeordneten Komponenten genau die gleichen Eingaben haben, die Sie zuweisen. Wenn Sie Komponenten mit unterschiedlichen Eingängen haben, verwenden Sie Typschutz:

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

Warum kann diese Methode der Servicemethode vorgezogen werden?

Weder diese Methode noch die Servicemethode sind "der richtige Weg", um mit untergeordneten Komponenten zu kommunizieren (beide Methoden entfernen sich von der reinen Vorlagenbindung). Sie müssen also nur entscheiden, welcher Weg für das Projekt besser geeignet ist.

Diese Methode ermöglicht es Ihnen jedoch, das Erstellen des Middle-Man-Objekts (des Dienstes) zu vermeiden und ist in vielen Fällen näher am "Winkelweg", da Sie @Input weiterhin in Ihren untergeordneten Komponenten verwenden können. Es eignet sich auch gut für bereits vorhandene Komponenten oder Komponenten von Drittanbietern, die Sie nicht eng mit diesem Service verbinden möchten oder können. Andererseits...

Vorbehalt

Die Einschränkung bei dieser Methode besteht darin, dass Sie, da Sie Daten programmgesteuert übergeben, nicht mehr die Möglichkeit haben, beobachtbare Daten über die Vorlageneingabebindung zu übergeben (Überraschung!). Das bedeutet, dass Sie den großen Vorteil der Winkelverwaltung des Lebenszyklus des Observablen für Sie verlieren, wenn Sie das Pipe-Async-Muster verwenden.

Stattdessen müssen Sie bei jedem onChildLoaded Aufruf der Funktion etwas einrichten, um die aktuell beobachtbaren Werte abzurufen . Dies erfordert wahrscheinlich auch einen gewissen Abbau der onDestroyFunktion der übergeordneten Komponente . Dies ist nichts Ungewöhnliches. Es gibt andere Fälle, in denen dies durchgeführt werden muss, z. B. wenn ein Observable verwendet wird, das nicht einmal zur Vorlage gelangt.


9

Bedienung:

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

Komponente:

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

Es gibt drei Möglichkeiten, Daten von Eltern an Kinder zu übergeben

  1. Durch gemeinsam nutzbaren Dienst: Sie sollten die Daten, die Sie mit den Kindern teilen möchten, in einem Dienst speichern
  2. Über Children Router Resolver, wenn Sie unterschiedliche Daten empfangen müssen

    this.data = this.route.snaphsot.data['dataFromResolver'];
    
  3. Über Parent Router Resolver, wenn Sie dieselben Daten vom übergeordneten Router erhalten müssen

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

Hinweis 1: Informationen zum Resolver finden Sie hier . Es gibt auch ein Beispiel für einen Resolver und wie Sie den Resolver im Modul registrieren und dann Daten vom Resolver in die Komponente abrufen. Die Resolver-Registrierung ist für Eltern und Kind gleich.

Hinweis 2: Sie können hier über ActivatedRoute lesen , um Daten vom Router abrufen zu können


1

Nach dieser Frage können Sie in Angular 7.2 Daten mithilfe des Verlaufsstatus von einem Elternteil an ein Kind übergeben. Sie können also so etwas tun

Senden:

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

Abrufen:

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

Aber seien Sie vorsichtig, um konsequent zu sein. Angenommen, Sie möchten eine Liste in einer linken Leiste und die Details des ausgewählten Elements auf der rechten Seite mithilfe eines Router-Ausgangs anzeigen. Etwas wie:


Punkt 1 (x) | ..............................................

Punkt 2 (x) | ...... Ausgewählte Artikeldetails .......

Punkt 3 (x) | ..............................................

Punkt 4 (x) | ..............................................


Angenommen, Sie haben bereits auf einige Elemente geklickt. Durch Klicken auf die Zurück-Schaltfläche des Browsers werden die Details des vorherigen Elements angezeigt. Aber was ist, wenn Sie inzwischen auf das (x) geklickt und dieses Element aus Ihrer Liste gelöscht haben? Wenn Sie dann den Zurückklick ausführen, werden Ihnen die Details eines gelöschten Elements angezeigt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.