Meneruskan data ke komponen turunan "router-outlet"

103

Saya punya komponen induk yang masuk ke server dan mengambil objek:

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

Dan di salah satu dari beberapa tampilan anak:

export class ChildDisplay implements OnInit{

    @Input()
    node: Node;

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

}

Sepertinya saya tidak bisa menyuntikkan data ke file router-outlet. Sepertinya saya mendapatkan kesalahan di konsol web:

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

Ini agak masuk akal, tetapi bagaimana saya melakukan hal berikut:

  1. Ambil data "node" dari server, dari dalam komponen induk?
  2. Meneruskan data yang telah saya ambil dari server ke outlet-router anak?

Sepertinya tidak router-outletsbekerja dengan cara yang sama.

Zack
sumber

Jawaban:

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

hanya tidak valid. Komponen yang ditambahkan oleh router ditambahkan sebagai saudara <router-outlet>dan tidak menggantikannya.

Lihat juga 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
sumber
Bisakah ini digunakan untuk memberikan ID kepada anak? Saya sudah mencoba mengubah semua di nodemana itu sebagai tipe stringdan sepertinya tidak berhasil atau saya melakukan sesuatu yang salah
Wanjia
Anda bisa meneruskan data apa pun ke komponen anak tetapi komponen anak perlu menyetel id itu sendiri. Anda dapat mencoba untuk mendapatkan ElementRefdari acara dari outlet router untuk menggunakannya untuk mengatur id tetapi saya tidak tahu dengan hati apakah atau bagaimana melakukannya (hanya di ponsel saya)
Günter Zöchbauer
46

Jawaban Günters bagus, saya hanya ingin menunjukkan cara lain tanpa menggunakan Observables.

Di sini kita harus ingat bahwa objek-objek ini diteruskan oleh referensi, jadi jika Anda ingin melakukan beberapa pekerjaan pada objek pada anak dan tidak memengaruhi objek induk, saya akan menyarankan menggunakan solusi Günther. Tetapi jika itu tidak masalah, atau sebenarnya adalah perilaku yang diinginkan , saya akan menyarankan yang berikut ini.

@Injectable()
export class SharedService {

    sharedNode = {
      // properties
    };
}

Di orang tua Anda, Anda dapat memberikan nilai:

this.sharedService.sharedNode = this.node;

Dan pada anak-anak Anda (AND orang tua), masukkan Layanan bersama di konstruktor Anda. Ingatlah untuk menyediakan layanan pada larik penyedia tingkat modul jika Anda menginginkan layanan tunggal di seluruh komponen dalam modul itu. Alternatifnya, cukup tambahkan layanan dalam larik penyedia di induk saja , lalu induk dan turunannya akan berbagi contoh layanan yang sama.

node: Node;

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

Dan seperti yang ditunjukkan oleh orang baru, Anda juga dapat memiliki this.sharedService.sharedNodetemplate html atau getter:

get sharedNode(){
  return this.sharedService.sharedNode;
}
AJT82
sumber
5
Anda dapat / harus mengikat ke sharedService.sharedNode di html secara langsung. Dengan cara ini, pembaruan di sharedNode akan tetap sinkron.
Newman
12

Ya, Anda dapat mengatur input komponen yang ditampilkan melalui outlet router . Sayangnya, Anda harus melakukannya secara terprogram, seperti yang disebutkan dalam jawaban lain. Ada peringatan besar untuk hal itu ketika observasi dilibatkan (dijelaskan di bawah).

Begini caranya:

(1) Hubungkan ke acara router-outlet activatedi template induk:

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

(2) Beralih ke file skrip induk dan setel input komponen anak secara terprogram setiap kali mereka diaktifkan:

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

Versi di atas onOutletLoadeddisederhanakan agar lebih jelas, tetapi hanya berfungsi jika Anda dapat menjamin semua komponen turunan memiliki input yang sama persis dengan yang Anda tetapkan. Jika Anda memiliki komponen dengan input berbeda, gunakan pelindung tipe:

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

Mengapa metode ini lebih disukai daripada metode layanan?

Baik metode ini maupun metode layanan bukanlah "cara yang tepat" untuk berkomunikasi dengan komponen turunan (kedua metode menjauh dari pengikatan template murni), jadi Anda hanya perlu memutuskan cara mana yang dirasa lebih sesuai untuk proyek tersebut.

Namun, metode ini memungkinkan Anda untuk menghindari pembuatan objek perantara (layanan), yang secara erat memasangkan komponen layanan dan turunan. Dalam banyak kasus, hal ini terasa lebih dekat dengan "cara sudut" karena Anda dapat terus meneruskan data ke komponen anak Anda melalui @Inputs. Ini juga cocok untuk komponen yang sudah ada atau pihak ketiga yang tidak Anda inginkan atau tidak dapat digabungkan dengan layanan itu. Di sisi lain, mungkin terasa kurang seperti sudut ketika ...

Peringatan

Peringatan dengan metode ini adalah karena Anda meneruskan data secara terprogram, Anda tidak lagi memiliki opsi untuk meneruskan data yang dapat diamati di template Anda (kejutan!). Itu berarti Anda kehilangan keuntungan besar dari pengelolaan angular dalam siklus hidup yang dapat diamati saat Anda menggunakan pola pipa-asinkron.

Sebaliknya, Anda harus menyiapkan sesuatu untuk mendapatkan nilai yang dapat diamati saat ini setiap kali onChildLoaded fungsi tersebut dipanggil. Ini kemungkinan juga akan membutuhkan beberapa pembongkaran dalam onDestroyfungsi komponen induk . Ini tidak terlalu aneh, ada kasus lain di mana hal ini perlu dilakukan, seperti saat menggunakan observable yang bahkan tidak sampai ke template.

Don Vaughn
sumber
9

Layanan:

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

Komponen:

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
sumber
8

Ada 3 cara untuk meneruskan data dari Parent ke Children

  1. Melalui layanan yang dapat dibagikan: Anda harus menyimpan ke dalam layanan data yang ingin Anda bagikan dengan anak-anak
  2. Melalui Children Router Resolver jika Anda harus menerima data yang berbeda

    this.data = this.route.snaphsot.data['dataFromResolver'];
    
  3. Melalui Pemecah Router Orang Tua jika Anda harus menerima data yang sama dari orang tua

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

Note1: Anda dapat membaca tentang resolver di sini . Ada juga contoh resolver dan cara mendaftarkan resolver ke dalam modul lalu mengambil data dari resolver ke dalam komponen. Pendaftaran resolver sama pada orang tua dan anak.

Note2: Anda dapat membaca tentang ActivatedRoute di sini untuk mendapatkan data dari router

Rzv Razvan
sumber
1

Mengikuti pertanyaan ini , di Angular 7.2 Anda dapat meneruskan data dari orang tua ke anak menggunakan status riwayat. Jadi Anda bisa melakukan sesuatu seperti

Kirim:

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

Ambil:

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

Tapi berhati-hatilah agar konsisten. Misalnya, Anda ingin menampilkan daftar di bilah sisi kiri dan rincian item yang dipilih di sebelah kanan dengan menggunakan outlet-router. Sesuatu seperti:


Item 1 (x) | ..............................................

Butir 2 (x) | ...... Detail Item yang Dipilih .......

Item 3 (x) | ..............................................

Butir 4 (x) | ..............................................


Sekarang, misalkan Anda sudah mengklik beberapa item. Mengklik tombol kembali browser akan menampilkan detail dari item sebelumnya. Tetapi bagaimana jika, sementara itu, Anda telah mengklik (x) dan menghapus item itu dari daftar Anda? Kemudian melakukan klik kembali, akan menampilkan detail item yang dihapus.

MTZ
sumber