การส่งผ่านข้อมูลไปยังส่วนประกอบลูกของ“ เราเตอร์เต้าเสียบ”


114

ฉันมีองค์ประกอบหลักที่ไปที่เซิร์ฟเวอร์และดึงวัตถุ:

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

สามารถใช้เพื่อให้ ID กับเด็กได้หรือไม่? ฉันได้ลองเปลี่ยนทุกอย่างnodeที่เป็นประเภทstringและดูเหมือนจะไม่ได้ผลหรือฉันทำอะไรผิดพลาด
Wanjia

คุณสามารถส่งข้อมูลใด ๆ ไปยังคอมโพเนนต์ย่อย แต่คอมโพเนนต์ย่อยต้องตั้งค่ารหัสเอง คุณสามารถลองรับElementRefเหตุการณ์จากเต้ารับเราเตอร์เพื่อใช้ในการตั้งค่า id ได้ แต่ฉันไม่รู้ด้วยใจว่าจะทำเช่นนั้นหรือไม่ (เฉพาะในโทรศัพท์ของฉัน)
GünterZöchbauer

46

คำตอบของGüntersนั้นยอดเยี่ยมฉันแค่อยากจะชี้ให้เห็นวิธีอื่นโดยไม่ต้องใช้ Observables

ที่นี่เราต้องจำไว้ว่าวัตถุเหล่านี้ผ่านการอ้างอิงดังนั้นหากคุณต้องการทำงานบางอย่างกับวัตถุในลูกและไม่ส่งผลกระทบต่อวัตถุแม่ฉันขอแนะนำให้ใช้โซลูชันของGünther แต่ถ้าไม่สำคัญหรือเป็นพฤติกรรมที่ต้องการจริงๆฉันขอแนะนำสิ่งต่อไปนี้

@Injectable()
export class SharedService {

    sharedNode = {
      // properties
    };
}

ในผู้ปกครองของคุณคุณสามารถกำหนดค่า:

this.sharedService.sharedNode = this.node;

และในบุตรหลานของคุณ (และผู้ปกครอง) ให้ฉีดบริการที่ใช้ร่วมกันในตัวสร้างของคุณ อย่าลืมให้บริการที่อาร์เรย์ผู้ให้บริการระดับโมดูลหากคุณต้องการบริการซิงเกิลตันทั่วส่วนประกอบในโมดูลนั้น หรือเพิ่มบริการในอาร์เรย์ผู้ให้บริการในพาเรนต์เท่านั้นจากนั้นพาเรนต์และเด็กจะแชร์อินสแตนซ์ของบริการเดียวกัน

node: Node;

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

และในฐานะคนใหม่คุณสามารถมีthis.sharedService.sharedNodeในเทมเพลต html หรือ getter:

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

5
คุณสามารถ / ควรผูกกับ sharedService.sharedNode ใน html โดยตรง ด้วยวิธีนี้การอัปเดตใน sharedNode จะถูกซิงค์
newman

18

ใช่คุณสามารถตั้งค่าปัจจัยการผลิตของส่วนประกอบแสดงผ่านทางร้านเราเตอร์ น่าเศร้าที่คุณต้องทำแบบเป็นโปรแกรมดังที่กล่าวไว้ในคำตอบอื่น ๆ มีข้อแม้ที่สำคัญเมื่อมีส่วนเกี่ยวข้องกับการสังเกตการณ์ (อธิบายไว้ด้านล่าง)

วิธีการมีดังนี้

(1)เชื่อมต่อกับactivateเหตุการณ์ของเราเตอร์เต้าเสียบในแม่แบบหลัก:

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

(2)สลับไปที่ไฟล์ typescript ของผู้ปกครองและตั้งค่าอินพุตขององค์ประกอบลูกโดยทางโปรแกรมทุกครั้งที่เปิดใช้งาน:

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 (นั่นคือส่วนการแยกส่วนซึ่งจะทำให้สามารถใช้ซ้ำที่อื่นได้) นอกจากนี้ยังเหมาะสำหรับส่วนประกอบที่มีอยู่แล้วหรือของบุคคลที่สามที่คุณไม่ต้องการหรือไม่สามารถจับคู่กับบริการของคุณได้อย่างแน่นหนา

ในทางกลับกันมันอาจจะรู้สึกน้อยกว่าทางเชิงมุมเมื่อ ...

ข้อแม้

ข้อแม้ของวิธีนี้คือเนื่องจากคุณกำลังส่งข้อมูลในไฟล์ typescript คุณจึงไม่มีตัวเลือกในการใช้รูปแบบ pipe-async ที่ใช้ในเทมเพลต (เช่น{{ myObservable$ | async }}) เพื่อใช้โดยอัตโนมัติและส่งต่อข้อมูลที่สังเกตได้ของคุณไปยังส่วนประกอบย่อย

คุณจะต้องตั้งค่าบางอย่างเพื่อให้ได้ค่าที่สังเกตได้ในปัจจุบันทุกครั้งที่onChildLoaded มีการเรียกใช้ฟังก์ชัน สิ่งนี้อาจต้องใช้การฉีกขาดบางอย่างในonDestroyฟังก์ชันขององค์ประกอบหลัก นี่ไม่ใช่เรื่องผิดปกติเกินไปมีหลายกรณีที่จำเป็นต้องทำเช่นเมื่อใช้สิ่งที่สังเกตได้ซึ่งไม่สามารถเข้าถึงแม่แบบได้


1
ขอบคุณ! นี่คือคำตอบที่ดี👍
Elad

10

บริการ:

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) และลบออกจากรายการของคุณรายการนั้นล่ะ? จากนั้นทำการคลิกย้อนกลับจะแสดงรายละเอียดของรายการที่ถูกลบ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.