Communication Des Composantes Angulaires 2

j'ai un ListComponent. Lorsqu'un élément est cliqué dans ListComponent, les détails de cet élément doivent être affichés dans DetailComponent. Les deux sont sur l'écran en même temps, donc il n'y a pas de routage impliqués.

Comment dire à DetailComponent quel élément de ListComponent a été cliqué?

j'ai envisagé d'émettre un événement jusqu'au parent (AppComponent), et j'ai demandé au parent de définir le selectedItem.id sur DetailComponent avec une entrée@. Ou je pourrais utilisez un service partagé avec des abonnements observables.


modifier: définir l'élément sélectionné via event + @Input ne déclenche pas le DetailComponent, cependant, dans le cas où je devais avoir besoin d'exécuter du code supplémentaire. Donc je ne suis pas sûr que ce soit une solution acceptable.


mais ces deux méthodes semblent beaucoup plus complexes que la 1 façon angulaire de faire les choses qui était soit à travers $rootScope.$diffusion$, ou de portée.$parent.$radiodiffusion.

avec tout ce qui est dans L'angle 2 étant un composant, je suis surpris qu'il n'y ait pas plus d'information là-bas sur la communication des composants.

y a-t-il une autre façon plus directe d'y parvenir?

85
demandé sur Sangwin Gawande 2016-03-09 09:24:21

9 réponses

mise à Jour de rc.4: En essayant d'obtenir des données transmises entre les composants de la fratrie dans l'angle 2, la manière la plus simple en ce moment (angulaire.rc.4) est de profiter de l'injection de dépendance hiérarchique d'angular2 et de créer un service partagé.

voici le service:

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

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

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

maintenant, voici le composant 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{

} 

et ses deux enfants

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

enfant 2 (frère, sœur)

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

maintenant: choses à noter en utilisant cette méthode.

  1. n'inclure que le fournisseur de services pour le service partagé dans la composante des parents et non les enfants.
  2. vous devez toujours inclure les constructeurs et importer le service dans les enfants""
  3. cette réponse a été initialement répondu pour un angle précoce 2 version bêta. Tout ce qui a changé cependant sont les déclarations d'importation, de sorte que tout ce que vous devez mettre à jour si vous avez utilisé la version originale par hasard.
59
répondu Alex J 2017-08-18 16:24:08

dans le cas de 2 composants différents (composants non emboîtés, parent\enfant\petit-enfant), je vous suggère ceci:

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

Source: les Parents et les enfants à communiquer par l'intermédiaire d'un service

23
répondu Dudi 2016-11-24 23:13:35

il y a une discussion à ce sujet ici.

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

la réponse D'Alex J est bonne, mais elle ne fonctionne plus avec le courant angulaire 4 à partir de juillet 2017.

et ce lien plongeur démontrerait comment communiquer entre frères et sœurs en utilisant le service partagé et observable.

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

9
répondu João Silva 2017-07-21 22:53:12

une façon de le faire est d'utiliser un service partagé .

cependant je trouve le suivant solution beaucoup plus simple, il permet de partager des données entre 2 frères et sœurs.(Je l'ai testé seulement sur Angular 5 )

En vous composant parent modèle:

<!-- 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.composant.ts

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

export class AppSibling2Component {
   ...
   @Input() data: AppSibling1Component;
   ...
}
7
répondu Caner 2018-01-05 13:29:45

une directive peut avoir du sens dans certaines situations de "connecter" des composants. En fait, les choses étant connectés n'ont même pas besoin d'être des composants complets, et parfois c'est plus léger et en fait plus simple si ce n'est pas le cas.

par exemple, j'ai un composant Youtube Player (enveloppant L'API Youtube) et je voulais des boutons de contrôle pour cela. La seule raison pour laquelle les boutons ne font pas partie de mon composant principal est qu'ils sont situés ailleurs dans le DOM.

dans ce cas, c'est vraiment juste un composant 'extension' qui ne sera jamais utilisé avec le composant 'parent'. Je dis "parent", mais dans le DOM c'est un frère - alors appelez-le comme vous voulez.

comme je l'ai dit, il n'a même pas besoin d'être un composant complet, dans mon cas c'est juste un <button> (mais ça pourrait être un composant).

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

en HTML pour ProductPage.component , où youtube-player est évidemment mon composant qui enveloppe Le Youtube API.

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

... lots more DOM ...

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

la directive met tout en place pour moi, et je n'ai pas à déclarer l'événement (clic) dans le HTML.

pour que la directive se connecte bien au lecteur vidéo sans avoir à impliquer ProductPage comme médiateur.

c'est la première fois que je fais réellement cela, donc pas encore sûr à quel point il pourrait être évolutif pour des situations beaucoup plus complexes. Pour cela cependant je suis heureux et il laisse mon HTML simple et responsabilités de tout ce qui est distinct.

3
répondu Simon_Weaver 2018-06-10 02:26:12

vous devez établir la relation parent-enfant entre vos composantes. Le problème est que vous pouvez simplement injecter les composants enfant dans le constructeur du composant parent et le stocker dans une variable locale. Vous devriez plutôt déclarer les composantes enfant dans votre composante parent en utilisant le déclarant de propriété @ViewChild . Voici à quoi devrait ressembler votre composant parent:

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

attention, le composant enfant ne sera pas disponible dans le constructeur du composant parent, juste après le crochet cycle de vie ngAfterViewInit est appelé. Pour attraper ce crochet simple mettre en œuvre l'interface AfterViewInit dans votre classe de parent le même comme pour OnInit .

mais, il ya d'autres déclarants de biens comme expliqué dans cette note de blog: http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders /

2
répondu Vereb 2016-08-07 06:49:42

le Comportement des sujets. j'ai écrit un blog à ce sujet.

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

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

dans cet exemple je déclare un comportement noïd sujet de numéro de type. C'est aussi observable. Et si "something happend" change avec la nouvelle fonction () {}.

Alors, dans la fratrie, on va appeler la fonction, pour faire le changement, et l'autre seront touchés par ce changement, ou vice-versa.

par exemple, j'obtiens l'id de L'URL et je mets à jour le noid du sujet behavior.

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

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

et de l'autre côté, je peux demander si cet ID est "ce que je veux" et faire un choix après cela, dans mon cas si je veux delte une tâche, et cette tâche est l'url courante, il doit me rediriger vers la maison: "15198090920"

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
répondu ValRob 2018-01-22 12:26:23

"ce n'est pas exactement ce que vous voulez, mais vous aidera certainement "

je suis surpris qu'il n'y ait pas plus d'information là-bas sur la communication des composants <=> considérez ce tutoriel par angualr2

pour la communication entre frères et sœurs, je suggère d'utiliser sharedService . Y sont également d'autres options disponibles.

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

Veuillez consulter le lien au dessus pour plus de code.

Edit: C'est une très petite démo. Vous avez déjà mentionner que vous avez déjà essayé avec sharedService . Alors s'il vous plaît considérez ce tutoriel par angualr2 pour plus d'information.

1
répondu micronyks 2016-03-09 06:36:27

j'ai transmis les méthodes setter du parent à l'un de ses enfants par le biais d'une liaison, appelant cette méthode avec les données de la composante enfant, ce qui signifie que la composante parent est mise à jour et peut ensuite mettre à jour sa deuxième composante enfant avec les nouvelles données. Il nécessite de lier "ceci" ou d'utiliser une fonction de flèche cependant.

cela présente l'avantage que les enfants ne sont pas tellement jumelés les uns aux autres qu'ils n'ont pas besoin d'un service partagé spécifique.

je ne suis pas entièrement sûr que c'est la meilleure pratique serait intéressant d'entendre d'autres points de vue sur ce.

0
répondu user3711899 2017-03-31 20:45:29