Як розподілити дані між компонентами в Angular 2?


84

У Angular 1.xx ви просто просите ту саму послугу, і ви отримуєте той самий екземпляр, що робить можливим обмін даними в службі.

Зараз у Angular 2 у мене є компонент, який має посилання на мою службу. Я можу читати та змінювати дані в службі, що добре. Коли я намагаюся ввести ту саму службу в інший компонент, здається, ніби я отримую новий екземпляр.

Що я роблю не так? Чи не сам шаблон є неправильним (за допомогою служби для обміну даними), чи мені потрібно позначити службу як єдину (в межах одного екземпляра програми) чи щось інше?

Я до 2.0.0-alpha.27/ речі

Я ввожу службу через appInjector(редагувати: зараз providers) в @Componentанотацію, а потім зберігаю посилання в конструкторі. Він працює локально в компоненті - тільки не між компонентами (вони не мають спільного екземпляра служби), як я думав, що вони будуть.

ОНОВЛЕННЯ : станом на Angular 2.0.0 тепер у нас є @ngModule, де ви можете визначити службу за providersвластивістю на @ngModule. Це забезпечить передачу того самого екземпляра цієї служби кожному компоненту, службі тощо в цьому модулі. https://angular.io/docs/ts/latest/guide/ngmodule.html#providers

ОНОВЛЕННЯ : З розробкою Angular та ІП загалом сталося багато. Як згадував @noririco, ви також можете використовувати державну систему управління, таку як NgRx: https://ngrx.io/


Шість методів обміну даними між кутовими компонентами: - angulartutorial.net/2017/12/…
Prashobh

Якщо ви потрапили сюди, розгляньте можливість використання системи державного управління
noririco

Відповіді:


63

Службовий синглтон - приємне рішення. Інший спосіб - 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
Я зрозумів це. Ви вводите лише один екземпляр служби - у 'app'. Цей самий екземпляр успадковується автоматично при додаванні параметра до дочірніх конструкторів :) Я зробив помилку, додавши інший appInjector до дочірніх компонентів, який створює нові екземпляри.
Per Hornshøj-Schierbeck

1
@AlexanderCrush не могли б Ви оновити свою відповідь? Оскільки в пізніших альфа-версіях (альфа 30+) appInjector було видалено . Наразі правильною відповіддю має бути використання viewInjector.
Ерік Мартінес

1
@EricMartinez подяка, відповідь та плюнкер оновлені.
Олександр Єрмолов

1
Цікавий ресурс, щоб зрозуміти, чому і як це працює: blog.thoughtram.io/angular/2015/08/20/… .
soyuka

2
У мене була та сама проблема, оскільки я вводив Службу в основну програму, а також у сам компонент, що використовував providers: [MyService]. Видаливши провайдерів, він став єдиним екземпляром програми
maufarinelli 02

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


Може бути, але для цього потрібні додаткові подробиці. У реальному світі це не так просто. уявіть, у вас є клас, яким ви хочете поділитися між кількома компонентами, і отримати доступ до даних. що не працює.
санцелот

Я використовую цей підхід у великому рішенні для обміну даними між багатьма компонентами. У вас може бути багато дітей, і кожен отримує один і той же предмет. Ви намагалися це зробити, перш ніж сказати, що це не працює?
Alexis Gamarra

Так . це спрацює .... але з деякими "хакерськими" з метою вирішення деяких питань. Ваша відповідь не дозволяє нікому користуватися нею.
sancelot

2

Є багато способів. Цей приклад є прикладом використання розповсюдження між батьківськими та дочірніми елементами. Це дуже ефективно.

Я подав приклад, який дозволяє переглядати використання двох способів прив'язки даних у двох формах. Якщо хтось може надати зразок plunkr, це було б дуже добре ;-)

Ви можете шукати інший спосіб, використовуючи постачальника послуг. Можливо, ви також подивитеся це відео для довідки: ( Обмін даними між компонентами в Angular )

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 -> B -> C A має двох дочірніх елементів B і C, і якщо ви хочете обмінюватися даними між A і B або A і C, тоді використовуйте

Якщо ви хочете поділитися між B і C, тоді ви також можете використовувати (введення / виведення), але пропонується використовувати службу.

б) Якщо дерево велике і складне. (якщо існує так багато рівнів батьківських та дитячих зв’язків.) І в цьому випадку, якщо ви хочете поділитися даними, я б запропонував ngrx

Він реалізує потокову архітектуру, яка створює сховище на стороні клієнта, на яке будь-який компонент може підписатися та може оновитись, не створюючи жодних умов перегони.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.