Skip to content

Instantly share code, notes, and snippets.

@sagrawal31
Last active January 26, 2024 13:22
Show Gist options
  • Save sagrawal31/76c089251008ac746fc8cf3aef4bc261 to your computer and use it in GitHub Desktop.
Save sagrawal31/76c089251008ac746fc8cf3aef4bc261 to your computer and use it in GitHub Desktop.
Alternative to Events which got removed in Ionic 5
import {Injectable} from '@angular/core';
import {Subject, Subscription} from 'rxjs';
/**
* A custom Events service just like Ionic 3 Events https://ionicframework.com/docs/v3/api/util/Events/ which got removed in Ionic 5.
*
* @author Shashank Agrawal
*/
@Injectable({
providedIn: 'root'
})
export class Events {
private channels: { [key: string]: Subject<any>; } = {};
/**
* Subscribe to a topic and provide a single handler/observer.
* @param topic The name of the topic to subscribe to.
* @param observer The observer or callback function to listen when changes are published.
*
* @returns Subscription from which you can unsubscribe to release memory resources and to prevent memory leak.
*/
subscribe(topic: string, observer: (_: any) => void): Subscription {
if (!this.channels[topic]) {
// You can also use ReplaySubject with one concequence
this.channels[topic] = new Subject<any>();
}
return this.channels[topic].subscribe(observer);
}
/**
* Publish some data to the subscribers of the given topic.
* @param topic The name of the topic to emit data to.
* @param data data in any format to pass on.
*/
publish(topic: string, data?: any): void {
const subject = this.channels[topic];
if (!subject) {
// Or you can create a new subject for future subscribers
return;
}
subject.next(data);
}
/**
* When you are sure that you are done with the topic and the subscribers no longer needs to listen to a particular topic, you can
* destroy the observable of the topic using this method.
* @param topic The name of the topic to destroy.
*/
destroy(topic: string): null {
const subject = this.channels[topic];
if (!subject) {
return;
}
subject.complete();
delete this.channels[topic];
}
}

1. Change the imports

Before

import {Events} from 'ionic-angular';

After

import {Events} from '../your/path/to/service/events';

2. Changes in the subscribe method

Before

events.subscribe('user:created', (user, time) => {
    console.log('Welcome', user, 'at', time);
});

After

this.events.subscribe('user:created', (data: any) => {
    console.log('Welcome', data.user, 'at', data.time);
});

3. Changes in the publish method

Before

this.events.publish('user:created', someUserInstance, Date.now());

After

this.events.publish('foo:user:logged-out', {
    user: someUserInstance,
    time: new Date()
});

4. To Unsubscribe

const subscription = this.events.subscribe('user:foo:created', (data: any) => {
    // your logic
});

Once you are done, you can do this-

subscription.unsubscribe();
@sagrawal31
Copy link
Author

Hi @fromage9747. This should also work from component to component. Will you be able to provide me with a working plunkr/project to reproduce this issue?

@fromage9747
Copy link

Hi @sagrawal31. I eventually discovered it wasn't working in any of my instances where I thought it was working. I think it might have been because the component is getting trashed when navigating to another component? Can't be sure, but the subscription must be dying somewhere.

I may have needed to set up subscriptions elsewhere in a service to get it to work like how I would do with normal angular. My app is a bit complex to copy and paste, it would take a bit of time to create a plunkr for it but basically, all I was doing was creating a subscription to the events in Component A which had been initiated and activated. Then navigated to component B, emitting the event. But the event was not picked up by component A.

I have made a workaround at the moment that is doing what I need it to do. If I get to a point where I desperately need the events as there is no other way to achieve what I want and I run into an issue I will definitely spend the time making a plunkr example.

Thank you for getting back to me though!

@sagrawal31
Copy link
Author

Hi @fromage9747 apologies, you had to work around the problem. Can you confirm two things-

  1. You have used @Injectable({ providedIn: 'root' }) at the top of the service.
  2. Not injected the service explicitly in any module (in providers)?

Apart from this, I will be able to help you out if you can provide any working repo whenever next you use this. Because this thing is working fine on all my complex & simply projects.

Thanks for reaching out again!

@fromage9747
Copy link

fromage9747 commented Jan 20, 2021

Hi @sagrawal31,

It is indeed provided in root. I haven't made any changes to your code:

image

  1. So I am not to inject it into app.module.ts or shared.module.ts for example? Or must it only be in app.module.ts?

@sagrawal31
Copy link
Author

Thanks for the update. I actually see the issue. Your code must be publishing to the topic first and letter subscribing while the original publisher might have removed by that time.

If you look closely, I have already left two comments in the code to either use ReplySubject (for scenario 1) or Or you can create a new subject for future subscribers (for another scenario).

Now, again based on the actual clarity of your requirement, you either have to change the code accordingly. Let me know if you need to fix and use it.

@fromage9747
Copy link

Okay, so to confirm, I need to publish to the topic first before the subscription? I would think that the subscription needs to take place first in order to listen for any publish events?

@sagrawal31
Copy link
Author

That's the catch, you don't have to worry about what to do first if you address two comments in my code-

  1. Instead of new Subject() use new ReplaySubject(1) (note- for future subscribers, this will always give you the last value).
  2. If your code is publishing first before any subscriber, change the method of publish to-
    publish(topic: string, data?: any): void {
        if (!this.channels[topic]) {
            this.channels[topic] = new ReplaySubject<any>();

        }

        this.channels[topic].next(data);
    }

Let me know if this helps!

@fromage9747
Copy link

Thanks! I will give it a bash!

@grantdevon
Copy link

Hi, Does someone perhaps know where I can find documentation on Unit testing this?

@johnwargo
Copy link

@grantdevon it's a Gist, a code sample; just a snippet of code, not a repo with tests, etc.
If you want unit tests, you're going to have to write them. :-)

@sagrawal31
Copy link
Author

Thanks @johnwargo for clarifying it for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment