wake-up-neo.net

Angular 2 Sibling Component Communication

Ich habe eine ListComponent. Wenn Sie in ListComponent auf ein Element klicken, sollten die Details dieses Elements in DetailComponent angezeigt werden. Beide werden gleichzeitig auf dem Bildschirm angezeigt, sodass kein Routing erforderlich ist.

Wie kann ich DetailComponent mitteilen, auf welches Element in ListComponent geklickt wurde?

Ich habe überlegt, ein Ereignis an das übergeordnete Element (AppComponent) zu senden, und das übergeordnete Element selectedItem.id für DetailComponent mit einem @Input festgelegt haben. Oder ich könnte einen gemeinsam genutzten Dienst mit beobachtbaren Abonnements verwenden.


EDIT: Setzen des ausgewählten Elements über Ereignis + @Input löst die DetailComponent nicht aus, falls ich zusätzlichen Code ausführen müsste. Ich bin mir nicht sicher, ob dies eine akzeptable Lösung ist.


Beide Methoden scheinen jedoch weitaus komplexer zu sein als die Angular-1-Methode, entweder durch $ rootScope. $ Broadcast oder $ scope. $ Parent. $ Broadcast.

Da alles in Angular 2 eine Komponente ist, bin ich überrascht, dass es keine weiteren Informationen zur Komponentenkommunikation gibt.

Gibt es einen anderen/einfacheren Weg, dies zu erreichen? 

93
dennis.sheppard

Auf rc.4 aktualisiert: Beim Versuch, Daten zwischen den gleichgeordneten Komponenten in Winkel 2 zu übertragen, besteht der derzeit einfachste Weg (angle.rc.4) darin, die hierarchische Abhängigkeitseingabe von angle2 zu nutzen und eine Shared Service.

Hier wäre der Service:

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

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

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

Nun wäre hier die PARENT-Komponente

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{

} 

und seine zwei Kinder

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

kind 2 (es ist Geschwister)

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

JETZT: Dinge, die zu beachten sind, wenn Sie diese Methode verwenden.

  1. Beziehen Sie nur den Dienstanbieter für den gemeinsam genutzten Dienst in die PARENT-Komponente ein und NICHT die untergeordneten.
  2. Sie müssen noch Konstruktoren einschließen und den Dienst in die untergeordneten Elemente importieren
  3. Diese Antwort wurde ursprünglich für eine frühe 2-Beta-Version beantwortet. Alles, was sich geändert hat, sind die Import-Anweisungen. Sie müssen also nur aktualisieren, wenn Sie die Originalversion zufällig verwendet haben.
61
Alex J

Bei 2 verschiedenen Komponenten (nicht verschachtelte Komponenten, Eltern\Kind\Enkelkind) schlage ich Folgendes vor:

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

}

Astronautenkomponente: 

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

Quelle: Eltern und Kinder kommunizieren über einen Dienst

24
Dudi

Es gibt hier eine Diskussion darüber.

https://github.com/angular/angular.io/issues/2663 ​​

Die Antwort von Alex J ist gut, funktioniert jedoch mit dem aktuellen Angular 4 ab Juli 2017 nicht mehr.

Und dieser Plunker-Link würde zeigen, wie man mit Hilfe von Shared Service und Observable zwischen Geschwistern kommuniziert.

https://embed.plnkr.co/P8xCEwSKgcOg07pwDrlO/

9
João Silva

Eine Möglichkeit hierzu ist die Verwendung eines Shared Service

Ich finde jedoch die folgende -Lösung viel einfacher, sie ermöglicht das Teilen von Daten zwischen zwei Geschwistern. (Ich habe dies nur unter Angular 5 getestet). 

In Ihrer übergeordneten Komponentenvorlage:

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

Eine Direktive kann in bestimmten Situationen sinnvoll sein, um Komponenten zu "verbinden". In der Tat müssen die Dinge, die verbunden werden, nicht einmal vollständige Komponenten sein, und manchmal sind sie leichter und in der Tat einfacher, wenn dies nicht der Fall ist.

Ich habe zum Beispiel eine Youtube Player-Komponente (Youtube API-Wrapping) und wollte einige Controller-Buttons dafür. Der einzige Grund, warum die Schaltflächen nicht Teil meiner Hauptkomponente sind, ist, dass sie sich an anderer Stelle im DOM befinden.

In diesem Fall handelt es sich eigentlich nur um eine "Erweiterungskomponente", die nur für die "Elternkomponente" von Nutzen ist. Ich sage "Eltern", aber im DOM ist es ein Geschwister - also nennen Sie es, wie Sie wollen.

Wie gesagt, es muss nicht einmal eine vollständige Komponente sein, in meinem Fall ist es nur ein <button> (aber es könnte auch eine Komponente sein).

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

Im HTML-Code für ProductPage.component ist youtube-player offensichtlich meine Komponente, die die Youtube-API einschließt.

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

... lots more DOM ...

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

Die Direktive fasst alles für mich zusammen und ich muss das Ereignis (click) nicht im HTML-Code angeben.

Die Direktive kann sich also gut mit dem Videoplayer verbinden, ohne ProductPage als Vermittler einsetzen zu müssen.

Dies ist das erste Mal, dass ich das tatsächlich gemacht habe, also noch nicht sicher, wie skalierbar es für komplexere Situationen sein könnte. Dafür bin ich glücklich und es lässt mein HTML einfach und die Verantwortlichkeiten von allem deutlich.

3
Simon_Weaver

Sie müssen die Eltern-Kind-Beziehung zwischen Ihren Komponenten einrichten. Das Problem ist, dass Sie die untergeordneten Komponenten einfach in den Konstruktor der übergeordneten Komponente einfügen und in einer lokalen Variablen speichern .. _. Stattdessen sollten Sie die untergeordneten Komponenten in Ihrer übergeordneten Komponente mithilfe des @ViewChild-Eigenschaftendeklarators deklarieren. So sollte Ihre übergeordnete Komponente aussehen:

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

Beachten Sie, dass die untergeordnete Komponente nicht direkt im Konstruktor der übergeordneten Komponente verfügbar ist, nachdem der Lebenszyklus-Hook ngAfterViewInit aufgerufen wurde. Um diesen Haken zu fangen, implementieren Sie die Schnittstelle AfterViewInit in Ihrer übergeordneten Klasse genauso wie bei OnInit.

Es gibt jedoch andere Eigenschaftsdeklaratoren, die in diesem Blog-Hinweis erläutert werden: http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/

2
Vereb

Verhaltensthemen. Ich habe ein blog darüber geschrieben. 

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

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

In diesem Beispiel erkläre ich ein Noid-Verhaltensthema der Typennummer. Es ist auch ein beobachtbarer. Und wenn "etwas passiert" ändert sich dies mit der Funktion new () {}. 

In den Komponenten des Geschwisters ruft man also die Funktion auf, um die Änderung vorzunehmen, und die andere wird von dieser Änderung betroffen sein oder umgekehrt. 

Zum Beispiel erhalte ich die ID von der URL und aktualisiere die Noid vom Verhaltenssubjekt. 

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

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

Und von der anderen Seite kann ich fragen, ob diese ID "was immer ich will" ist und danach eine Wahl treffen. In meinem Fall, wenn ich eine Aufgabe löschen möchte, und diese Aufgabe die aktuelle URL ist, muss sie mich umleiten nach Hause: 

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

Dies ist nicht das, was Sie genau wollen, aber es wird Ihnen sicherlich helfen

Ich bin überrascht, es gibt keine weiteren Informationen über die Kommunikation von Komponenten <=> betrachten Sie dieses Tutorial von angualr2

Für die Kommunikation von Geschwisterkomponenten würde ich vorschlagen, mit sharedService zu gehen. Es gibt jedoch auch andere Optionen.

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

Bitte beziehen Sie sich auf den Link oben für mehr Code.

Edit: Dies ist eine sehr kleine Demo. Sie haben bereits erwähnt, dass Sie bereits mit sharedService versucht haben. Also bitte dieses Tutorial von angualr2 für weitere Informationen. 

1
micronyks

Shared Service ist eine gute Lösung für dieses Problem. Wenn Sie auch einige Aktivitätsinformationen speichern möchten, können Sie Shared Service zu Ihrer Providerliste für Hauptmodule (app.module) hinzufügen.

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

Dann können Sie es direkt an Ihre Komponenten liefern,

constructor(private sharedService: SharedService)

Mit Shared Service können Sie entweder Funktionen verwenden oder einen Betreff erstellen, um mehrere Orte gleichzeitig zu aktualisieren.

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

In Ihrer Listenkomponente können Sie angeklickte Elementinformationen veröffentlichen.

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

und dann können Sie diese Informationen in Ihrer Detailkomponente abrufen:

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

Offensichtlich können die Daten, die Komponentenfreigaben auflisten, alles sein. Hoffe das hilft.

1
Nick Greaves

Hier ist eine einfache praktische Erklärung: Einfach erklärt hier

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

Komponente, von der aus Sie Observable aufrufen möchten, um eine andere Komponente darüber zu informieren, dass auf die Schaltfläche geklickt wird

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

Komponente, bei der Sie eine Aktion ausführen möchten, wenn Sie auf eine andere Komponente klicken

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

});

}

Mein Verständnis verdeutlicht die Kommunikation von Komponenten, indem ich Folgendes lese: http://musttoknow.com/angular-4-angular-5-communicate-two-components-using-observable-subject/

0

Ich habe Setter-Methoden von einem übergeordneten Element über eine Bindung an eines seiner untergeordneten Elemente übergeben, wobei diese Methode mit den Daten der untergeordneten Komponente aufgerufen wurde. Dies bedeutet, dass die übergeordnete Komponente aktualisiert wird und dann ihre zweite untergeordnete Komponente mit den neuen Daten aktualisieren kann. Es erfordert jedoch das Binden von 'this' oder die Verwendung einer Pfeilfunktion. 

Dies hat den Vorteil, dass die Kinder nicht so miteinander gekoppelt sind, da sie keinen bestimmten gemeinsam genutzten Dienst benötigen.

Ich bin mir nicht ganz sicher, ob dies die beste Praxis ist. Es wäre interessant, die Ansichten anderer zu hören. 

0
user3711899