web-dev-qa-db-fra.com

Communication angulaire 2 composante fratrie

J'ai un composant de liste. Lorsqu'un élément est cliqué dans ListComponent, les détails de cet élément doivent être affichés dans DetailComponent. Les deux sont à l'écran en même temps, donc aucun routage n'est impliqué.

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

J'ai envisagé d'émettre un événement jusqu'au parent (AppComponent) et je lui ai demandé de définir le fichier selectedItem.id sur DetailComponent avec un @Input. Ou je pourrais utiliser un service partagé avec des abonnements observables.


EDIT: La définition de l'élément sélectionné via event + @Input ne déclenche pas le DetailComponent, cependant, dans le cas où je devrais 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 bien plus complexes que la méthode angulaire 1 consistant à utiliser $ rootScope. $ Broadcast ou $ scope. $ Parent. $ Broadcast.

Comme tout dans Angular 2 est un composant, je suis surpris qu'il n'y ait pas plus d'informations disponibles sur la communication des composants.

Existe-t-il un autre moyen plus simple d’y parvenir? 

93
dennis.sheppard

Mise à jour vers rc.4: Lorsque vous tentez de transférer des données entre des composants frères dans angular 2, la méthode la plus simple actuellement (angular.rc.4) consiste à tirer parti de l’injection de dépendance hiérarchique de angular2 et à 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 (c'est un frère)

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

NOW: Points à prendre en compte lors de l'utilisation de cette méthode.

  1. Incluez uniquement le fournisseur de services pour le service partagé dans le composant PARENT et PAS les enfants.
  2. Vous devez toujours inclure les constructeurs et importer le service dans les enfants
  3. Cette réponse a été initialement répondue pour une version 2 bêta angulaire précoce. Cependant, tout ce qui a changé est l’instruction d’importation. C’est donc tout ce dont vous avez besoin de mettre à jour si vous avez utilisé la version originale par hasard.
61
Alex J

Dans le cas de 2 composants différents (composants non imbriqué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: Le parent et les enfants communiquent via un service

24
Dudi

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 Angular 4 à compter de juillet 2017.

Et ce lien plunker montrerait comment communiquer entre frères utilisant un service partagé et observable.

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

9
João Silva

Une façon de faire consiste à utiliser un service partagé

Cependant, je trouve la solution Suivante beaucoup plus simple, elle permet de partager des données entre deux frères et soeurs (je l’ai testée uniquement sur Angular 5). 

Dans votre modèle de composant parent:

<!-- 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

Une directive peut avoir du sens dans certaines situations pour «connecter» des composants. En fait, les éléments connectés n’ont même pas besoin d’être des composants complets. Parfois, ils sont plus légers et plus simples s’ils ne le sont pas.

Par exemple, j'ai un composant Youtube Player (enveloppant l'API Youtube) et je voulais des boutons de contrôleur 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, il ne s'agit en réalité que d'un composant 'extension' qui ne sera jamais utilisable qu'avec le composant 'parent'. Je dis «parent», mais dans le DOM, c'est un frère - appelez-le comme vous voulez.

Comme je l'ai dit, il n'est même pas nécessaire que ce soit un composant complet. Dans mon cas, il s'agit simplement d'un <button> (mais ce 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
   }
}

Dans le code HTML pour ProductPage.component, où youtube-player est évidemment mon composant qui encapsule l'API Youtube.

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

... lots more DOM ...

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

La directive raccorde tout pour moi et je n'ai pas à déclarer l'événement (clic) dans le code HTML.

La directive peut donc bien se connecter au lecteur vidéo sans avoir à impliquer ProductPage en tant que médiateur.

C’est la première fois que je fais cela, alors je ne sais pas encore dans quelle mesure cela peut être adapté à des situations beaucoup plus complexes. Pour cela, je suis heureux et laisse mon code HTML simple et des responsabilités distinctes.

3
Simon_Weaver

Vous devez configurer la relation parent-enfant entre vos composants. Le problème est que vous pouvez simplement injecter les composants enfants dans le constructeur du composant parent et les stocker dans une variable locale . À la place, vous devez déclarer les composants enfants dans votre composant parent à l'aide du déclarateur de la propriété @ViewChild ..__ Voici comment votre composant parent devrait ressembler à ceci:

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 l'appel du hook ngAfterViewInit lifecycle. Pour attraper ce crochet simple, implémentez l'interface AfterViewInit dans votre classe parente comme vous le feriez avec OnInit.

Cependant, il existe d'autres déclarateurs de propriété, comme expliqué dans cette note de blog: http://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/

2
Vereb

Sujets de comportement. 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 de type noid de type numéro. Aussi c'est un observable. Et si "quelque chose est arrivé", cela changera avec la fonction new () {}. 

Ainsi, dans les composants de la fratrie, l’un appellera la fonction pour effectuer le changement et l’autre sera affecté par ce changement, ou inversement. 

Par exemple, je récupère l'ID de l'URL et met à jour le noid du sujet du comportement. 

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 "tout ce que je veux" et faire un choix par la suite, dans mon cas, si je veux supprimer une tâche, et que cette tâche est l’URL actuelle, elle doit me rediriger à la maison: 

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

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

Je suis surpris qu'il n'y ait pas plus d'informations sur la communication entre composants} [ <=> considérons ce tutoriel par angualr2

Pour la communication entre composants frères et soeurs, je suggérerais d'utiliser sharedService. Il existe é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 vous référer au lien en haut pour plus de code.

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

1
micronyks

Le service partagé est une bonne solution à ce problème. Si vous souhaitez également stocker des informations sur vos activités, vous pouvez ajouter Service partagé à la liste de vos fournisseurs de modules principaux (app.module).

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

Ensuite, vous pouvez directement le fournir à vos composants,

constructor(private sharedService: SharedService)

Avec le service partagé, vous pouvez utiliser des fonctions ou créer un sujet pour mettre à jour plusieurs espaces à la fois.

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

Dans votre composant de liste, vous pouvez publier les informations sur l'élément cliqué,

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

et ensuite vous pouvez récupérer ces informations sur votre composant de détail:

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

De toute évidence, les données répertoriées peuvent être n'importe quoi. J'espère que cela t'aides.

1
Nick Greaves

Voici une explication pratique simple: Simplement expliqué ici

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

Composant d'où vous voulez appeler observable pour informer un autre composant que le bouton est cliqué

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

Composant pour lequel vous souhaitez effectuer une action sur un bouton cliqué sur un autre composant

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

});

}

Ma compréhension est claire sur la communication des composants en lisant ceci: http://musttoknow.com/angular-4-angular-5-communicate-two-components-using-observable-subject/

0
Prashant M Bhavsar

J'ai transmis des méthodes de définition du parent à l'un de ses enfants via une liaison, en appelant cette méthode avec les données du composant enfant, ce qui signifie que le composant parent est mis à jour et qu'il peut ensuite mettre à jour son second composant enfant avec les nouvelles données. Cela nécessite toutefois de lier 'this' ou d'utiliser une fonction de flèche. 

Cela présente l'avantage que les enfants ne sont pas tellement couplés l'un à l'autre car ils n'ont pas besoin d'un service partagé spécifique.

Je ne suis pas tout à fait sûr que ce soit la meilleure pratique, il serait intéressant d'entendre d'autres points de vue à ce sujet. 

0
user3711899