'Angular decorator call without trigger preserve context

I'm trying to create a decorator for an angular component function, so my web sockets are automatically setup in an easy an conform way.

I currently store the context, but I feel like there is a better way, calling it without the context, gives undefined errors.

decorator

import { SocketService } from './socket.service';

export function Listen(topic: string, options?: DecoratorOptions) {
  return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
    // Original context
    let self: unknown;

    // default values for our config, we’ll overwrite this with our options parameter
    const config = {
      path: '/',
    } as DecoratorOptions;

    // overwrite any keys passed in to our decorator in the config object
    if (options) {
      Object.keys(options).forEach((x) => (config[x] = options[x]));
    }

    // Subscribe to the topic
    const service = SocketService.getService({ path: config.path });
    let subscription = service.fromEvent(topic).subscribe(function (args) {
      descriptor.value.call(self, args);
    });

    // Destroy subscription on component destroy
    const _originalOnDestroy = target['ngOnDestroy'];
    target['ngOnDestroy'] = function () {
      console.log('destroy');
      subscription.unsubscribe();
      if (_originalOnDestroy) {
        _originalOnDestroy?.apply(this);
      }
    };

    // Store original context
    const _originalOnInit = target['ngOnInit'];
    target['ngOnInit'] = function () {
      self = this;
      if (_originalOnInit) {
        _originalOnInit?.apply(this);
      }
    };

    return descriptor;
  };
}

export interface DecoratorOptions {
  path?: string;
}

Component

@Listen(EVENTS.JOB_STARTED)
jobStarted(data: Cron) {
    const index = this.dataSource.data.findIndex((cron) => cron.id === data.id);
    this.dataSource.data[index]!.running! = true;
}

I know its already solved, but I think it would be helpful for myself and others to know if there is a better way.



Solution 1:[1]

Update: found it. but maybe there is a better way.

I now setup the subscription in the ngOnInit instead of the function. This gives the intended result

import { Subscription } from 'rxjs';
import { AppInjector } from './socket.module';
import { SocketService } from './socket.service';

export function ListenTopic(topic: string, options?: DecoratorOptions) {
  return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
    // default values for our config, we’ll overwrite this with our options parameter
    const config = {
      path: '/',
    } as DecoratorOptions;

    // overwrite any keys passed in to our decorator in the config object
    if (options) {
      Object.keys(options).forEach((x) => (config[x] = options[x]));
    }

    // Create subscription object
    let subscription: Subscription;

    // Destroy subscription on component destroy
    const _originalOnDestroy = target['ngOnDestroy'];
    target['ngOnDestroy'] = function () {
      subscription?.unsubscribe();
      if (_originalOnDestroy) {
        _originalOnDestroy?.apply(this);
      }
    };

    // Store original context
    const _originalOnInit = target['ngOnInit'];
    target['ngOnInit'] = function () {
      // Get the socket service
      const service = AppInjector.get(SocketService)?.getService({ path: config.path });

      // Subscribe to the topic
      subscription = service.fromEvent(topic).subscribe((args) => {
        descriptor.value.call(this, args);
      });
      if (_originalOnInit) {
        _originalOnInit?.apply(this);
      }
    };

    return descriptor;
  };
}

export interface DecoratorOptions {
  path?: string;
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Kiwi