ฉันจะแบ่งปันข้อมูลระหว่างส่วนประกอบใน Angular 2 ได้อย่างไร


84

ใน Angular 1.xx คุณเพียงแค่ขอบริการเดียวกันและลงเอยด้วยอินสแตนซ์เดียวกันทำให้สามารถแบ่งปันข้อมูลในบริการได้

ตอนนี้ใน Angular 2 ฉันมีส่วนประกอบที่อ้างอิงถึงบริการของฉัน ฉันสามารถอ่านและแก้ไขข้อมูลในบริการซึ่งเป็นสิ่งที่ดี เมื่อฉันพยายามฉีดบริการเดียวกันในส่วนประกอบอื่นดูเหมือนว่าฉันได้รับอินสแตนซ์ใหม่

ผมทำอะไรผิดหรือเปล่า? มันเป็นรูปแบบที่ผิด (ใช้บริการเพื่อแบ่งปันข้อมูล) หรือฉันต้องทำเครื่องหมายบริการเป็นซิงเกิลตัน (ภายในแอปเดียว) หรืออะไร?

ฉันอยู่ใน2.0.0-alpha.27/ btw

ฉันฉีดบริการผ่านappInjector(แก้ไข: ตอนนี้providers) ใน@Componentคำอธิบายประกอบจากนั้นบันทึกการอ้างอิงในตัวสร้าง มันทำงานในคอมโพเนนต์ - ไม่ใช่ในคอมโพเนนต์ (ไม่แชร์อินสแตนซ์บริการเดียวกัน) อย่างที่คิด

UPDATE : ณ เชิงมุม 2.0.0 ตอนนี้เรามี @ngModule ที่คุณจะกำหนดให้บริการภายใต้ทรัพย์สินในกล่าวว่าproviders @ngModuleซึ่งจะช่วยให้มั่นใจได้ว่าอินสแตนซ์เดียวกันของบริการนั้นจะถูกส่งต่อไปยังส่วนประกอบบริการ ฯลฯ ในโมดูลนั้น https://angular.io/docs/ts/latest/guide/ngmodule.html#providers

UPDATE : เกิดขึ้นมากมายกับการพัฒนา Angular และ FE โดยทั่วไป ดังที่ @noririco กล่าวไว้คุณสามารถใช้ระบบการจัดการของรัฐเช่น NgRx: https://ngrx.io/


หกวิธีในการแบ่งปันข้อมูลระหว่างส่วนประกอบเชิงมุม: - angulartutorial.net/2017/12/…
Prashobh

หากคุณมาที่นี่โปรดพิจารณาใช้ระบบการจัดการของรัฐ
noririco

คำตอบ:


63

Singleton บริการเป็นทางออกที่ดี วิธีอื่น - data/events bindings.

นี่คือตัวอย่างของทั้งสอง:

class BazService{
  n: number = 0;
  inc(){
    this.n++;
  }
}

@Component({
  selector: 'foo'
})
@View({
  template: `<button (click)="foobaz.inc()">Foo {{ foobaz.n }}</button>`
})
class FooComponent{
  constructor(foobaz: BazService){
    this.foobaz = foobaz;
  }
}

@Component({
  selector: 'bar',
  properties: ['prop']
})
@View({
  template: `<button (click)="barbaz.inc()">Bar {{ barbaz.n }}, Foo {{ prop.foobaz.n }}</button>`
})
class BarComponent{
  constructor(barbaz: BazService){
    this.barbaz = barbaz;
  }
}

@Component({
    selector: 'app',
    viewInjector: [BazService]
})
@View({
  template: `
    <foo #f></foo>
    <bar [prop]="f"></bar>
  `,
  directives: [FooComponent, BarComponent]
})
class AppComponent{}

bootstrap(AppComponent);

ดูถ่ายทอดสด


20
ฉันคิดออกแล้ว คุณฉีดอินสแตนซ์บริการเดียว - ใน "แอป" อินสแตนซ์เดียวกันนั้นได้รับการสืบทอดโดยอัตโนมัติเมื่อเพิ่มพารามิเตอร์ให้กับตัวสร้างลูก :) ฉันทำผิดพลาดในการเพิ่ม appInjector อื่นไปยังส่วนประกอบลูกซึ่งสร้างอินสแตนซ์ใหม่
Per Hornshøj-Schierbeck

1
@AlexanderCrush คุณสามารถปรับปรุงคำตอบของคุณได้หรือไม่? เนื่องจากในเวอร์ชันอัลฟารุ่นหลัง (alpha 30+) appInjector ถูกลบออกไป viewInjectorคำตอบที่ถูกต้องสำหรับตอนนี้ควรจะใช้งาน
Eric Martinez

1
@EricMartinez ขอบคุณคำตอบและคำอธิบายที่ได้รับการอัปเดต
Alexander Ermolov

1
แหล่งข้อมูลที่น่าสนใจเพื่อทำความเข้าใจว่าเหตุใดจึงได้ผล: blog.thoughtram.io/angular/2015/08/20/… .
soyuka

2
ผมก็มีปัญหาเหมือนกันเพราะผมได้รับการฉีดบริการใน app providers: [MyService]หลักและยังอยู่ในองค์ประกอบของตัวเองโดยใช้ การลบผู้ให้บริการมันกลายเป็นเพียงตัวอย่างเดียวของแอพ
maufarinelli

43

ความคิดเห็นโดย @maufarinelli สมควรได้รับคำตอบของตัวเองเพราะจนกระทั่งฉันเห็นฉันยังคงทุบหัวกับกำแพงด้วยปัญหานี้แม้จะมีคำตอบของ @Alexander Ermolov ก็ตาม

ปัญหาคือเมื่อคุณเพิ่ม a providersในcomponent:

@Component({
    selector: 'my-selector',
    providers: [MyService],
    template: `<div>stuff</div>`
})

นี่เป็นสาเหตุตัวอย่างใหม่ของการให้บริการของคุณจะได้รับการฉีด ... แทนที่จะเป็นเดี่ยว

ดังนั้นลบอินสแตนซ์ทั้งหมดของคุณproviders: [MyService]ในแอปพลิเคชันของคุณยกเว้นในmoduleและจะใช้งานได้!


2
แค่แสดงความคิดเห็นมันไม่เคยเป็นซิงเกิลตัน - มันเป็นเพียงอินสแตนซ์เดียวกันที่ถูกส่งต่อไปทั่ว คุณยังสามารถขออินสแตนซ์ใหม่ได้ ...
Per Hornshøj-Schierbeck

10

คุณต้องใช้อินพุตและเอาต์พุตของมัณฑนากร @Component นี่คือตัวอย่างพื้นฐานที่สุดของการใช้ทั้งสองอย่าง

import { bootstrap } from 'angular2/platform/browser';
import { Component, EventEmitter } from 'angular2/core';
import { NgFor } from 'angular2/common';

@Component({
  selector: 'sub-component',
  inputs: ['items'],
  outputs: ['onItemSelected'],
  directives: [NgFor],
  template: `
    <div class="item" *ngFor="#item of items; #i = index">
      <span>{{ item }}</span>
      <button type="button" (click)="select(i)">Select</button>
    </div>
  `
})

class SubComponent {
  onItemSelected: EventEmitter<string>;
  items: string[];

  constructor() {
    this.onItemSelected = new EventEmitter();
  }

  select(i) {
    this.onItemSelected.emit(this.items[i]);
  }
}

@Component({
  selector: 'app',
  directives: [SubComponent],
  template: `
    <div>
      <sub-component [items]="items" (onItemSelected)="itemSelected($event)">
      </sub-component>
    </div>
  `
})

class App {
  items: string[];

  constructor() {
    this.items = ['item1', 'item2', 'item3'];
  }

  itemSelected(item: string): void {
    console.log('Selected item:', item);
  }
}

bootstrap(App);

7
ไม่จำเป็นต้องมีการนำเข้าเป็นngFor,
ริชาร์ดแฮมิลตัน

7

ในแม่แบบองค์ประกอบหลัก:

<hero-child [hero]="hero">
</hero-child>

ในองค์ประกอบลูก:

@Input() hero: Hero;

ที่มา: https://angular.io/docs/ts/latest/cookbook/component-communication.html


อาจเป็นได้ แต่จะต้องมีรายละเอียดเพิ่มเติม ในโลกแห่งความเป็นจริงมันไม่ง่ายเลย ลองจินตนาการว่าคุณมีชั้นเรียนที่คุณต้องการแชร์ระหว่างองค์ประกอบต่างๆและเข้าถึงข้อมูล ที่ไม่ได้ผล
sancelot

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

ใช่ฉันทำ . มันจะทำงาน .... แต่ด้วย "แฮ็ก" บางอย่างเพื่อแก้ปัญหาบางอย่าง คำตอบของคุณไม่อนุญาตให้ใครนำไปใช้
sancelot

2

มีหลายวิธีด้วยกัน อันนี้เป็นตัวอย่างโดยใช้การเผยแพร่ระหว่างองค์ประกอบแม่และลูก นี้มีประสิทธิภาพมาก

ฉันส่งตัวอย่างที่อนุญาตให้ดูการใช้สองวิธีการเชื่อมฐานข้อมูลภายในสองรูปแบบ ถ้าใครสามารถให้ตัวอย่าง plunkr ได้จะดีมาก ;-)

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

mymodel.ts (ข้อมูลที่จะแบ่งปัน)

// Some data we want to share against multiple components ...
export class mymodel {
    public data1: number;
    public data2: number;
    constructor(
    ) {
        this.data1 = 8;
        this.data2 = 45;
    }
}

ข้อควรจำ: ต้องมีผู้ปกครองที่จะแชร์ "mymodel" กับส่วนประกอบย่อย

องค์ประกอบหลัก

import { Component, OnInit } from '@angular/core';
import { mymodel } from './mymodel';
@Component({
    selector: 'app-view',
    template: '<!-- [model]="model" indicates you share model to the child component -->
        <app-mychild [model]="model" >
        </app-mychild>'

        <!-- I add another form component in my view,
         you will see two ways databinding is working :-) -->
        <app-mychild [model]="model" >
        </app-mychild>',
})

export class MainComponent implements OnInit {
    public model: mymodel;
    constructor() {
        this.model = new mymodel();
    }
    ngOnInit() {
    }
}

ส่วนประกอบลูก mychild.component.ts

import { Component, OnInit,Input } from '@angular/core';
import { FormsModule }   from '@angular/forms'; // <-- NgModel lives here
import { mymodel } from './mymodel';

@Component({
    selector: 'app-mychild',
    template: '
        <form #myForm="ngForm">
            <label>data1</label>
            <input type="number"  class="form-control" required id="data1 [(ngModel)]="model.data1" name="data1">
            <label>val {{model.data1}}</label>

            label>data2</label>
            <input  id="data2"  class="form-control" required [(ngModel)]="model.data2" name="data2" #data2="ngModel">
            <div [hidden]="data2.valid || data2.pristine"
                class="alert alert-danger">
                data2 is required
            </div>

            <label>val2 {{model.data2}}</label>
        </form>
    ',
})

export class MychildComponent implements OnInit {
    @Input() model: mymodel ;  // Here keywork @Input() is very important it indicates that model is an input for child component
    constructor() {
    }
    ngOnInit() {
    }
}

หมายเหตุ: ในบางกรณีที่เกิดขึ้นไม่บ่อยคุณอาจมีข้อผิดพลาดเมื่อมีการแยกวิเคราะห์โค้ด HTML เนื่องจากโมเดลไม่ "พร้อม" ที่จะใช้ในการเริ่มต้นเพจ ในกรณีนี้ให้นำหน้าโค้ด HTML ด้วยเงื่อนไข ngIf:

<div *ngIf="model"> {{model.data1}} </div>

1

มันขึ้นอยู่กับว่าถ้ามีกรณีง่ายๆ

a) A -> B -> C A มีลูกสองคน B และ C และถ้าคุณต้องการแบ่งปันข้อมูลระหว่าง A และ B หรือ A และ C ให้ใช้ (อินพุต / เอาต์พุต)

หากคุณต้องการแบ่งปันระหว่าง B และ C คุณสามารถใช้ (อินพุต / เอาต์พุต) ได้ แต่ขอแนะนำให้ใช้บริการ

b) ถ้าต้นไม้ใหญ่และซับซ้อน (หากมีการเชื่อมต่อผู้ปกครองและเด็กหลายระดับ) และในกรณีนี้หากคุณต้องการแชร์ข้อมูลฉันขอแนะนำngrx

ใช้สถาปัตยกรรมฟลักซ์ซึ่งสร้างที่เก็บฝั่งไคลเอ็นต์ซึ่งส่วนประกอบใด ๆ สามารถสมัครสมาชิกและสามารถอัปเดตได้โดยไม่ต้องสร้างเงื่อนไขการแข่งขันใด ๆ

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