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?
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.
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
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.
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;
...
}
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.
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/
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']);
}
})
})
}
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.
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.
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/
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.