Ionic Events replace with Angular Observables

Viewed 8441

I understand Ionic events will be deprecated in next version. Currently I use events to executes functions in my parent page from sub components. Here is an example:

In my main page it subscribes to the event to refresh:

constructor(){
  this.eventFormRefresh = (obj) => {
    this.fetch(obj.isReset);
  };
  this.events.subscribe('form:refresh', this.eventFormRefresh);
}

ngOnDestroy(): void {
  this.events.unsubscribe('form:refresh', this.eventFormRefresh);
}

In a subcomponent I activate the refresh by publishing the 'form:refresh' event as so:

this.events.publish('form:refresh');

How would I do the above using angular observables?

6 Answers

you can use rxjs Subject for that so first create a common service eg.

@Injectable()
export class EventService{
  private formRefreshAnnouncedSource = new Subject();
  formRefreshSource$ = this.formRefreshAnnouncedSource.asObservable();

  publishFormRefresh(){
    this.formRefreshAnnouncedSource.next()
  }

}

then publish like

this.eventService.publishFormRefresh();

and subscribe in some componenet

this.subscription = this.eventService.formRefreshSource$.subscribe(data => {
  //do something here
});

and cancel subscription on ngOnDestroy

this.subscription.unsubscribe()

USER EVENTS AS YOU ARE USED TO IN IONIC 3. You just have to import this service/provider in a page you want to use it. Reference https://git.furworks.de/opensourcemirror/Ionic/commit/e5f2a18230f3ca3017f0302fb57ef275d0f63a8b

import { Injectable } from '@angular/core';

export type EventHandler = (...args: any[]) => any;
@Injectable({
  providedIn: 'root',
})
export class Events {
  private c = new Map<string, EventHandler[]>();

  constructor() {
  //   console.warn(`[DEPRECATION][Events]: The Events provider is deprecated, and it will be removed in the next major release.
  // - Use "Observables" for a similar pub/sub architecture: https://angular.io/guide/observables
  // - Use "Redux" for advanced state management: https://ngrx.io`);
  }
  /**
   * 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: any, ...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;
      }
    });
  }
}

// make service using : ionic g service events

    import { Injectable } from '@angular/core';
    import { Subject } from 'rxjs';

    @Injectable({
    providedIn: 'root'
     })

     export class EventsService {

     private refreshData=new Subject();

     constructor() {};

     refreshData= this.refreshDatasObservable();

     refresh(message){
     this.refreshDatanext(message)
     }

   }

in app.module.ts

      import {EventsService} from './service/vents.service'; // add it to   provider 

       providers:[....
               EventsService
                ....
                ]

in your desired page subscribe and call like this

      import {EventsService} from './service/vents.service'; //import service                                   
          constructor(private eventsService:EventsService){

           //for refreshing or publishing
           this.eventsService.refresh('i am data');

          //for subscribing to change
          this.eventsService.refreshChatData$.subscribe(data=>{
            //do something
            })

           }

You can create a service with a BehaviorSubject and you can pass data and subscribe to it wherever you want. In your case pass in the child and subscribe in the parent component.

For anyone else finding this in the same setup I was in, here's another possible route. We're looking at going from Ionic V3 -> V4 and know we have some headaches in store. One being events deprecated. I decided to look how the ionic-angular implementation of events was set up, and realized I kind of liked it. I made a service mimicking their implementation, hoping that replacing the ionic-angular Events later will be easier.

@Injectable()
export class Events {
    private _channels: { [key: string]: ((...args: any[]) => void)[] } = {};
    public subscribe(topic: string, handler: (...args: any[]) => void) {
        if (!this._channels[topic]) {
            this._channels[topic] = [];
        }
        this._channels[topic].push(handler);
    }
    public publish(topic: string, ...args: any[]) {
        if (this._channels[topic] && this._channels[topic].length) {
            this._channels[topic].forEach(handler => handler(...args));
        }
    }
    public unsubscribe(topic: string, handler: ((...args: any[]) => void) = null) {
        if (!handler) {
            delete this._channels[topic];
        }
        const index = this._channels[topic].indexOf(handler);
        if (index > 0) {
            this._channels[topic].splice(index, 1);
        }
        if (!this._channels[topic].length) {
            delete this._channels[topic];
        }
    }
}

I know this doesn't use Observables, but wanted to offer a different solution for anyone else hoping for a drop-in fix for ionic-angular/events I simplified / removed some of their code, if there's any concerns let me know.

This can be dealt with by using an Events service as pointed out in this medium post Dealing with breaking change in Ionic 5

The author illustrates how one can refactor code with minimal ease and maintain the flow.

Related