Как передать данные между компонентами в Angular 2?

84

В Angular 1.xx вы просто запрашиваете одну и ту же службу, и в итоге вы получаете один и тот же экземпляр, что позволяет обмениваться данными в службе.

Теперь в Angular 2 у меня есть компонент, который ссылается на мою службу. Я могу читать и изменять данные в сервисе, и это хорошо. Когда я пытаюсь внедрить ту же службу в другой компонент, мне кажется, что я получаю новый экземпляр.

Что я делаю неправильно? Это неправильный шаблон (использование службы для обмена данными), или мне нужно пометить службу как синглтон (в одном экземпляре приложения) или что-то в этом роде?

Я на 2.0.0-alpha.27/ BTW

Я добавляю службу через appInjector(изменить: сейчас providers) в @Componentаннотации, а затем сохраняю ссылку в конструкторе. Он работает локально в компоненте, а не между компонентами (они не используют один и тот же экземпляр службы), как я думал.

ОБНОВЛЕНИЕ : Начиная с Angular 2.0.0, у нас теперь есть @ngModule, где вы определяете службу в providersсвойстве, указанном @ngModule. Это обеспечит передачу одного и того же экземпляра этой службы каждому компоненту, службе и т. Д. В этом модуле. https://angular.io/docs/ts/latest/guide/ngmodule.html#providers

ОБНОВЛЕНИЕ : в разработке Angular и FE в целом произошло многое. Как упоминал @noririco, вы также можете использовать систему управления состоянием, такую ​​как NgRx: https://ngrx.io/

Пер Хорнсхой-Ширбек
источник
Шесть методов обмена данными между компонентами angular: - 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
Я понял. Вы вводите только один экземпляр службы - в «приложение». Этот же экземпляр наследуется автоматически при добавлении параметра в дочерние конструкторы :) Я допустил ошибку, добавив еще один appInjector к дочерним компонентам, который создает новые экземпляры.
Пер Хорнсхой-Ширбек
1
@AlexanderCrush не могли бы вы обновить свой ответ? Поскольку в более поздних альфа-версиях (альфа 30+) appInjector был удален . Правильный ответ на данный момент - использовать viewInjector.
Эрик Мартинес
1
@EricMartinez спасибо, ответ и плункер были обновлены.
Александр Ермолов
1
Интересный ресурс, чтобы понять, почему и как это работает: blog.oughttram.io/angular/2015/08/20/… .
soyuka 01
2
У меня была такая же проблема, потому что я внедрял службу в основное приложение, а также в сам компонент, использующий providers: [MyService]. Удалив провайдеров, он стал единственным экземпляром приложения
maufarinelli
43

Комментарий @maufarinelli заслуживает отдельного ответа, потому что, пока я его не увидел, я все еще бил головой об стену с этой проблемой даже с ответом @Alexander Ermolov.

Проблема в том, что когда вы добавляете providersв свой component:

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

Это приводит к внедрению нового экземпляра вашей службы ... вместо того, чтобы быть синглтоном .

Поэтому удалите все экземпляры your providers: [MyService]в своем приложении, кроме как в module, и он будет работать!

Серж Саган
источник
2
Просто комментарий, это никогда не синглтон - это просто передается один и тот же экземпляр. Вы все еще можете запросить новый экземпляр ...
Пер Хорнсхой-Ширбек
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, это было бы очень хорошо ;-)

Вы можете найти другой способ, обратившись к поставщику услуг. Вы также можете посмотреть это видео для справки: ( Обмен данными между компонентами в 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) A -> B -> C У A есть два дочерних элемента B и C, и если вы хотите обмениваться данными между A и B или A и C, используйте (ввод / вывод)

Если вы хотите поделиться между B и C, вы также можете использовать (ввод / вывод), но рекомендуется использовать Service.

б) Если дерево большое и сложное. (если существует так много уровней родительских и дочерних соединений.) И в этом случае, если вы хотите поделиться данными, я бы предложил ngrx

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

Пратиюш
источник