navigationend - router events angular 6
Eventos globales en angular (11)
El siguiente código como ejemplo de un reemplazo para $ scope.emit () o $ scope.broadcast () en Angular 2 usando un servicio compartido para manejar eventos.
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
});
}
}
Ejemplo de uso:
Emisión:
function handleHttpError(error) {
this.eventsService.broadcast(''http-error'', error);
return ( Rx.Observable.throw(error) );
}
Oyente:
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();
});
}
}
Puede soportar múltiples argumentos:
this.eventsService.broadcast(''something'', "Am I a?", "Should be b", "C?");
this.eventsService.on(''something'', function (a, b, c) {
console.log(a, b, c);
});
¿No hay equivalente a
$scope.emit()
o
$scope.broadcast()
en Angular?
Conozco la funcionalidad
EventEmitter
, pero por lo que entiendo, solo emitirá un evento para el elemento HTML principal.
¿Qué pasa si necesito comunicarme entre FX? hermanos o entre un componente en la raíz del DOM y un elemento anidado en varios niveles de profundidad?
Esta es mi versión
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});
}
}
utilizar:
export class <YOURCOMPONENT> implements IEventListener{
constructor(private eventManager: EventManagerService) {
this.eventManager.registerEvent(''EVENT_NAME'',this,(args:any)=>{
....
})
}
ngOnDestroy(): void {
this.eventManager.unregisterEvent(''closeModal'',this)
}
}
emitir:
this.eventManager.emit("EVENT_NAME");
Estoy usando un servicio de mensajes que envuelve un
Subject
rxjs (TypeScript)
Ejemplo de Plunker: servicio de mensajes
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);
}
}
Los componentes pueden suscribirse y transmitir eventos (remitente):
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();
}
}
(receptor)
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();
}
}
El método de
subscribe
de
MessageService
devuelve un objeto de
Subscription
rxjs, que se puede cancelar de la siguiente manera:
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();
});
}
}
También vea esta respuesta: https://.com/a/36782616/1861779
Eventos de servicio: los componentes pueden suscribirse a eventos de servicio. Por ejemplo, dos componentes hermanos pueden suscribirse al mismo evento de servicio y responder modificando sus respectivos modelos. Más sobre esto a continuación.
Pero asegúrese de cancelar la suscripción a eso al destruir el componente principal.
He creado una muestra de pub-sub aquí:
http://www.syntaxsuccess.com/viewarticle/pub-sub-in-angular-2.0
La idea es utilizar los sujetos RxJs para conectar un observador y observables como una solución genérica para emitir y suscribirse a eventos personalizados. En mi muestra utilizo un objeto de cliente con fines de demostración
this.pubSubService.Stream.emit(customer);
this.pubSubService.Stream.subscribe(customer => this.processCustomer(customer));
Aquí también hay una demostración en vivo: http://www.syntaxsuccess.com/angular-2-samples/#/demo/pub-sub
Implementamos una directiva observable ngModelChange que envía todos los cambios del modelo a través de un emisor de eventos que usted instancia en su propio componente. Simplemente tiene que vincular su emisor de eventos a la directiva.
Ver: https://github.com/atomicbits/angular2-modelchangeobservable
En html, vincule su emisor de eventos (countryChanged en este ejemplo):
<input [(ngModel)]="country.name"
[modelChangeObservable]="countryChanged"
placeholder="Country"
name="country" id="country"></input>
En su componente de mecanografiado, realice algunas operaciones asíncronas en 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)
})
})
}
}
Mi forma favorita de hacerlo es usar el sujeto de comportamiento o el emisor de eventos (casi el mismo) en mi servicio para controlar todo mi subcomponente.
Usando cli angular, ejecute ng gs para crear un nuevo servicio y luego use un BehaviorSubject o 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);
}
}
Cuando lo haga, todos los componentes que utilicen su servicio como proveedor serán conscientes del cambio. Simplemente suscríbase al resultado como lo hace con 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
}
}
No hay equivalente a
$scope.emit()
o
$scope.broadcast()
de AngularJS.
EventEmitter dentro de un componente se acerca, pero como mencionó, solo emitirá un evento al componente primario inmediato.
En Angular, hay otras alternativas que intentaré explicar a continuación.
Los enlaces @Input () permiten que el modelo de aplicación se conecte en un gráfico de objeto dirigido (raíz a hojas). El comportamiento predeterminado de la estrategia del detector de cambios de un componente es propagar todos los cambios a un modelo de aplicación para todos los enlaces desde cualquier componente conectado.
Aparte: hay dos tipos de modelos: Ver modelos y Modelos de aplicación. Un modelo de aplicación está conectado a través de enlaces @Input (). Un modelo de vista es solo una propiedad de componente (no decorada con @Input ()) que está vinculada en la plantilla del componente.
Para responder tu pregunta:
¿Qué sucede si necesito comunicarme entre componentes hermanos?
-
Modelo de aplicación compartida : los hermanos pueden comunicarse a través de un modelo de aplicación compartida (al igual que angular 1). Por ejemplo, cuando un hermano hace un cambio en un modelo, el otro hermano que tiene enlaces al mismo modelo se actualiza automáticamente.
-
Eventos de componentes : los componentes secundarios pueden emitir un evento al componente principal mediante enlaces @Output (). El componente principal puede manejar el evento y manipular el modelo de aplicación o su propio modelo de vista. Los cambios en el modelo de aplicación se propagan automáticamente a todos los componentes que se unen directa o indirectamente al mismo modelo.
-
Eventos de servicio : los componentes pueden suscribirse a eventos de servicio. Por ejemplo, dos componentes hermanos pueden suscribirse al mismo evento de servicio y responder modificando sus respectivos modelos. Más sobre esto a continuación.
¿Cómo puedo comunicarme entre un componente raíz y un componente anidado a varios niveles de profundidad?
- Modelo de aplicación compartida : el modelo de aplicación se puede pasar del componente raíz a subcomponentes profundamente anidados mediante enlaces @Input (). Los cambios en un modelo desde cualquier componente se propagarán automáticamente a todos los componentes que compartan el mismo modelo.
-
Eventos de servicio
: también puede mover EventEmitter a un servicio compartido, que permite que cualquier componente inyecte el servicio y se suscriba al evento.
De esa manera, un componente raíz puede llamar a un método de servicio (generalmente mutando el modelo), que a su vez emite un evento.
Varias capas hacia abajo, un componente de nieto que también ha inyectado el servicio y suscrito al mismo evento, puede manejarlo.
Cualquier controlador de eventos que cambie un modelo de aplicación compartido se propagará automáticamente a todos los componentes que dependen de él.
Este es probablemente el equivalente más cercano a
$scope.broadcast()
de Angular 1. La siguiente sección describe esta idea con más detalle.
Ejemplo de un servicio observable que usa eventos de servicio para propagar cambios
Aquí hay un ejemplo de un servicio observable que usa eventos de servicio para propagar cambios. Cuando se agrega TodoItem, el servicio emite un evento que notifica a sus suscriptores componentes.
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);
}
}
Así es como un componente raíz se suscribiría al evento:
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 componente hijo anidado en varios niveles profundos se suscribiría al evento de la misma manera:
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;
}
}
Aquí está el componente que llama al servicio para activar el evento (puede residir en cualquier parte del árbol de componentes):
@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));
}
}
Referencia: Detección de cambio en angular
Otra forma de comunicarse entre componentes y servicios es con el proveedor de eventos utilizado en Ionic.
Prefiero el "publicar" y "suscribir" Synthax, más legible en mi opinión.
Simplemente copie este servicio a su proyecto 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;
}
});
}
}
Más información sobre Ionic Events: Ionic Events Doc
Puede usar
EventEmitter u
observables para crear un servicio de bus de eventos que registre con DI.
Cada componente que quiere participar solo solicita el servicio como parámetro constructor y emite y / o se suscribe a eventos.
Ver también
NO use EventEmitter para la comunicación de su servicio.
Debe usar uno de los tipos Observables. Personalmente me gusta BehaviorSubject.
Ejemplo simple:
Puedes pasar el estado inicial, aquí paso nulo
let subject = new BehaviorSubject (nulo);
Cuando quieres actualizar el tema
subject.next (myObject)
Observe desde cualquier servicio o componente y actúe cuando reciba nuevas actualizaciones.
sujeto.suscribirse (este.YOURMETHOD);