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

I'm glad @Syntaxv7 that you found this piece of code helpful. What kind of problem are you having?

@Syntaxv7
Copy link

Syntaxv7 commented Apr 7, 2020 via email

@sagrawal31
Copy link
Author

Apologies for the late reply. Where are you getting undefined for this.channels[topic] ? I mean where have you added this log? Please give me the line number

@johnwargo
Copy link

How exactly do you unsubscribe? I see you have destroy, and that seems to fit the bill, but your code says "Subscription from which you can unsubscribe to release memory resources and to prevent memory leak" which implies there's a missing unsubscribe method.

@vahidvdn
Copy link

vahidvdn commented Jun 1, 2020

Hi. Is there any way without changing the parameters? (Because events is used in a lot of places)

@sagrawal31
Copy link
Author

Hi @johnwargo & @vahidvdn, apologies for the late reply. Did you guys manage to solve your points?

@johnwargo
Copy link

@sagrawal31 No, I did not.

@vahidvdn
Copy link

Me neither.

@sagrawal31
Copy link
Author

Hi @johnwargo, updated the documentation for you which now shows how to unsubscribe once you are done.

Is there any way without changing the parameters?

@vahidvdn yes that is possible. May I know which syntax you are looking for? subscribe, publish or both?

@vahidvdn
Copy link

@sagrawal31 I prefer publish syntax since it's used in a lot of places in my project. I'm wondering why ionic made this deprecated.

@sagrawal31
Copy link
Author

sagrawal31 commented Jun 28, 2020

@vahidvdn you can change the publish method in events class like this-

    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);
    }

And it's done. Now, you can do something like this as before-

this.events.publish('foo:bar', "some value", 11, "some other value");

And you will receive it like this-

events.subscribe('foo:bar', (values) => {
    console.log(values[0] === 'some value');
    console.log(values[1] === 11);
    console.log(values[2] === 'some other value');
});

I hope this helps!

@vahidvdn
Copy link

@sagrawal31 Thanks man

@fromage9747
Copy link

fromage9747 commented Jan 9, 2021

@sagrawal31 I have reached a roadblock with this. It works great with a component/page and a modal. However, it does not work at all between the component to component or page to page.

Is this correct? Or is this meant to work between components as well? I have a list of items, I click on the item to navigate t its detailed page/component. I perform an action like delete or archive which I intended to use events to emit back to the list in order to splice it from the array and remove it from view.

In standard Angular, I would just have the parent component and the child component emitting an event to the parent component in order to pass this communication. I understand that Ionic does work a tad differently in some areas.

@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