Comunicazione Component Angular 2 Sibling


118

Ho un ListComponent. Quando si fa clic su un elemento in ListComponent, i dettagli di quell'elemento dovrebbero essere visualizzati in DetailComponent. Entrambi sono sullo schermo contemporaneamente, quindi non sono coinvolti percorsi.

Come faccio a dire a DetailComponent quale elemento in ListComponent è stato cliccato?

Ho considerato l'emissione di un evento fino al genitore (AppComponent) e chiedo al genitore di impostare selectedItem.id su DetailComponent con un @Input. Oppure potrei usare un servizio condiviso con sottoscrizioni osservabili.


MODIFICA: L' impostazione dell'elemento selezionato tramite l'evento + @Input non attiva il DetailComponent, tuttavia, nel caso in cui dovessi eseguire codice aggiuntivo. Quindi non sono sicuro che questa sia una soluzione accettabile.


Ma entrambi questi metodi sembrano molto più complessi del modo Angular 1 di fare le cose che era tramite $ rootScope. $ Broadcast o $ scope. $ Parent. $ Broadcast.

Poiché tutto in Angular 2 è un componente, sono sorpreso che non ci siano più informazioni là fuori sulla comunicazione dei componenti.

C'è un altro / più semplice modo per farlo?


Hai trovato un modo per condividere i dati tra fratelli? Ne ho bisogno come osservabile ..
Essere umano

Risposte:


65

Aggiornato a rc.4: quando si tenta di ottenere dati passati tra componenti di pari livello in angular 2, il modo più semplice in questo momento (angular.rc.4) è sfruttare l'iniezione di dipendenza gerarchica di angular2 e creare un servizio condiviso.

Ecco il servizio:

import {Injectable} from '@angular/core';

@Injectable()
export class SharedService {
    dataArray: string[] = [];

    insertData(data: string){
        this.dataArray.unshift(data);
    }
}

Ora, qui sarebbe il componente PARENT

import {Component} from '@angular/core';
import {SharedService} from './shared.service';
import {ChildComponent} from './child.component';
import {ChildSiblingComponent} from './child-sibling.component';
@Component({
    selector: 'parent-component',
    template: `
        <h1>Parent</h1>
        <div>
            <child-component></child-component>
            <child-sibling-component></child-sibling-component>
        </div>
    `,
    providers: [SharedService],
    directives: [ChildComponent, ChildSiblingComponent]
})
export class parentComponent{

} 

ei suoi due figli

bambino 1

import {Component, OnInit} from '@angular/core';
import {SharedService} from './shared.service'

@Component({
    selector: 'child-component',
    template: `
        <h1>I am a child</h1>
        <div>
            <ul *ngFor="#data in data">
                <li>{{data}}</li>
            </ul>
        </div>
    `
})
export class ChildComponent implements OnInit{
    data: string[] = [];
    constructor(
        private _sharedService: SharedService) { }
    ngOnInit():any {
        this.data = this._sharedService.dataArray;
    }
}

bambino 2 (è fratello)

import {Component} from 'angular2/core';
import {SharedService} from './shared.service'

@Component({
    selector: 'child-sibling-component',
    template: `
        <h1>I am a child</h1>
        <input type="text" [(ngModel)]="data"/>
        <button (click)="addData()"></button>
    `
})
export class ChildSiblingComponent{
    data: string = 'Testing data';
    constructor(
        private _sharedService: SharedService){}
    addData(){
        this._sharedService.insertData(this.data);
        this.data = '';
    }
}

ORA: cose da tenere in considerazione quando si utilizza questo metodo.

  1. Includere solo il fornitore di servizi per il servizio condiviso nel componente GENITORE e NON i bambini.
  2. Devi ancora includere i costruttori e importare il servizio nei figli
  3. Questa risposta è stata originariamente risolta per una prima versione beta angolare 2. Tuttavia, tutto ciò che è cambiato sono le istruzioni di importazione, quindi è tutto ciò di cui hai bisogno per aggiornare se hai usato la versione originale per caso.

2
Questo è ancora valido per angular-rc1?
Sergio

4
Tuttavia, non credo che questo notifichi al fratello che qualcosa è stato aggiornato nel servizio condiviso. Se child-component1 fa qualcosa a cui child-component2 deve rispondere, questo metodo non lo gestisce. Credo che il modo per aggirare sia con gli osservabili?
dennis.sheppard

1
@ Sufyan: Immagino che l'aggiunta dei campi del provider ai figli induca Angular a creare nuove istanze private per ciascuno. Quando non li aggiungi, usano l'istanza "singleton" del genitore.
Ralph

1
Sembra che questo non funzioni più con gli ultimi aggiornamenti
Sufyan Jabr

3
Questo è obsoleto. directivesnon sono più dichiarati nel componente.
Nate Gardner,

26

In caso di 2 componenti differenti (non componenti nidificati, genitore \ figlio \ nipote) ti suggerisco questo:

MissionService:

import { Injectable } from '@angular/core';
import { Subject }    from 'rxjs/Subject';

@Injectable()

export class MissionService {
  // Observable string sources
  private missionAnnouncedSource = new Subject<string>();
  private missionConfirmedSource = new Subject<string>();
  // Observable string streams
  missionAnnounced$ = this.missionAnnouncedSource.asObservable();
  missionConfirmed$ = this.missionConfirmedSource.asObservable();
  // Service message commands
  announceMission(mission: string) {
    this.missionAnnouncedSource.next(mission);
  }
  confirmMission(astronaut: string) {
    this.missionConfirmedSource.next(astronaut);
  }

}

AstronautComponent:

import { Component, Input, OnDestroy } from '@angular/core';
import { MissionService } from './mission.service';
import { Subscription }   from 'rxjs/Subscription';
@Component({
  selector: 'my-astronaut',
  template: `
    <p>
      {{astronaut}}: <strong>{{mission}}</strong>
      <button
        (click)="confirm()"
        [disabled]="!announced || confirmed">
        Confirm
      </button>
    </p>
  `
})
export class AstronautComponent implements OnDestroy {
  @Input() astronaut: string;
  mission = '<no mission announced>';
  confirmed = false;
  announced = false;
  subscription: Subscription;
  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission => {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }
  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }
  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}

Fonte: genitore e figli comunicano tramite un servizio


2
Vorrei che aggiungessi un po 'di terminologia a questa risposta. Credo che sia in linea con RxJS, pattern Observable, ecc. Non del tutto sicuro, ma aggiungere descrizioni ad alcune di queste sarebbe vantaggioso per le persone (come me).
karns

13

Un modo per farlo è utilizzare un servizio condiviso .

Tuttavia trovo la seguente soluzione molto più semplice, che consente di condividere i dati tra 2 fratelli (l'ho testato solo su Angular 5 )

Nel modello del componente principale:

<!-- Assigns "AppSibling1Component" instance to variable "data" -->
<app-sibling1 #data></app-sibling1>
<!-- Passes the variable "data" to AppSibling2Component instance -->
<app-sibling2 [data]="data"></app-sibling2> 

app-sibling2.component.ts

import { AppSibling1Component } from '../app-sibling1/app-sibling1.component';
...

export class AppSibling2Component {
   ...
   @Input() data: AppSibling1Component;
   ...
}

Non è questo in contrasto con l'idea di accoppiamento lasco e quindi di componenti?
Robin

Qualcuno ha idea se questo sia un modo pulito o sporco? Sembra molto più semplice condividere i dati in una direzione, ad esempio solo da sibiling1 a sibiling2, ma non viceversa
Sarah


7

Una direttiva può avere senso in determinate situazioni per "collegare" i componenti. In effetti, le cose da collegare non devono nemmeno essere componenti complete, e talvolta è più leggero e in realtà più semplice se non lo sono.

Ad esempio, ho un Youtube Playercomponente (wrapping dell'API di Youtube) e volevo alcuni pulsanti del controller per questo. L'unico motivo per cui i pulsanti non fanno parte del mio componente principale è che si trovano altrove nel DOM.

In questo caso è in realtà solo un componente "estensione" che potrà essere utilizzato solo con il componente "genitore". Dico "genitore", ma nel DOM è un fratello, quindi chiamalo come vuoi.

Come ho detto, non deve nemmeno essere un componente completo, nel mio caso è solo un <button>(ma potrebbe essere un componente).

@Directive({
    selector: '[ytPlayerPlayButton]'
})
export class YoutubePlayerPlayButtonDirective {

    _player: YoutubePlayerComponent; 

    @Input('ytPlayerVideo')
    private set player(value: YoutubePlayerComponent) {
       this._player = value;    
    }

    @HostListener('click') click() {
        this._player.play();
    }

   constructor(private elementRef: ElementRef) {
       // the button itself
   }
}

Nell'HTML per ProductPage.component, dov'è youtube-playerovviamente il mio componente che avvolge l'API di Youtube.

<youtube-player #technologyVideo videoId='NuU74nesR5A'></youtube-player>

... lots more DOM ...

<button class="play-button"        
        ytPlayerPlayButton
        [ytPlayerVideo]="technologyVideo">Play</button>

La direttiva aggancia tutto per me e non devo dichiarare l'evento (clic) nell'HTML.

Quindi la direttiva può connettersi perfettamente al lettore video senza dover coinvolgere ProductPagecome mediatore.

Questa è la prima volta che lo faccio effettivamente, quindi non sono ancora sicuro di quanto possa essere scalabile per situazioni molto più complesse. Per questo però sono felice e lascia il mio HTML semplice e le responsabilità di tutto distinte.


Uno dei concetti angolari più importanti da comprendere è che un componente è solo una direttiva con un modello. Una volta che hai davvero apprezzato il significato di questo, le direttive non sono così spaventose e ti renderai conto che puoi applicarle a qualsiasi elemento per associarvi un comportamento.
Simon_Weaver

Ho provato questo ma ottengo un errore di identificatore duplicato per l'equivalente di player. Se tralascio la prima menzione di player ottengo un rangeError. Sono perplesso su come dovrebbe funzionare.
Katharine Osborne

@KatharineOsborne Sembra che nel mio codice attuale io uso _playerper il campo privato che rappresenta il giocatore, quindi sì, riceveresti un errore se lo copiassi esattamente. Si aggiornerà. Scusa!
Simon_Weaver

4

Ecco una semplice spiegazione pratica: Spiegata semplicemente qui

In call.service.ts

import { Observable } from 'rxjs';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class CallService {
 private subject = new Subject<any>();

 sendClickCall(message: string) {
    this.subject.next({ text: message });
 }

 getClickCall(): Observable<any> {
    return this.subject.asObservable();
 }
}

Componente da cui si desidera chiamare osservabile per informare un altro componente che è stato fatto clic sul pulsante

import { CallService } from "../../../services/call.service";

export class MarketplaceComponent implements OnInit, OnDestroy {
  constructor(public Util: CallService) {

  }

  buttonClickedToCallObservable() {
   this.Util.sendClickCall('Sending message to another comp that button is clicked');
  }
}

Componente in cui si desidera eseguire un'azione sul pulsante cliccato su un altro componente

import { Subscription } from 'rxjs/Subscription';
import { CallService } from "../../../services/call.service";


ngOnInit() {

 this.subscription = this.Util.getClickCall().subscribe(message => {

 this.message = message;

 console.log('---button clicked at another component---');

 //call you action which need to execute in this component on button clicked

 });

}

import { Subscription } from 'rxjs/Subscription';
import { CallService } from "../../../services/call.service";


ngOnInit() {

 this.subscription = this.Util.getClickCall().subscribe(message => {

 this.message = message;

 console.log('---button clicked at another component---');

 //call you action which need to execute in this component on button clicked

});

}

La mia comprensione è chiara sulla comunicazione dei componenti leggendo questo: http://musttoknow.com/angular-4-angular-5-communicate-two-components-using-observable-subject/


Ehi, grazie mille per la semplice soluzione> l'ho provato in Stackblitz che ha funzionato bene. Ma la mia applicazione ha percorsi caricati in modo pigro (ho usato providedin: 'root') e chiamate HTTP per impostare e ottenere. Potresti aiutarmi con le chiamate HTTP. ho provato molto ma non funzionava:
Kshri

4

Il servizio condiviso è una buona soluzione per questo problema. Se desideri memorizzare anche alcune informazioni sull'attività, puoi aggiungere il servizio condiviso all'elenco dei provider dei moduli principali (app.module).

@NgModule({
    imports: [
        ...
    ],
    bootstrap: [
        AppComponent
    ],
    declarations: [
        AppComponent,
    ],
    providers: [
        SharedService,
        ...
    ]
});

Quindi puoi fornirlo direttamente ai tuoi componenti,

constructor(private sharedService: SharedService)

Con il servizio condiviso puoi utilizzare le funzioni o creare un oggetto per aggiornare più luoghi contemporaneamente.

@Injectable()
export class FolderTagService {
    public clickedItemInformation: Subject<string> = new Subject(); 
}

Nel tuo componente elenco puoi pubblicare le informazioni sugli elementi cliccati,

this.sharedService.clikedItemInformation.next("something");

e quindi puoi recuperare queste informazioni nel tuo componente dettagli:

this.sharedService.clikedItemInformation.subscribe((information) => {
    // do something
});

Ovviamente, i dati condivisi dai componenti dell'elenco possono essere qualsiasi cosa. Spero che questo ti aiuti.


Questo è l'esempio più diretto (noto anche come conciso) di questo concetto di servizio condiviso e dovrebbe davvero essere votato verso l'alto per aumentarne la visibilità, poiché non esiste una risposta accettata.
iGanja

3

È necessario impostare la relazione genitore-figlio tra i componenti. Il problema è che potresti semplicemente iniettare i componenti figlio nel costruttore del componente genitore e memorizzarli in una variabile locale. Invece, è necessario dichiarare i componenti figlio nel componente padre utilizzando il @ViewChilddichiaratore di proprietà. Ecco come dovrebbe apparire il tuo componente genitore:

import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { ListComponent } from './list.component';
import { DetailComponent } from './detail.component';

@Component({
  selector: 'app-component',
  template: '<list-component></list-component><detail-component></detail-component>',
  directives: [ListComponent, DetailComponent]
})
class AppComponent implements AfterViewInit {
  @ViewChild(ListComponent) listComponent:ListComponent;
  @ViewChild(DetailComponent) detailComponent: DetailComponent;

  ngAfterViewInit() {
    // afther this point the children are set, so you can use them
    this.detailComponent.doSomething();
  }
}

https://angular.io/docs/ts/latest/api/core/index/ViewChild-var.html

https://angular.io/docs/ts/latest/cookbook/component-communication.html#parent-to-view-child

Attenzione, il componente figlio non sarà disponibile nel costruttore del componente genitore, subito dopo la ngAfterViewInitchiamata dell'hook del ciclo di vita. Per catturare questo hook, implementa semplicemente l' AfterViewInitinterfaccia nella tua classe genitore nello stesso modo in cui faresti conOnInit .

Tuttavia, ci sono altri dichiaratori di proprietà come spiegato in questa nota del blog: http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/


2

Materie comportamentali. Ho scritto un blog su questo.

import { BehaviorSubject } from 'rxjs/BehaviorSubject';
private noId = new BehaviorSubject<number>(0); 
  defaultId = this.noId.asObservable();

newId(urlId) {
 this.noId.next(urlId); 
 }

In questo esempio sto dichiarando un soggetto di comportamento noid di tipo numero. Inoltre è un osservabile. E se "qualcosa è accaduto" questo cambierà con la funzione new () {}.

Quindi, nei componenti del fratello, uno chiamerà la funzione, per apportare la modifica, e l'altro sarà influenzato da quel cambiamento, o viceversa.

Ad esempio, ottengo l'ID dall'URL e aggiorno il noid dall'oggetto del comportamento.

public getId () {
  const id = +this.route.snapshot.paramMap.get('id'); 
  return id; 
}

ngOnInit(): void { 
 const id = +this.getId ();
 this.taskService.newId(id) 
}

E dall'altra parte, posso chiedere se quell'ID è "quello che voglio" e fare una scelta dopo, nel mio caso se voglio eliminare un'attività e quell'attività è l'URL corrente, deve reindirizzarmi a casa:

delete(task: Task): void { 
  //we save the id , cuz after the delete function, we  gonna lose it 
  const oldId = task.id; 
  this.taskService.deleteTask(task) 
      .subscribe(task => { //we call the defaultId function from task.service.
        this.taskService.defaultId //here we are subscribed to the urlId, which give us the id from the view task 
                 .subscribe(urlId => {
            this.urlId = urlId ;
                  if (oldId == urlId ) { 
                // Location.call('/home'); 
                this.router.navigate(['/home']); 
              } 
          }) 
    }) 
}

1

Questo non è esattamente quello che vuoi, ma di sicuro ti aiuterà

Sono sorpreso che non ci siano più informazioni là fuori sulla comunicazione dei componenti <=> considera questo tutorial di angualr2

Per la comunicazione di componenti tra fratelli, suggerirei di andare con sharedService. Tuttavia, sono disponibili anche altre opzioni.

import {Component,bind} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {HTTP_PROVIDERS} from 'angular2/http';
import {NameService} from 'src/nameService';


import {TheContent} from 'src/content';
import {Navbar} from 'src/nav';


@Component({
  selector: 'app',
  directives: [TheContent,Navbar],
  providers: [NameService],
  template: '<navbar></navbar><thecontent></thecontent>'
})


export class App {
  constructor() {
    console.log('App started');
  }
}

bootstrap(App,[]);

Fare riferimento al collegamento in alto per ulteriori codici.

Modifica: questa è una demo molto piccola. Hai già detto che hai già provato con sharedService. Quindi per favore considera questo tutorial di angualr2 per maggiori informazioni.


0

Ho trasmesso metodi setter dal genitore a uno dei suoi figli attraverso un'associazione, chiamando quel metodo con i dati dal componente figlio, il che significa che il componente genitore viene aggiornato e può quindi aggiornare il suo secondo componente figlio con i nuovi dati. Tuttavia, richiede l'associazione "this" o l'utilizzo di una funzione freccia.

Questo ha il vantaggio che i bambini non sono così accoppiati tra loro perché non hanno bisogno di uno specifico servizio condiviso.

Non sono del tutto sicuro che questa sia la migliore pratica, sarebbe interessante sentire altri punti di vista su questo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.