Des Événements mondiaux Angulaire
n'y a-t-il pas D'équivalent de $scope.emit()
ou $scope.broadcast()
en angle?
je connais la fonctionnalité EventEmitter
, mais pour autant que je comprenne que cela va juste émettre un événement à l'élément HTML parent.
Que faire si je dois communiquer entre fx. fratrie ou entre un composant de la racine du DOM et un élément niché à plusieurs niveaux de profondeur?
10 réponses
il n'y a pas d'équivalent de $scope.emit()
ou $scope.broadcast()
de AngularJS.
EventEmitter à l'intérieur d'un composant est proche, mais comme vous l'avez mentionné, il n'émettent qu'un événement immédiat composant parent.
en angle, il y a d'autres alternatives que je vais essayer d'expliquer ci-dessous.
@Input() bindings permet de connecter le modèle d'application dans un graphe objet dirigé (racine à feuilles). Le comportement par défaut d'un composant la stratégie du détecteur de changement est de propager toutes les modifications à un modèle d'application pour toutes les fixations de n'importe quel composant connecté.
mis à part: il existe deux types de Modèles: voir les modèles et les modèles D'Application. Un modèle d'application est connecté par le biais de fixations @Input (). Un modèle de vue est une propriété juste un composant (non décoré avec @Input ()) qui est liée dans le modèle du composant.
pour répondre à vos questions:
et si je besoin de communiquer entre frère composants?
-
Modèle D'Application Partagée : Les frères et sœurs peuvent communiquer par le biais d'un modèle d'application partagé (tout comme angular 1). Par exemple, lorsqu'un frère ou une sœur fait une modification à un modèle, l'autre frère ou la sœur qui a des fixations sur le même modèle est automatiquement mis à jour.
-
les Événements du Composant : les composants Enfants peut émettre un événement vers le composant parent en utilisant les fixations @Output (). Le composant parent peut gérer l'événement, et manipuler le modèle d'application ou son propre modèle de vue. Les modifications apportées au modèle de demande sont automatiquement propagées à tous les composants qui se lient directement ou indirectement au même modèle.
-
Événements de Service : les Composants peuvent s'abonner à des événements de service. Par exemple, deux composants frères peuvent s'abonner au même événement de service et répondre en modifiant leurs modèles respectifs. En savoir plus sur ce ci-dessous.
Comment puis-je communiquer entre une composante racine et une composante imbriquée à plusieurs niveaux?
- "Shared Application Model : le modèle d'application peut être passé de la composante racine aux sous-composantes profondément imbriquées par le biais de fixations @Input (). Modifications apportées à un modèle à partir de n'importe quel composant seront automatiquement propagées à tous les composants qui partagent le même modèle.
- Service Events : vous pouvez également déplacer L'EventEmitter vers un service partagé, ce qui permet à n'importe quel composant d'injecter le service et de s'abonner à l'événement. De cette façon, un composant racine peut appeler une méthode de service (généralement la mutation du modèle), qui à son tour émet un événement. Plusieurs couches vers le bas, une composante de petit-enfant qui a également injecté le service et souscrit à la un même événement peut s'en occuper. Tout gestionnaire d'événements qui modifie un modèle d'Application partagé se propagera automatiquement à tous les composants qui en dépendent. C'est probablement l'équivalent le plus proche de
$scope.broadcast()
de l'Angulaire 1. La section suivante décrit cette idée plus en détail.
exemple de Service Observable qui utilise des événements de Service pour propager des changements
voici un exemple de service observable qui utilise des événements de service pour propager des changements. Lorsqu'un TodoItem est ajouté, le service émet un événement avisant ses abonnés.
export class TodoItem {
constructor(public name: string, public done: boolean) {
}
}
export class TodoService {
public itemAdded$: EventEmitter<TodoItem>;
private todoList: TodoItem[] = [];
constructor() {
this.itemAdded$ = new EventEmitter();
}
public list(): TodoItem[] {
return this.todoList;
}
public add(item: TodoItem): void {
this.todoList.push(item);
this.itemAdded$.emit(item);
}
}
Voici comment un composant root s'abonnerait à l'événement:
export class RootComponent {
private addedItem: TodoItem;
constructor(todoService: TodoService) {
todoService.itemAdded$.subscribe(item => this.onItemAdded(item));
}
private onItemAdded(item: TodoItem): void {
// do something with added item
this.addedItem = item;
}
}
un composant enfant imbriqué plusieurs niveaux profonds souscrirait à l'événement de la même manière:
export class GrandChildComponent {
private addedItem: TodoItem;
constructor(todoService: TodoService) {
todoService.itemAdded$.subscribe(item => this.onItemAdded(item));
}
private onItemAdded(item: TodoItem): void {
// do something with added item
this.addedItem = item;
}
}
Ici est le composant qui appelle le service pour déclencher l'événement (il peut résider n'importe où dans l'arbre des composants):
@Component({
selector: 'todo-list',
template: `
<ul>
<li *ngFor="#item of model"> {{ item.name }}
</li>
</ul>
<br />
Add Item <input type="text" #txt /> <button (click)="add(txt.value); txt.value='';">Add</button>
`
})
export class TriggeringComponent{
private model: TodoItem[];
constructor(private todoService: TodoService) {
this.model = todoService.list();
}
add(value: string) {
this.todoService.add(new TodoItem(value, false));
}
}
référence: détection de changement angulaire
le code suivant comme exemple de remplacement de $scope.emit() ou $champ d'application.broadcast () en angle 2 en utilisant un service partagé pour gérer les événements.
import {Injectable} from 'angular2/core';
import * as Rx from 'rxjs/Rx';
@Injectable()
export class EventsService {
constructor() {
this.listeners = {};
this.eventsSubject = new Rx.Subject();
this.events = Rx.Observable.from(this.eventsSubject);
this.events.subscribe(
({name, args}) => {
if (this.listeners[name]) {
for (let listener of this.listeners[name]) {
listener(...args);
}
}
});
}
on(name, listener) {
if (!this.listeners[name]) {
this.listeners[name] = [];
}
this.listeners[name].push(listener);
}
broadcast(name, ...args) {
this.eventsSubject.next({
name,
args
});
}
}
exemple d'usage:
diffusion:
function handleHttpError(error) {
this.eventsService.broadcast('http-error', error);
return ( Rx.Observable.throw(error) );
}
auditeur:
import {Inject, Injectable} from "angular2/core";
import {EventsService} from './events.service';
@Injectable()
export class HttpErrorHandler {
constructor(eventsService) {
this.eventsService = eventsService;
}
static get parameters() {
return [new Inject(EventsService)];
}
init() {
this.eventsService.on('http-error', function(error) {
console.group("HttpErrorHandler");
console.log(error.status, "status code detected.");
console.dir(error);
console.groupEnd();
});
}
}
Il peut prendre en charge plusieurs arguments:
this.eventsService.broadcast('something', "Am I a?", "Should be b", "C?");
this.eventsService.on('something', function (a, b, c) {
console.log(a, b, c);
});
j'utilise un service de messagerie qui enveloppe un rxjs Subject
(dactylographié)
exemple de Plunker: Message Service
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/filter'
import 'rxjs/add/operator/map'
interface Message {
type: string;
payload: any;
}
type MessageCallback = (payload: any) => void;
@Injectable()
export class MessageService {
private handler = new Subject<Message>();
broadcast(type: string, payload: any) {
this.handler.next({ type, payload });
}
subscribe(type: string, callback: MessageCallback): Subscription {
return this.handler
.filter(message => message.type === type)
.map(message => message.payload)
.subscribe(callback);
}
}
Les composantes peuvent s'abonner et diffuser des événements (expéditeur):
import { Component, OnDestroy } from '@angular/core'
import { MessageService } from './message.service'
import { Subscription } from 'rxjs/Subscription'
@Component({
selector: 'sender',
template: ...
})
export class SenderComponent implements OnDestroy {
private subscription: Subscription;
private messages = [];
private messageNum = 0;
private name = 'sender'
constructor(private messageService: MessageService) {
this.subscription = messageService.subscribe(this.name, (payload) => {
this.messages.push(payload);
});
}
send() {
let payload = {
text: `Message ${++this.messageNum}`,
respondEvent: this.name
}
this.messageService.broadcast('receiver', payload);
}
clear() {
this.messages = [];
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
(récepteur)
import { Component, OnDestroy } from '@angular/core'
import { MessageService } from './message.service'
import { Subscription } from 'rxjs/Subscription'
@Component({
selector: 'receiver',
template: ...
})
export class ReceiverComponent implements OnDestroy {
private subscription: Subscription;
private messages = [];
constructor(private messageService: MessageService) {
this.subscription = messageService.subscribe('receiver', (payload) => {
this.messages.push(payload);
});
}
send(message: {text: string, respondEvent: string}) {
this.messageService.broadcast(message.respondEvent, message.text);
}
clear() {
this.messages = [];
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
la méthode subscribe
de MessageService
renvoie un objet rxjs Subscription
, qui peut être désabonné de la même façon:
import { Subscription } from 'rxjs/Subscription';
...
export class SomeListener {
subscription: Subscription;
constructor(private messageService: MessageService) {
this.subscription = messageService.subscribe('someMessage', (payload) => {
console.log(payload);
this.subscription.unsubscribe();
});
}
}
Voir Aussi cette réponse: https://stackoverflow.com/a/36782616/1861779
N'utilisez pas EventEmitter pour votre communication de service.
vous devez utiliser l'un des types observables. J'aime personnellement BehaviorSubject.
exemple Simple:
vous pouvez passer l'état initial, ici je passe null
laissez l'objet = new BehaviorSubject(null);
quand vous voulez mettre à jour le sujet
sujet.suivant (myObject)
Observez depuis n'importe quel service ou composant et agissez quand il reçoit de nouvelles mises à jour.
sujet.abonnez-vous(c'.VOTREMÉTHODE);
vous pouvez utiliser EventEmitter ou observables pour créer un service eventbus que vous enregistrez avec DI. Chaque composant qui veut participer demande simplement le service comme paramètre constructeur et émet et/ou s'abonne à des événements.
Voir aussi
j'ai créé un sous-échantillon de pub ici:
http://www.syntaxsuccess.com/viewarticle/pub-sub-in-angular-2.0
l'idée est d'utiliser des sujets RxJs pour connecter un observateur et et Observables comme une solution générique pour l'émission et l'abonnement à des événements personnalisés. Dans mon échantillon j'utilise un objet client à des fins de démonstration
this.pubSubService.Stream.emit(customer);
this.pubSubService.Stream.subscribe(customer => this.processCustomer(customer));
voici aussi une démo en direct: http://www.syntaxsuccess.com/angular-2-samples/#/demo/pub-sub
ma façon préférée de faire est d'utiliser le sujet de comportement ou l'émetteur d'événement (presque le même) dans mon service pour contrôler tous mes sous-composants.
en utilisant angular cli, exécuter ng G S pour créer un nouveau service puis utiliser un BehaviorSubject ou EventEmitter
export Class myService {
#all the stuff that must exist
myString: string[] = [];
contactChange : BehaviorSubject<string[]> = new BehaviorSubject(this.myString);
getContacts(newContacts) {
// get your data from a webservices & when you done simply next the value
this.contactChange.next(newContacts);
}
}
lorsque vous faites que chaque composant utilisant votre service en tant que fournisseur sera au courant du changement. Il suffit de souscrire au résultat comme vous le faites avec eventEmitter ;)
export Class myComp {
#all the stuff that exists like @Component + constructor using (private myService: myService)
this.myService.contactChange.subscribe((contacts) => {
this.contactList += contacts; //run everytime next is called
}
}
nous avons implémenté une directive ngmodelchange observable qui envoie toutes les modifications du modèle par l'intermédiaire d'un émetteur d'événements que vous instanciez dans votre propre composant. Vous devez simplement lier votre émetteur d'événements à la directive.
voir: https://github.com/atomicbits/angular2-modelchangeobservable
en html, liez votre émetteur d'événements (représenté dans cet exemple):
<input [(ngModel)]="country.name"
[modelChangeObservable]="countryChanged"
placeholder="Country"
name="country" id="country"></input>
dans votre manuscrit composant, faire des opérations asynchrones sur le EventEmitter:
import ...
import {ModelChangeObservable} from './model-change-observable.directive'
@Component({
selector: 'my-component',
directives: [ModelChangeObservable],
providers: [],
templateUrl: 'my-component.html'
})
export class MyComponent {
@Input()
country: Country
selectedCountries:Country[]
countries:Country[] = <Country[]>[]
countryChanged:EventEmitter<string> = new EventEmitter<string>()
constructor() {
this.countryChanged
.filter((text:string) => text.length > 2)
.debounceTime(300)
.subscribe((countryName:string) => {
let query = new RegExp(countryName, 'ig')
this.selectedCountries = this.countries.filter((country:Country) => {
return query.test(country.name)
})
})
}
}
C'est ma version:
export interface IEventListenr extends OnDestroy{
ngOnDestroy(): void
}
@Injectable()
export class EventManagerService {
private listeners = {};
private subject = new EventEmitter();
private eventObserver = this.subject.asObservable();
constructor() {
this.eventObserver.subscribe(({name,args})=>{
if(this.listeners[name])
{
for(let listener of this.listeners[name])
{
listener.callback(args);
}
}
})
}
public registerEvent(eventName:string,eventListener:IEventListenr,callback:any)
{
if(!this.listeners[eventName])
this.listeners[eventName] = [];
let eventExist = false;
for(let listener of this.listeners[eventName])
{
if(listener.eventListener.constructor.name==eventListener.constructor.name)
{
eventExist = true;
break;
}
}
if(!eventExist)
{
this.listeners[eventName].push({eventListener,callback});
}
}
public unregisterEvent(eventName:string,eventListener:IEventListenr)
{
if(this.listeners[eventName])
{
for(let i = 0; i<this.listeners[eventName].length;i++)
{
if(this.listeners[eventName][i].eventListener.constructor.name==eventListener.constructor.name)
{
this.listeners[eventName].splice(i, 1);
break;
}
}
}
}
emit(name:string,...args:any[])
{
this.subject.next({name,args});
}
}
utiliser:
export class <YOURCOMPONENT> implements IEventListener{
constructor(private eventManager: EventManagerService) {
this.eventManager.registerEvent('EVENT_NAME',this,(args:any)=>{
....
})
}
ngOnDestroy(): void {
this.eventManager.unregisterEvent('closeModal',this)
}
}
emit:
this.eventManager.emit("EVENT_NAME");
Événements de Service: les Composants peuvent s'abonner à des événements de service. Par exemple, deux composantes sœurs peuvent s'abonner au même événement de service et répondre en modifiant leurs modèles respectifs. En savoir plus sur ce ci-dessous.
Mais assurez-vous de vous désabonner à détruire le composant parent.