Home Manual Reference Source Repository

docs/view/hooks/ViewHookExecutor.js

import { VIEW_HOOK_METADATA, ViewHookMetadata } from './common';
import { isFunction, get } from '../../utils';
import { ViewContainer } from '../ViewContainer';
import { Subject } from '../../events';

/**
 * Executes view hooks on a component instance.
 * @export
 * @class ViewHookExecutor
 */
export class ViewHookExecutor {
  private _interceptors = new WeakMap<any, { [key: string]: Function[] }>();

  /**
   * Invokes the view hook on the instance with the given argument.
   * This will also invoke any interceptors for the hook as well after the hook
   * on the component has been invoked.
   * @template T 
   * @param {T} instance 
   * @param {string} method 
   * @param {*} [arg] 
   * @returns {*} 
   */
  execute<T>(instance: T, method: string, arg?: any): any {
    const target = instance[method];
    let returnValue;

    if (isFunction(target)) {
      returnValue = target.call(instance, arg);
    }

    const interceptorMap = this._interceptors.get(instance) || {};
    const interceptors = interceptorMap[method] || [];

    for (const interceptor of interceptors) {
      interceptor(arg);
    }

    return returnValue;
  }

  /**
   * Registers an interceptor for a hook on a given instance.
   * @param {*} instance 
   * @param {string} hook 
   * @param {Function} fn 
   */
  registerInterceptor(instance: any, hook: string, fn: Function): void {
    const map = this._interceptors.get(instance) || {};
    const list = map[hook] = map[hook] || [];

    list.push(fn);

    this._interceptors.set(instance, map);
  }

  /**
   * Links a resolved view container to any hook observables. Metadata is read from the given target.
   * @template T The component type for the view container.
   * @param {ViewContainer<T>} viewContainer 
   * @param {Object} target 
   */
  linkObservers<T>(viewContainer: ViewContainer<T>, target: Object): void {
    const hooks = Reflect.getOwnMetadata(VIEW_HOOK_METADATA, target) || {};
    const { component } = viewContainer;

    if (!component) {
      return;
    }

    for (const hookObservableName of Object.keys(hooks)) {
      const hookNames = hooks[hookObservableName] || [];
      const subject = new Subject<any>();

      Object.defineProperty(component, hookObservableName, {
        writable: true,
        configurable: true,
        enumerable: true,
        value: subject.asObservable()
      });

      viewContainer.destroyed.subscribe(() => subject.complete());

      for (const name of hookNames) {
        this.registerInterceptor(component, name, arg => subject.next(arg));
      }
    }
  }
}