N'y a-t-il pas d'équivalent de $scope.emit()
ou $scope.broadcast()
en angulaire?
Je connais la fonctionnalité EventEmitter
, mais si j'ai bien compris, elle ne fera qu'émettre un événement à l'élément HTML parent.
Et si j'ai besoin de communiquer entre fx. frères ou sœurs ou entre un composant dans la racine du DOM et un élément imbriqué plusieurs niveaux de profondeur?
Il n'y a pas d'équivalent à $scope.emit()
ou $scope.broadcast()
de AngularJS . EventEmitter à l'intérieur d'un composant se rapproche, mais comme vous l'avez mentionné, il ne fera qu'émettre un événement au composant parent immédiat.
Dans Angular, il y a d'autres alternatives que j'essaierai d'expliquer ci-dessous.
Les liaisons @Input () permettent au modèle d'application d'être connecté à un graphe d'objet dirigé (racine aux feuilles). Le comportement par défaut de la stratégie de détecteur de changement d'un composant consiste à propager toutes les modifications apportées à un modèle d'application pour toutes les liaisons à partir de tout composant connecté.
De plus, il existe deux types de modèles: Modèles de vue et Modèles d'application. Un modèle d'application est connecté via les liaisons @Input (). Un modèle de vue est juste une propriété de composant (non décorée avec @Input ()) qui est liée dans le modèle du composant.
Pour répondre à vos questions:
Que faire si j'ai besoin de communiquer entre des composants frères?
Modèle d'application partagée: Les frères et sœurs peuvent communiquer via un modèle d'application partagé (comme dans angular 1). Par exemple, lorsqu'un frère modifie un modèle, l'autre frère ayant des liaisons au même modèle est automatiquement mis à jour.
Component Events: les composants enfants peuvent émettre un événement vers le composant parent à l'aide des liaisons @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 d'application sont automatiquement propagées à tous les composants liés 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. Plus à ce sujet ci-dessous.
Comment puis-je communiquer entre un composant racine et un composant imbriqué à plusieurs niveaux?
$scope.broadcast()
dans Angular 1. La section suivante décrit cette idée plus en détail.Exemple de service observable utilisant les événements de service pour propager les modifications
Voici un exemple de service observable qui utilise des événements de service pour propager les modifications. Lorsqu'un TodoItem est ajouté, le service émet un événement pour notifier ses abonnés au composant.
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 racine 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é sur plusieurs niveaux 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;
}
}
Voici le composant qui appelle le service pour déclencher l'événement (il peut résider n'importe où dans l'arborescence 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 en angulaire
Le code suivant est un exemple de remplacement de $ scope.emit () ou $ scope.broadcast () dans Angular 2 en utilisant un service partagé pour gérer des é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);
}
off(name, listener) {
this.listeners[name] = this.listeners[name].filter(x => x != listener);
}
broadcast(name, ...args) {
this.eventsSubject.next({
name,
args
});
}
}
Exemple d'utilisation:
Diffuser:
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 supporter 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 encapsule une rxjs Subject
(TypeScript)
Exemple Plunker: Service de messagerie
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 composants 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();
}
}
(receveur)
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 manière suivante:
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
NE PAS utiliser EventEmitter pour votre communication de service.
Vous devez utiliser l'un des types observables. Personnellement, j'aime BehaviorSubject.
Exemple simple:
Vous pouvez passer l'état initial, ici je passe null
let subject = new BehaviorSubject (null);
Quand vous voulez mettre à jour le sujet
subject.next (myObject)
Observez n'importe quel service ou composant et agissez dès qu'il reçoit de nouvelles mises à jour.
subject.subscribe (this.YOURMETHOD);
Vous pouvez utiliser EventEmitter ou observables pour créer un service Eventbus que vous enregistrez auprès de DI. Chaque composant qui souhaite participer demande simplement le service en tant que paramètre constructeur et émet et/ou s'abonne à des événements.
Voir également
Ma méthode préférée consiste à utiliser un sujet de comportement ou un émetteur d’événements (presque identique) dans mon service pour contrôler l’ensemble de mes sous-composants.
À l'aide d'angular cli, exécutez ng g s pour créer un nouveau service, puis utilisez un BehaviorSubject ou un 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 le ferez, chaque composant utilisant votre service en tant que fournisseur sera informé du changement. Inscrivez-vous simplement 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
}
}
J'ai créé un exemple de pub-sub ici:
http://www.syntaxsuccess.com/viewarticle/pub-sub-in-angular-2.0
L'idée est d'utiliser les sujets RxJs pour connecter un observateur et des observables en tant que solution générique permettant d'émettre et de s'abonner à des événements personnalisés. Dans mon exemple, j'utilise un objet client à des fins de démonstration.
this.pubSubService.Stream.emit(customer);
this.pubSubService.Stream.subscribe(customer => this.processCustomer(customer));
Voici une démonstration en direct: http://www.syntaxsuccess.com/angular-2-samples/#/demo/pub-sub
Ceci 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});
}
}
utilisation:
export class <YOURCOMPONENT> implements IEventListener{
constructor(private eventManager: EventManagerService) {
this.eventManager.registerEvent('EVENT_NAME',this,(args:any)=>{
....
})
}
ngOnDestroy(): void {
this.eventManager.unregisterEvent('closeModal',this)
}
}
émettre:
this.eventManager.emit("EVENT_NAME");
Nous avons implémenté une directive observable ngModelChange qui envoie toutes les modifications de modèle via un émetteur d'événements que vous instanciez dans votre propre composant. Vous devez simplement lier votre émetteur d'événement à la directive.
Voir: https://github.com/atomicbits/angular2-modelchangeobservable
En html, liez l’émetteur de votre événement (countryChanged dans cet exemple):
<input [(ngModel)]="country.name"
[modelChangeObservable]="countryChanged"
placeholder="Country"
name="country" id="country"></input>
Dans votre composant TypeScript, effectuez des opérations asynchrones sur 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)
})
})
}
}
Evé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. Plus à ce sujet ci-dessous.
Mais assurez-vous de vous désabonner de cette option lors de la destruction du composant parent.
Une autre façon de communiquer entre les composants et les services consiste à utiliser le fournisseur d'événements utilisé dans Ionic.
Je préfère les synthax "publier" et "s'abonner", plus lisibles à mon avis.
Copiez simplement ce service dans votre projet Angular.
import {Injectable} from '@angular/core';
export type EventHandler = (...args: any[]) => any;
@Injectable({
providedIn: 'root',
})
export class Events {
private c = new Map<string, EventHandler[]>();
/**
* Subscribe to an event topic. Events that get posted to that topic will trigger the
provided handler.
*
* @param topic the topic to subscribe to
* @param handler the event handler
*/
subscribe(topic: string, ...handlers: EventHandler[]) {
let topics = this.c.get(topic);
if (!topics) {
this.c.set(topic, topics = []);
}
topics.Push(...handlers);
}
/**
* Unsubscribe from the given topic. Your handler will no longer receive events published to this topic.
*
* @param topic the topic to unsubscribe from
* @param handler the event handler
*
* @return true if a handler was removed
*/
unsubscribe(topic: string, handler?: EventHandler): boolean {
if (!handler) {
return this.c.delete(topic);
}
const topics = this.c.get(topic);
if (!topics) {
return false;
}
// We need to find and remove a specific handler
const index = topics.indexOf(handler);
if (index < 0) {
// Wasn't found, wasn't removed
return false;
}
topics.splice(index, 1);
if (topics.length === 0) {
this.c.delete(topic);
}
return true;
}
/**
* Publish an event to the given topic.
*
* @param topic the topic to publish to
* @param eventData the data to send as the event
*/
publish(topic: string, ...args: any[]): any[] | null {
const topics = this.c.get(topic);
if (!topics) {
return null;
}
return topics.map(handler => {
try {
return handler(...args);
} catch (e) {
console.error(e);
return null;
}
});
}
}
Plus d'informations sur Ionic Events: Ionic Events Doc