This is not about an issue. It is to help any users struggling with events in their components, specifically
- how to unsubscribe successfully
- how to access the component's variables within an event handler
If you have a component that subscribes to abp events, you must unsubscribe when the component is destroyed. Otherwise, when the component subscribes again in its next instantiation, the handler will be called twice. However, getting this right is not easy. There have been some threads about this matter, but I haven’t seen any solutions that give an example of putting it all together correctly.
There are two main problems.
PROBLEM 1: abp.event.on and abp.event.off need to refer to the same instance of the handler. In the abp documentation, there is only this example of an event handler:
abp.event.on('itemAddedToBasket', function (item) {
console.log(item.name + ' was added to basket!');
});
Unfortunately: If you use this format for abp.event.on, there is no way to ever unsubscribe using abp.event.off. The reason is that the handler is embedded as a lambda and has no reference that can be used later for unsubscribing. The documentation does mention this problem:
You can use the abp.event.off method to unregister from an event. Note that the same function should be provided so it can be unregistered. So for the example above, you must set the callback function to a variable, then use both the on and off methods.
But there is no example given. And there is no mention of the fact that, if you do define a named method for the event handler, this method is called within the scope of the EVENT instance, not within the scope of the COMPONENT instance, no matter where the method is defined. Which brings us to:
PROBLEM 2: It is quite likely that, if you subscribe to an event within a component, you will want to interact with the component in your event handler. Maybe you’ll need to set a member variable depending on the event data. But if you put a breakpoint in your event handler, you will see that “this” refers to the event class (with the methods “on” and “off”) and not to the component. Any attempt to reference, say, “this.myVariable” will fail. So the first thing the event handler must do is change the scope to that of the component. The following example shows how to put it all together.
interface AbpSubscription {
name: string;
handler: (...args: any[]) => void;
}
@Component({
selector: 'my-component',
templateUrl: './my-component.component.html'
})
export class MyComponent extends AppComponentBase implements OnInit, OnDestroy {
subscriptions: Array<AbpSubscription> = [];
eventContents: string;
constructor(
injector: Injector,
public _zone: NgZone
) {
super(injector);
}
ngOnInit() {
// register all subscriptions
this.registerSubscriptions();
}
ngOnDestroy() {
// unregister all registered subscriptions
this.unRegisterSubscriptions();
}
// register all subscriptions
registerSubscriptions() {
const self = this;
// register handler for 'EventA' which emits one argument
this.registerSubscription('EventA', eventAHandler);
// this is the registered handler
function eventAHandler(arg1: string)
{
// switch to the component scope
self._zone.run(() => {
// call the component's 'real' event handler
self.onEventA(arg1);
});
}
// register handler for 'EventB' which emits two arguments
this.registerSubscription('EventB', eventBHandler);
// this is the registered handler
function eventBHandler(arg1: string, arg2: string)
{
// switch to the component scope
self._zone.run(() => {
// call the component's 'real' event handler
self.onEventB(arg1, arg2);
});
}
}
// register a single subscription
registerSubscription(name: string, handler: (...args: any[]) => void) {
// activate the subscription
abp.event.on(name, handler);
// remember this subscription for the unRegister method
this.subscriptions.push({
name: name,
handler: handler
});
}
// unregister all registered subscriptions
unRegisterSubscriptions() {
// call abp.event.off on each subscription
this.subscriptions.forEach(s => abp.event.off(s.name, s.handler));
// clear the list of subscriptions
this.subscriptions = [];
}
// the component's event handler for 'EventA'
onEventA(arg1: string) {
// we have access to the component's variables
this.eventContents = arg1;
}
// the component's event handler for 'EventB'
onEventB(arg1: string, arg2: string) {
// we have access to the component's variables
this.eventContents = arg1 + arg2;
}
}
1 Answer(s)
-
0
Hi,
Thank you for your detailed explaination. Actually, abp events are not designed to be used in Angular apps at first but we will make enhancements about this in the following versions. I have created an issue about this https://github.com/aspnetzero/aspnet-zero-core/issues/3929