Gegevens doorgeven aan onderliggende componenten “router-outlet”

Ik heb een bovenliggende component die naar de server gaat en een object ophaalt:

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

En in een van de vele kinderdisplays:

export class ChildDisplay implements OnInit{
    @Input()
    node: Node;
    ngOnInit(): void {
        console.log(this.node);
    }
}

Het lijkt erop dat ik niet zomaar gegevens in de router-outletkan injecteren. Het lijkt erop dat ik de foutmelding krijg in de webconsole:

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

Dit is enigszins logisch, maar hoe zou ik het volgende doen:

  1. Pak de “knooppunt”-gegevens van de server, vanuit de ouder
    onderdeel?
  2. De gegevens die ik van de server heb opgehaald, doorgeven aan de onderliggende router-outlet?

Het lijkt erop dat router-outletsniet op dezelfde manier werken.


Antwoord 1, autoriteit 100%

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

is gewoon ongeldig. De component die door de router is toegevoegd, wordt als broer of zus toegevoegd aan <router-outlet>en vervangt deze niet.

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

Antwoord 2, autoriteit 53%

Het antwoord van Günters is geweldig, ik wil alleen wijzen op een andere manier zonder Observables te gebruiken.

Hier moeten we echter onthouden dat deze objecten door verwijzing worden doorgegeven, dus als je wat werk aan het object in het kind wilt doen en het bovenliggende object niet wilt beïnvloeden, raad ik aan om de oplossing van Günther te gebruiken. Maar als het niet uitmaakt, of eigenlijk gewenst gedragis, zou ik het volgende willen voorstellen.

@Injectable()
export class SharedService {
    sharedNode = {
      // properties
    };
}

In je ouder kun je de waarde toewijzen:

this.sharedService.sharedNode = this.node;

En in uw kinderen (EN ouder), injecteer de gedeelde service in uw constructor. Vergeet niet om de service aan te bieden op moduleniveau providersarray als u een enkele service wilt voor alle componenten in die module. U kunt ook de service gewoon toevoegen in de providers-array in de bovenliggende alleen, dan zullen de ouder en het kind hetzelfde exemplaar van de service delen.

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

En zoals Newman vriendelijk opmerkte, je kunt ook this.sharedService.sharedNodein de html-sjabloon of een getter hebben:

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

Antwoord 3, autoriteit 40%

Ja, u kunt gegevens rechtstreeks doorgeven aan de componenten van de routeruitgang. Helaas kunt u dit niet doen met hoekige sjabloonbinding, zoals vermeld in andere antwoorden. U moet de gegevens in het typoscript-bestand instellen. Er is een groot voorbehoud als het om waarneembare zaken gaat (hieronder beschreven).

Zo gaat het:

(1)Sluit aan op de activate-gebeurtenis van de router-outlet in de bovenliggende sjabloon:

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

(2)Schakel over naar het typescript-bestand van de ouder en stel de invoer van de onderliggende component programmatisch in elke keer dat ze worden geactiveerd:

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

Gereed.

De bovenstaande versie van onOutletLoadedis echter vereenvoudigd voor de duidelijkheid. Het werkt alleen als u kunt garanderen dat alle onderliggende componenten exact dezelfde ingangen hebben die u toewijst. Als je componenten hebt met verschillende inputs, gebruik dan type guards:

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

Waarom kan deze methode de voorkeur hebben boven de servicemethode?

Noch deze methode, noch de servicemethode zijn “de juiste manier” om met onderliggende componenten te communiceren (beide methoden wijken af ​​van pure sjabloonbinding), dus u hoeft alleen maar te beslissen welke manier het meest geschikt is voor het project.

Deze methode vermijdt echter de strakke koppeling die hoort bij de benadering “maak een communicatiedienst” (dwz de ouder heeft de service nodig en de kinderen hebben allemaal de service nodig, waardoor de kinderen elders onbruikbaar worden).

In veel gevallen voelt deze methode ook dichter bij de “hoekige manier” omdat u gegevens kunt blijven doorgeven aan uw onderliggende componenten via @Inputs. Het is ook geschikt voor reeds bestaande componenten of componenten van derden die u niet nauw wilt of kunt koppelen aan uw service.

Aan de andere kant voelt het misschien minder als de hoekige manier wanneer…

Voorbehoud

Het voorbehoud bij deze methode is dat, aangezien u gegevens in het typescript-bestand doorgeeft, u niet langer de optie hebt om het pipe-async-patroon te gebruiken dat in sjablonen wordt gebruikt (bijv. {{ myObservable$ | async }}) om uw waarneembare gegevens automatisch te gebruiken en door te geven aan onderliggende componenten.

In plaats daarvan moet je iets instellen om de huidige waarneembare waarden te krijgen wanneer de functie onChildLoaded wordt aangeroepen. Dit vereist waarschijnlijk ook enige demontage in de functie onDestroyvan de bovenliggende component. Dit is niets bijzonders, er zijn vaak gevallen waarin dit moet worden gedaan, zoals bij het gebruik van een waarneembaar bestand dat niet eens bij de sjabloon komt.


Antwoord 4, autoriteit 10%

Service:

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

Onderdeel:

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

Antwoord 5, autoriteit 10%

Er zijn 3 manieren om gegevens van ouder aan kinderen door te geven

  1. Via deelbare service: u moet in een service de gegevens opslaan die u met de kinderen wilt delen
  2. Via Children Router Resolver als u andere gegevens moet ontvangen

    this.data = this.route.snaphsot.data['dataFromResolver'];
    
  3. Via Parent Router Resolver als u dezelfde gegevens van ouder moet ontvangen

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

Opmerking1:u kunt hierlezen over de resolver. Er is ook een voorbeeld van een resolver en hoe de resolver in de module te registreren en vervolgens gegevens van de resolver in de component op te halen. De registratie van de resolver is hetzelfde voor de ouder en het kind.

Opmerking2:u kunt ActivatedRoutehier lezen om gegevens van de router te kunnen krijgen


Antwoord 6, autoriteit 3%

Volgend op dezevraag, kun je in Angular 7.2 geef gegevens door van ouder naar kind met behulp van de geschiedenisstatus. Dus je kunt iets doen als

Verzenden:

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

Ophalen:

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

Maar wees voorzichtig om consistent te zijn. Stel bijvoorbeeld dat u een lijst wilt weergeven in een linkerzijbalk en de details van het geselecteerde item aan de rechterkant met behulp van een routeruitgang. Iets als:


Artikel 1 (x) | ……………………………………….

Artikel 2 (x) | ……Geselecteerde itemdetails…….

Artikel 3 (x) | ……………………………………….

Artikel 4 (x) | ……………………………………….


Stel nu dat je al op enkele items hebt geklikt. Als u op de terug-knoppen van de browser klikt, worden de details van het vorige item weergegeven. Maar wat als je ondertussen op de (x) hebt geklikt en dat item uit je lijst hebt verwijderd? Als u vervolgens terugklikt, ziet u de details van een verwijderd item.


Antwoord 7

Er is nog een andere manier. Ik geloof dat mijn manier veel problemen oplost :

dit is de standaard manier om de routes af te handelen :

{ path: 'sample/page1', component: sampleComponent1 },
{ path: 'sample/page2', component: sampleComponent2 },
{ path: 'sample/page3', component: sampleComponent3 },

maar laten we in plaats daarvan de routes zoals hieronder schrijven:

{ path: 'sample/:sub', component: sampleComponent },

zoals je kunt zien hebben we routes gecombineerd en een route-parameter gemaakt. we kunnen die parameterwaarde op de component ontvangen:

// sampleComponents.ts :
sub = this.route.snapshot.params['sub'];

nu in het html-bestand voor die component importeren we die pagina’s als subcomponenten. dan kunnen we de gegevens rechtstreeks naar hen sturen.

// sampleComponent.html :
<app-cmp-page1 *ngIf="sub==='page1'" [data]="data"></app-cmp-page1>
<app-cmp-page2 *ngIf="sub==='page2'" [data]="data"></app-cmp-page2>
<app-cmp-page3 *ngIf="sub==='page3'" [data]="data"></app-cmp-page3>

Other episodes