¿Cómo comparto datos entre componentes en Angular 2?

84

En Angular 1.xx, simplemente solicita el mismo servicio y termina con la misma instancia, lo que hace posible compartir los datos en el servicio.

Ahora en Angular 2 tengo un componente que tiene una referencia a mi servicio. Puedo leer y modificar los datos en el servicio, lo cual es bueno. Cuando intento inyectar el mismo servicio en otro componente, parece que obtengo una nueva instancia.

¿Qué estoy haciendo mal? ¿Es el patrón en sí el que está mal (usar un servicio para compartir datos) o necesito marcar el servicio como singleton (dentro de una instancia de la aplicación) o algo así?

Estoy en por 2.0.0-alpha.27/ cierto

Inyecto un servicio a través de appInjector(editar: ahora providers) en la @Componentanotación y luego guardo una referencia en el constructor. Funciona localmente en el componente, pero no entre componentes (no comparten la misma instancia de servicio) como pensé que lo harían.

ACTUALIZACIÓN : A partir de Angular 2.0.0 ahora tenemos @ngModule donde definiría el servicio bajo la providerspropiedad en dicho @ngModule. Eso asegurará que la misma instancia de ese servicio se pase a cada componente, servicio, etc. en ese módulo. https://angular.io/docs/ts/latest/guide/ngmodule.html#providers

ACTUALIZACIÓN : Mucho ha sucedido con el desarrollo de Angular y FE en general. Como mencionó @noririco, también podría usar un sistema de gestión estatal como NgRx: https://ngrx.io/

Per Hornshøj-Schierbeck
fuente
Seis métodos para compartir datos entre componentes angulares: - angulartutorial.net/2017/12/…
Prashobh
Si llega aquí, considere usar un sistema de gestión ESTATAL
noririco

Respuestas:

63

Un servicio singleton es una buena solución. De otra manera - data/events bindings.

A continuación, se muestra un ejemplo de ambos:

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

Ver en directo

Alexander Ermolov
fuente
20
Me lo imaginé. Solo inyecta una instancia de servicio: en 'aplicación'. Esa misma instancia se hereda automáticamente al agregar el parámetro a los constructores secundarios :) Cometí el error de agregar otro appInjector a los componentes secundarios que crea nuevas instancias.
Per Hornshøj-Schierbeck
1
@AlexanderCrush, ¿podrías actualizar tu respuesta? Dado que en las versiones alfa posteriores (alpha 30+) se eliminó appInjector . La respuesta correcta, por ahora, debería ser utilizar viewInjector.
Eric Martinez
1
@EricMartinez gracias, respuesta y plunker han sido actualizados.
Alexander Ermolov
1
Recurso interesante para entender por qué y cómo funciona esto: blog . Thoughtram.io/angular/2015/08/20/… .
soyuka
2
Tenía el mismo problema, porque estaba inyectando el servicio en la aplicación principal y también en el propio componente que usaba providers: [MyService]. Al eliminar los proveedores, se convirtió en la única instancia de la aplicación
maufarinelli
43

El comentario de @maufarinelli merece su propia respuesta porque hasta que lo vi, todavía estaba golpeándome la cabeza contra la pared con este problema incluso con la respuesta de @Alexander Ermolov.

El problema es que cuando agregas un providersa tu component:

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

Esto hace que se inyecte una nueva instancia de su servicio ... en lugar de ser un singleton .

Así que elimine todas las instancias de su providers: [MyService]en su aplicación, excepto en el module, ¡y funcionará!

Serj Sagan
fuente
2
Solo un comentario, nunca es un singleton, es solo la misma instancia que se transmite. Aún puede solicitar una nueva instancia ...
Per Hornshøj-Schierbeck
10

Debe utilizar entradas y salidas de un decorador de @Component. Aquí está el ejemplo más básico de usar ambos;

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);
Jan Kuri
fuente
7
No hay necesidad de importar ngFor,
Richard Hamilton
7

En la plantilla de componente principal:

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

En el componente hijo:

@Input() hero: Hero;

Fuente: https://angular.io/docs/ts/latest/cookbook/component-communication.html

Alexis Gamarra
fuente
Puede ser, pero esto necesitaría más detalles. En el mundo real no es tan fácil. Imagine que tiene una clase que desea compartir entre varios componentes y acceder a los datos. eso no funciona.
sancelote
Estoy usando este enfoque en una gran solución para compartir datos entre muchos componentes. Puede tener muchos hijos y cada uno reciba el mismo objeto. ¿Intentaste hacer esto antes de decir que no funciona?
Alexis Gamarra
Sí, lo hice . Funcionará ... pero con algunos "hackeos" para solucionar algunos problemas. Tu respuesta no permite que nadie la use.
sancelote
2

Hay muchas maneras. Este es un ejemplo que utiliza la propagación entre elementos padre e hijo. Esto es muy eficaz.

Envié un ejemplo que permite ver el uso de dos formas de enlace de datos dentro de dos formas. Si alguien puede proporcionar una muestra de plunkr, sería muy bueno ;-)

Puede buscar otra forma utilizando un proveedor de servicios. También puede ver este video como referencia: ( Compartir datos entre componentes en Angular )

mymodel.ts (datos para compartir)

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

Recuerde: debe haber un padre que comparta "mymodel" con los componentes secundarios.

Componente padre

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

Componente hijo, 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() {
    }
}

Nota: En algunos casos raros, es posible que tenga un error cuando se analiza el código HTML, porque el modelo no está "listo" para usar en la inicialización de la página. En este caso, anteponga el código HTML con una condición ngIf:

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

Depende, si hay un caso sencillo

a) A -> B -> C A tiene dos hijos B y C y si desea compartir datos entre A y B o A y C, utilice (entrada / salida)

Si desea compartir entre B y C, también puede usar (entrada / salida), pero se sugiere usar Service.

b) Si el árbol es grande y complejo. (si hay tantos niveles de conexiones entre padres e hijos). Y en este caso, si desea compartir datos, sugeriría ngrx

Implementa la arquitectura de flujo que crea una tienda del lado del cliente a la que cualquier componente puede suscribirse y puede actualizar sin crear ninguna condición de carrera.

Pratiyush
fuente