2.0.1 • Published 4 years ago

@thenja/event-manager v2.0.1

Weekly downloads
-
License
MIT
Repository
github
Last release
4 years ago

Test Coverage-shield-badge-1

Event Manager

Event manager is an easy way to manage events in a web applications. Its a basic class that you can use to add extra event functionality using methods like: emit(), on(), off() and so on.

Key features

  • Bind and unbind to events using a decorator
    • Reduce code by automatically binding and unbinding to events with a method decorator
    • Works great with Angular components, binds on the ngOnInit method and unbinds on the ngOnDestroy method
  • Run code after all event listeners have executed
    • Easily run code after all event listeners have completed. Works with async event listeners as well
  • Small & light weight
  • Inheritance or Composition
    • Use the EventManager class via inheritance or composition

How to use

  1. Install the module:

npm install @thenja/event-manager --save

  1. You have two ways to use the EventManager. Inheritance via extends or composition via property composition.

Inheritance:

import { EventManager, EventListener, INextFn } from '@thenja/event-manager';

// Important: event names have to be unique for the whole application
const USER_EVENTS = {
  SIGN_IN: 'user-sign-in'
};

export class UserService extends EventManager {
  constructor() {
    super(USER_EVENTS);
  }

  userSignIn() {
    this.emit(USER_EVENTS.SIGN_IN, {});
  }
}

// Now you could listen to events
userService.on(USER_EVENTS.SIGN_IN, () => {});

Composition with property (recommended):

import { EventManager, EventListener, INextFn } from '@thenja/event-manager';

// Important: event names have to be unique for the whole application
const USER_EVENTS = {
  SIGN_IN: 'user-sign-in'
};

export class UserService {
  events = new EventManager(USER_EVENTS);

  userSignIn() {
    this.events.emit(USER_EVENTS.SIGN_IN, {});
  }
}

// Now you could listen to events
userService.events.on(USER_EVENTS.SIGN_IN, () => {});

Methods / API

constructor (emittedEvents: { key: string: string })

The possible emitted events that the manager can emit.

emit (eventName: string, data?: any)

Emit an event. The second parameter is the data that is passed to the listener function.

.on (eventName: string, fn: (data?: any, next?: INextFn) => void, scope?: any)

Bind to an event. If the emitted event sends data, it will be the first parameter.

.once (eventName: string, fn: (data?: any, next?: INextFn) => void, scope?: any)

Bind to an event once, the listener will only fire once.

.off (eventName: string, fn: (data?: any, next?: INextFn) => void)

Unbind from an event.

.offAll (eventName?: string)

Unbind from all events. If you pass in an eventName, it will only unbind all listeners for that event.

@EventListener decorator

The @EventListener decorator is a method decorator that will automatically subscribe and unsubscribe to events, its very useful in Angular components, but can be used anywhere.

IMPORTANT: If using the EventListener decorator, make event names unique throughout your app. If not, the EventListener decorator will throw an error.

How it works:

The decorator simple binds the event on an initialisation method, in the case of Angular, its the ngOnInit method. It unbinds the event on a destroy method, in the case of Angular, its the ngOnDestroy method.

The decorator can be used in two ways:

Two individual arguments:

@EventListener(eventName: string, classObject?: Object)
  • eventName : The name of the emitted event.
  • classObject: (optional) The class that is emitting the event. (view example 1 below in the Use cases / Examples section). If the listener method is listening to internal events that are emitted from within the same class, this can be left blank (view example 2 below in the Use cases / Examples section).

One object argument:

@EventListener(args: IEventListenerArgs)

// Example
@EventListener({
  eventName: 'user-sign-in',
  eventClass: UserService.name,
  initFn: 'ngOnInit',
  destroyFn: 'ngOnDestroy'
})
  • eventName : Same as above
  • eventClass : The constructor name of the class that is emitting the event.
  • initFn : Default = ngOnInit The function that is fired when the component / class is initialised. This is where binding to events will occur. If you want to bind to events when the constructor is fired, view example 3 below in the Use cases / Examples section.
  • destroyFn : Default = ngOnDestroy The function that is fired when the component is destroyed. This is where unbinding from events will occur.

Use cases / Examples

Example 1 - Listen to an event thats emitted from a service inside an Angular component:

In this example, we have a service that is injected into an Angular component, the service emits events, the component can bind to these events.

import { EventManager, EventListener, INextFn } from '@thenja/event-manager';

const USER_EVENTS = {
  SIGN_IN: 'user-sign-in'
};

// our service which extends EventManager
export class UserService extends EventManager {
  constructor() {
    super(USER_EVENTS);
  }

  userSignIn() {
    const userData = {};
    this.emit(USER_EVENTS.SIGN_IN, userData);
  }
}

// our angular component
@Component(...)
export class HomePageComponent {
  constructor(private userSrv: UserService) {}

  @EventListener(USER_EVENTS.SIGN_IN, UserService)
  userSignInListener(userData: any) {
    // This method will be fired when the user-sign-in event is emitted
  }
}

OR you could use composition like:

import { EventManager, EventListener, INextFn } from '@thenja/event-manager';

const USER_EVENTS = {
  SIGN_IN: 'user-sign-in'
};

// our service which extends EventManager
export class UserService {
  events: EventManager;

  constructor() {
    this.events = new EventManager(USER_EVENTS);
  }

  userSignIn() {
    const userData = {};
    this.events.emit(USER_EVENTS.SIGN_IN, userData);
  }
}

// our angular component
@Component(...)
export class HomePageComponent {
  constructor(private userSrv: UserService) {}

  @EventListener(USER_EVENTS.SIGN_IN, UserService)
  userSignInListener(userData: any) {
    // This method will be fired when the user-sign-in event is emitted
  }
}

Example 2 - Listen to internal events:

In this example, we will listen to internal events that are emitted within the same class.

import { EventManager, EventListener, INextFn } from '@thenja/event-manager';

const USER_EVENTS = {
  SIGN_IN: 'user-sign-in'
};

export class UserService extends EventManager {
  constructor() {
    super(USER_EVENTS);
  }

  userSignIn() {
    const userData = {};
    this.emit(USER_EVENTS.SIGN_IN, userData);
  }

  @EventListener(USER_EVENTS.SIGN_IN)
  userSignInListener(userData: any) {
    // This method will be fired when the user-sign-in event is emitted
  }
}

Example 3 - Bind to events inside constructor:

In this example, we set different init and destroy functions. In this case, we bind to events when the constructor is fired.

import { EventManager, EventListener, INextFn } from '@thenja/event-manager';

const USER_EVENTS = {
  SIGN_IN: 'user-sign-in'
};

export class UserService extends EventManager {
  constructor() {
    super(USER_EVENTS);
    this.init();
  }

  protected init();
  destroy();

  @EventListener({
    eventName: USER_EVENTS.SIGN_IN,
    initFn: 'init',
    destroyFn: 'destroy'
  })
  userSignInListener(userData: any) {
    // This method will now be bound to the event when the constructor fires
  }
}

Example 4 - Run code after all event listeners have completed execution:

In this example, we will run code after all event listeners have finished executing. In this example, we are not using the EventListener decorator, but you could still do the same with it.

import { EventManager, EventListener, INextFn } from '@thenja/event-manager';

const USER_EVENTS = {
  SIGN_OUT: 'user-sign-out'
};

// our user settings service
class UserSettingsService {
  constructor(private appEventsHub: AppEventsHub) {
    this.appEventsHub.on(USER_EVENTS.SIGN_OUT, this.userSignedOutListener, this);
  }

  private userSignedOutListener(data, next: INextFn) {
    setTimeout(() => {
      // fire the next function to indicate we are done
      next();
    }, 10);
  }
}

// our user service
class UserService {
  constructor(private appEventsHub: AppEventsHub) {
    this.appEventsHub.on(USER_EVENTS.SIGN_OUT, this.userSignedOutListener, this);
  }

  private userSignedOutListener(data, next: INextFn) {
    setTimeout(() => {
      // the next function returns a completed function, you can set a callback
      // function that gets fired when all listeners have completed and fired
      // their next() functions.
      next().completed(() => {
        // all listeners are done...
      });
    }, 5);
  }
}

// our app events hub service
class AppEventsHub extends EventManager {
  constructor() {
    super(USER_EVENTS);
  }

  userSignedOut() {
    this.emit(USER_EVENTS.SIGN_OUT);
  }
}

Example 5 - Setting the scope

Most of the time you will want to set the scope to this so that the keyword this inside your listener function points to your class instance.

const USER_EVENTS = {
  SIGN_OUT: 'user-sign-out'
};

class UserService {
  private userIsSignedIn = true;
  constructor(private appEventsHub: AppEventsHub) {
    // set this as the scope
    this.appEventsHub.on(USER_EVENTS.SIGN_OUT, this.userSignedOutListener, this);
  }

  private userSignedOutListener(data, next: INextFn) {
    this.userIsSignedIn = false;
  }
}

Development

npm run init - Setup the app for development (run once after cloning)

npm run dev - Run this command when you want to work on this app. It will compile typescript, run tests and watch for file changes.

Distribution

npm run build -- -v <version> - Create a distribution build of the app.

-v (version) - Optional Either "patch", "minor" or "major". Increase the version number in the package.json file.

The build command creates a /compiled directory which has all the javascript compiled code and typescript definitions. As well, a /dist directory is created that contains a minified javascript file.

Testing

Tests are automatically ran when you do a build.

npm run test - Run the tests. The tests will be ran in a nodejs environment. You can run the tests in a browser environment by opening the file /spec/in-browser/SpecRunner.html.

License

MIT © Nathan Anderson