Angular 2 Sibling Component Communication


118

Mam ListComponent. Po kliknięciu elementu w ListComponent szczegóły tego elementu powinny zostać wyświetlone w DetailComponent. Oba są wyświetlane na ekranie w tym samym czasie, więc nie ma tu żadnego routingu.

Jak powiedzieć DetailComponent, który element w ListComponent został kliknięty?

Rozważyłem wysłanie zdarzenia do elementu nadrzędnego (AppComponent) i kazałem rodzicowi ustawić selectedItem.id na DetailComponent z @Input. Albo mógłbym skorzystać z usługi współdzielonej z obserwowalnymi subskrypcjami.


EDYCJA: Ustawienie wybranego elementu za pomocą zdarzenia + @Input nie powoduje jednak wywołania elementu DetailComponent na wypadek, gdyby trzeba było wykonać dodatkowy kod. Więc nie jestem pewien, czy to akceptowalne rozwiązanie.


Ale obie te metody wydają się o wiele bardziej złożone niż sposób robienia rzeczy w Angular 1, który był albo przez $ rootScope. $ Broadcast, albo $ scope. $ Parent. $ Broadcast.

Ponieważ wszystko w Angular 2 jest komponentem, jestem zaskoczony, że nie ma więcej informacji o komunikacji komponentów.

Czy jest inny / prostszy sposób na osiągnięcie tego?


Czy znalazłeś sposób na udostępnianie danych rodzeństwa? Potrzebuję tego jako możliwego do zaobserwowania ...
Człowiek,

Odpowiedzi:


65

Zaktualizowano do rc.4: Podczas próby przesłania danych między komponentami rodzeństwa w angular 2, najprostszym obecnie sposobem (angular.rc.4) jest wykorzystanie hierarchicznego wstrzyknięcia zależności angular2 i utworzenie usługi współdzielonej.

Oto usługa:

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

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

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

Teraz tutaj będzie komponent 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{

} 

i dwoje jego dzieci

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

dziecko 2 (rodzeństwo)

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 = '';
    }
}

TERAZ: Rzeczy, o których należy pamiętać podczas korzystania z tej metody.

  1. Uwzględnij tylko dostawcę usług wspólnych w komponencie PARENT, a NIE dzieci.
  2. Nadal musisz dołączyć konstruktorów i zaimportować usługę w elementach podrzędnych
  3. Ta odpowiedź została pierwotnie udzielona dla wczesnej wersji beta Angular 2. Jedyne, co się zmieniło, to instrukcje importu, więc to wszystko, co musisz zaktualizować, jeśli przypadkowo użyłeś oryginalnej wersji.

2
Czy jest to nadal ważne dla angular-rc1?
Sergio

4
Nie sądzę jednak, aby to powiadomiło rodzeństwo, że coś się zaktualizowało w usłudze wspólnej. Jeśli komponent potomny1 robi coś, na co komponent potomny2 musi odpowiedzieć, ta metoda tego nie obsługuje. Wierzę, że sposób na to jest z obserwa- lami?
dennis.sheppard

1
@Sufyan: Zgaduję, że dodanie pól dostawcy do dzieci powoduje, że Angular tworzy nowe prywatne instancje dla każdego z nich. Gdy ich nie dodasz, używają instancji „singleton” rodzica.
Ralph

1
Wygląda na to, że to już nie działa z najnowszymi aktualizacjami
Sufyan Jabr

3
To jest nieaktualne. directivesnie są już deklarowane w komponencie.
Nate Gardner

26

W przypadku dwóch różnych komponentów (nie zagnieżdżonych komponentów, rodzic \ dziecko \ wnuk) proponuję to:

MisjaSerwis:

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

}

Astronauta Składnik:

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

Źródło: rodzice i dzieci komunikują się za pośrednictwem usługi


2
Chciałbym, abyś dodał trochę terminologii do tej odpowiedzi. Uważam, że jest to zgodne z RxJS, wzorcem obserwowalnym itp. Nie do końca pewne, ale dodanie opisów do niektórych z nich byłoby korzystne dla ludzi (takich jak ja).
karns

13

Jednym ze sposobów jest skorzystanie z usługi współdzielonej .

Jednak uważam, że poniższe rozwiązanie jest znacznie prostsze, pozwala na udostępnianie danych między dwojgiem rodzeństwa (testowałem to tylko na Angular 5 )

W swoim macierzystym szablonie komponentu:

<!-- 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;
   ...
}

Czy nie jest to sprzeczne z ideą luźnego sprzężenia, a tym samym elementów?
Robin

Czy ktoś ma pojęcie, czy jest to czysty czy brudny sposób? Wydaje się o wiele prostsze w przypadku udostępniania danych w jednym kierunku, na przykład tylko od rodzenia1 do rodzenia2, ale nie odwrotnie
Sarah


7

Dyrektywa może mieć sens w pewnych sytuacjach, aby „połączyć” komponenty. W rzeczywistości podłączone elementy nie muszą być nawet pełnymi komponentami, a czasami jest to lżejsze i prostsze, jeśli nie są.

Na przykład mam Youtube Playerkomponent (owijający YouTube API) i chciałem do niego kilka przycisków kontrolera. Jedynym powodem, dla którego przyciski nie są częścią mojego głównego komponentu, jest to, że znajdują się w innym miejscu DOM.

W tym przypadku jest to tak naprawdę tylko komponent „rozszerzenia”, który będzie używany tylko z komponentem „nadrzędnym”. Mówię „rodzic”, ale w DOM jest to rodzeństwo - więc nazwij to, jak chcesz.

Tak jak powiedziałem, nie musi to być nawet pełny komponent, w moim przypadku to tylko <button>(ale może to być komponent).

@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
   }
}

W kodzie HTML dla ProductPage.component, gdzie youtube-playerjest oczywiście mój komponent, który otacza API YouTube.

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

... lots more DOM ...

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

Dyrektywa podpina wszystko za mnie i nie muszę deklarować zdarzenia (kliknięcia) w kodzie HTML.

Dzięki temu dyrektywa może ładnie łączyć się z odtwarzaczem wideo bez konieczności angażowania się ProductPagew rolę mediatora.

To pierwszy raz, kiedy to zrobiłem, więc nie jestem jeszcze pewien, jak skalowalny może być w znacznie bardziej złożonych sytuacjach. Z tego powodu jestem zadowolony i pozostawia mój HTML prosty i odpowiedzialność za wszystko odrębne.


Jedną z najważniejszych koncepcji kątowych, które należy zrozumieć, jest to, że komponent to tylko dyrektywa z szablonem. Kiedy już naprawdę docenisz, co to oznacza, dyrektywy nie są już tak przerażające - i zdasz sobie sprawę, że możesz zastosować je do dowolnego elementu, aby dołączyć do niego zachowanie.
Simon_Weaver

Próbowałem tego, ale pojawia się błąd podwójnego identyfikatora dla odpowiednika player. Jeśli pominę pierwszą wzmiankę o graczu, pojawi się RangeError. Jestem zdumiony, jak to ma działać.
Katharine Osborne

@KatharineOsborne Wygląda na to, że w moim rzeczywistym kodzie używam _playerpola prywatnego, które reprezentuje gracza, więc tak, dostaniesz błąd, jeśli skopiujesz to dokładnie. Zaktualizuję. Przepraszam!
Simon_Weaver,

4

Oto proste praktyczne wyjaśnienie: po prostu wyjaśnione tutaj

W 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();
 }
}

Komponent, z którego chcesz wywołać obserwowalny, aby poinformować inny komponent, że przycisk został kliknięty

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

Komponent, w którym chcesz wykonać akcję po kliknięciu przycisku na innym komponencie

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

});

}

Moje zrozumienie komunikacji komponentów jest jasne, czytając to: http://musttoknow.com/angular-4-angular-5-communicate-two-components-using-observable-subject/


Hej wielkie dzięki za proste rozwiązanie> wypróbowałem to w stackblitz, które działało dobrze. Ale moja aplikacja ma leniwie ładowane trasy (użyłem podanych w: „root”) i wywołania HTTP do ustawiania i pobierania. Czy możesz mi pomóc z połączeniami HTTP. próbował dużo, ale nie działa:
Kshri

4

Usługa współdzielona jest dobrym rozwiązaniem tego problemu. Jeśli chcesz również przechowywać informacje o aktywności, możesz dodać usługę udostępnioną do listy dostawców głównych modułów (moduł aplikacji).

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

Następnie możesz dostarczyć go bezpośrednio do swoich komponentów,

constructor(private sharedService: SharedService)

Dzięki usłudze Shared Service możesz użyć funkcji lub utworzyć Temat, aby zaktualizować wiele miejsc jednocześnie.

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

W komponencie listy możesz publikować informacje o klikniętym elemencie,

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

a następnie możesz pobrać te informacje do komponentu szczegółów:

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

Oczywiście dane udostępniane przez składnik listy mogą być dowolne. Mam nadzieję że to pomoże.


Jest to najprostszy (aka zwięzły) przykład tej koncepcji usługi współdzielonej i naprawdę należy go głosować za, aby zwiększyć jej widoczność, ponieważ nie ma akceptowanej odpowiedzi.
iGanja

3

Musisz skonfigurować relację rodzic-dziecko między komponentami. Problem polega na tym, że możesz po prostu wstrzyknąć komponenty potomne do konstruktora komponentu nadrzędnego i zapisać je w zmiennej lokalnej. Zamiast tego należy zadeklarować składniki podrzędne w składniku nadrzędnym za pomocą @ViewChilddeklaratora właściwości. Tak powinien wyglądać Twój komponent nadrzędny:

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

Uwaga, komponent potomny nie będzie dostępny w konstruktorze komponentu macierzystego zaraz po ngAfterViewInitwywołaniu zaczepu cyklu życia. Aby złapać ten haczyk, po prostu zaimplementuj AfterViewInitinterfejs w swojej klasie nadrzędnej w taki sam sposób, jak zrobiłbyś to zOnInit .

Istnieją jednak inne deklaratory właściwości, jak wyjaśniono w tej notatce na blogu: http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/


2

Podmioty zachowania. Napisałem o tym blog .

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

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

W tym przykładzie deklaruję zachowanie noida typu numer. To także jest obserwowalne. A jeśli „coś się wydarzy”, zmieni się to dzięki funkcji new () {}.

Tak więc w komponentach rodzeństwa jeden wywoła funkcję, aby dokonać zmiany, a drugi zostanie dotknięty tą zmianą lub odwrotnie.

Na przykład pobieram identyfikator z adresu URL i aktualizuję noid z tematu zachowania.

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

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

A z drugiej strony mogę zapytać, czy ten identyfikator jest „cokolwiek chcę”, a następnie dokonać wyboru, w moim przypadku, jeśli chcę usunąć zadanie, a tym zadaniem jest bieżący adres URL, musi mnie przekierować do domu:

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

Nie jest to dokładnie to, czego chcesz, ale na pewno ci pomoże

Jestem zaskoczony, że nie ma więcej informacji na temat komunikacji komponentów <=> rozważ ten samouczek autorstwa angualr2

W przypadku komunikacji z komponentami rodzeństwa proponuję skorzystać z sharedService. Dostępne są jednak również inne opcje.

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,[]);

Więcej kodu znajdziesz w linku u góry.

Edycja: To jest bardzo małe demo. Wspomniałeś już, że próbowałeś już z sharedService. Dlatego prosimy o rozważenie tego samouczka autorstwa angualr2, aby uzyskać więcej informacji.


0

Przekazywałem metody ustawiające z elementu nadrzędnego do jednego z jego elementów podrzędnych za pośrednictwem powiązania, wywołując tę ​​metodę z danymi ze składnika podrzędnego, co oznacza, że ​​składnik nadrzędny jest aktualizowany i może następnie zaktualizować drugi składnik podrzędny o nowe dane. Wymaga jednak wiązania „this” lub użycia funkcji strzałki.

Ma to tę zaletę, że dzieci nie są ze sobą tak połączone, ponieważ nie potrzebują specjalnej wspólnej usługi.

Nie jestem całkowicie pewien, czy jest to najlepsza praktyka, byłoby interesujące usłyszeć opinie innych na ten temat.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.