#

API Docs for: v2.11.1
Show: Inherited Protected Private Deprecated

# File: packages/container/lib/container.js

    import { dictionary, symbol, setOwner, OWNER, NAME_KEY } from 'ember-utils';
    import { ENV } from 'ember-environment';
    import { assert, deprecate, runInDebug } from 'ember-metal';

    const CONTAINER_OVERRIDE = symbol('CONTAINER_OVERRIDE');

    /**
     A container used to instantiate and cache objects.

     Every `Container` must be associated with a `Registry`, which is referenced
     to determine the factory and options that should be used to instantiate
     objects.

     The public API for `Container` is still in flux and should not be considered
     stable.

     @private
     @class Container
     */
    export default function Container(registry, options) {
      this.registry        = registry;
      this.owner           = options && options.owner ? options.owner : null;
      this.cache           = dictionary(options && options.cache ? options.cache : null);
      this.factoryCache    = dictionary(options && options.factoryCache ? options.factoryCache : null);
      this.validationCache = dictionary(options && options.validationCache ? options.validationCache : null);
      this._fakeContainerToInject = buildFakeContainerWithDeprecations(this);
      this[CONTAINER_OVERRIDE] = undefined;
      this.isDestroyed = false;
    }

    Container.prototype = {
      /**
       @private
       @property owner
       @type Object
       */
      owner: null,

      /**
       @private
       @property registry
       @type Registry
       @since 1.11.0
       */
      registry: null,

      /**
       @private
       @property cache
       @type InheritingDict
       */
      cache: null,

      /**
       @private
       @property factoryCache
       @type InheritingDict
       */
      factoryCache: null,

      /**
       @private
       @property validationCache
       @type InheritingDict
       */
      validationCache: null,

      /**
       Given a fullName return a corresponding instance.

       The default behaviour is for lookup to return a singleton instance.
       The singleton is scoped to the container, allowing multiple containers
       to all have their own locally scoped singletons.

       ```javascript
       let registry = new Registry();
       let container = registry.container();

       registry.register('api:twitter', Twitter);

       let twitter = container.lookup('api:twitter');

       twitter instanceof Twitter; // => true

       // by default the container will return singletons
       let twitter2 = container.lookup('api:twitter');
       twitter2 instanceof Twitter; // => true

       twitter === twitter2; //=> true
       ```

       If singletons are not wanted, an optional flag can be provided at lookup.

       ```javascript
       let registry = new Registry();
       let container = registry.container();

       registry.register('api:twitter', Twitter);

       let twitter = container.lookup('api:twitter', { singleton: false });
       let twitter2 = container.lookup('api:twitter', { singleton: false });

       twitter === twitter2; //=> false
       ```

       @private
       @method lookup
       @param {String} fullName
       @param {Object} [options]
       @param {String} [options.source] The fullname of the request source (used for local lookup)
       @return {any}
       */
      lookup(fullName, options) {
        assert('fullName must be a proper full name', this.registry.validateFullName(fullName));
        return lookup(this, this.registry.normalize(fullName), options);
      },

      /**
       Given a fullName, return the corresponding factory.

       @private
       @method lookupFactory
       @param {String} fullName
       @param {Object} [options]
       @param {String} [options.source] The fullname of the request source (used for local lookup)
       @return {any}
       */
      lookupFactory(fullName, options) {
        assert('fullName must be a proper full name', this.registry.validateFullName(fullName));
        return factoryFor(this, this.registry.normalize(fullName), options);
      },

      /**
       A depth first traversal, destroying the container, its descendant containers and all
       their managed objects.

       @private
       @method destroy
       */
      destroy() {
        eachDestroyable(this, item => {
          if (item.destroy) {
            item.destroy();
          }
        });

        this.isDestroyed = true;
      },

      /**
       Clear either the entire cache or just the cache for a particular key.

       @private
       @method reset
       @param {String} fullName optional key to reset; if missing, resets everything
       */
      reset(fullName) {
        if (arguments.length > 0) {
          resetMember(this, this.registry.normalize(fullName));
        } else {
          resetCache(this);
        }
      },

      /**
       Returns an object that can be used to provide an owner to a
       manually created instance.

       @private
       @method ownerInjection
       @returns { Object }
      */
      ownerInjection() {
        return { [OWNER]: this.owner };
      }
    };

    function isSingleton(container, fullName) {
      return container.registry.getOption(fullName, 'singleton') !== false;
    }

    function lookup(container, fullName, options = {}) {
      if (options.source) {
        fullName = container.registry.expandLocalLookup(fullName, options);

        // if expandLocalLookup returns falsey, we do not support local lookup
        if (!fullName) { return; }
      }

      if (container.cache[fullName] !== undefined && options.singleton !== false) {
        return container.cache[fullName];
      }

      let value = instantiate(container, fullName);

      if (value === undefined) { return; }

      if (isSingleton(container, fullName) && options.singleton !== false) {
        container.cache[fullName] = value;
      }

      return value;
    }

    function markInjectionsAsDynamic(injections) {
      injections._dynamic = true;
    }

    function areInjectionsDynamic(injections) {
      return !!injections._dynamic;
    }

    function buildInjections(/* container, ...injections */) {
      let hash = {};

      if (arguments.length > 1) {
        let container = arguments[0];
        let injections = [];
        let injection;

        for (let i = 1; i < arguments.length; i++) {
          if (arguments[i]) {
            injections = injections.concat(arguments[i]);
          }
        }

        container.registry.validateInjections(injections);

        for (let i = 0; i < injections.length; i++) {
          injection = injections[i];
          hash[injection.property] = lookup(container, injection.fullName);
          if (!isSingleton(container, injection.fullName)) {
            markInjectionsAsDynamic(hash);
          }
        }
      }

      return hash;
    }

    function factoryFor(container, fullName, options = {}) {
      let registry = container.registry;

      if (options.source) {
        fullName = registry.expandLocalLookup(fullName, options);

        // if expandLocalLookup returns falsey, we do not support local lookup
        if (!fullName) { return; }
      }

      let cache = container.factoryCache;
      if (cache[fullName]) {
        return cache[fullName];
      }
      let factory = registry.resolve(fullName);
      if (factory === undefined) { return; }

      let type = fullName.split(':')[0];
      if (!factory || typeof factory.extend !== 'function' || (!ENV.MODEL_FACTORY_INJECTIONS && type === 'model')) {
        if (factory && typeof factory._onLookup === 'function') {
          factory._onLookup(fullName);
        }

        // TODO: think about a 'safe' merge style extension
        // for now just fallback to create time injection
        cache[fullName] = factory;
        return factory;
      } else {
        let injections = injectionsFor(container, fullName);
        let factoryInjections = factoryInjectionsFor(container, fullName);
        let cacheable = !areInjectionsDynamic(injections) && !areInjectionsDynamic(factoryInjections);

        factoryInjections[NAME_KEY] = registry.makeToString(factory, fullName);

        let injectedFactory = factory.extend(injections);

        // TODO - remove all `container` injections when Ember reaches v3.0.0
        injectDeprecatedContainer(injectedFactory.prototype, container);
        injectedFactory.reopenClass(factoryInjections);

        if (factory && typeof factory._onLookup === 'function') {
          factory._onLookup(fullName);
        }

        if (cacheable) {
          cache[fullName] = injectedFactory;
        }

        return injectedFactory;
      }
    }

    function injectionsFor(container, fullName) {
      let registry = container.registry;
      let splitName = fullName.split(':');
      let type = splitName[0];

      let injections = buildInjections(container,
                                       registry.getTypeInjections(type),
                                       registry.getInjections(fullName));
      injections._debugContainerKey = fullName;

      setOwner(injections, container.owner);

      return injections;
    }

    function factoryInjectionsFor(container, fullName) {
      let registry = container.registry;
      let splitName = fullName.split(':');
      let type = splitName[0];

      let factoryInjections = buildInjections(container,
                                              registry.getFactoryTypeInjections(type),
                                              registry.getFactoryInjections(fullName));
      factoryInjections._debugContainerKey = fullName;

      return factoryInjections;
    }

    function instantiate(container, fullName) {
      let factory = factoryFor(container, fullName);
      let lazyInjections, validationCache;

      if (container.registry.getOption(fullName, 'instantiate') === false) {
        return factory;
      }

      if (factory) {
        if (typeof factory.create !== 'function') {
          throw new Error(`Failed to create an instance of '${fullName}'. Most likely an improperly defined class or` +
                          ` an invalid module export.`);
        }

        validationCache = container.validationCache;

        runInDebug(() => {
          // Ensure that all lazy injections are valid at instantiation time
          if (!validationCache[fullName] && typeof factory._lazyInjections === 'function') {
            lazyInjections = factory._lazyInjections();
            lazyInjections = container.registry.normalizeInjectionsHash(lazyInjections);

            container.registry.validateInjections(lazyInjections);
          }
        });

        validationCache[fullName] = true;

        let obj;

        if (typeof factory.extend === 'function') {
          // assume the factory was extendable and is already injected
          obj = factory.create();
        } else {
          // assume the factory was extendable
          // to create time injections
          // TODO: support new'ing for instantiation and merge injections for pure JS Functions
          let injections = injectionsFor(container, fullName);

          // Ensure that a container is available to an object during instantiation.
          // TODO - remove when Ember reaches v3.0.0
          // This "fake" container will be replaced after instantiation with a
          // property that raises deprecations every time it is accessed.
          injections.container = container._fakeContainerToInject;
          obj = factory.create(injections);

          // TODO - remove when Ember reaches v3.0.0
          if (!Object.isFrozen(obj) && 'container' in obj) {
            injectDeprecatedContainer(obj, container);
          }
        }

        return obj;
      }
    }

    // TODO - remove when Ember reaches v3.0.0
    function injectDeprecatedContainer(object, container) {
      Object.defineProperty(object, 'container', {
        configurable: true,
        enumerable: false,
        get() {
          deprecate('Using the injected `container` is deprecated. Please use the `getOwner` helper instead to access the owner of this object.',
                    false,
                    { id: 'ember-application.injected-container', until: '3.0.0', url: 'http://emberjs.com/deprecations/v2.x#toc_injected-container-access' });
          return this[CONTAINER_OVERRIDE] || container;
        },

        set(value) {
          deprecate(
            `Providing the \`container\` property to ${this} is deprecated. Please use \`Ember.setOwner\` or \`owner.ownerInjection()\` instead to provide an owner to the instance being created.`,
            false,
            { id: 'ember-application.injected-container', until: '3.0.0', url: 'http://emberjs.com/deprecations/v2.x#toc_injected-container-access' }
          );

          this[CONTAINER_OVERRIDE] = value;

          return value;
        }
      });
    }

    function eachDestroyable(container, callback) {
      let cache = container.cache;
      let keys = Object.keys(cache);

      for (let i = 0; i < keys.length; i++) {
        let key = keys[i];
        let value = cache[key];

        if (container.registry.getOption(key, 'instantiate') !== false) {
          callback(value);
        }
      }
    }

    function resetCache(container) {
      eachDestroyable(container, (value) => {
        if (value.destroy) {
          value.destroy();
        }
      });

      container.cache.dict = dictionary(null);
    }

    function resetMember(container, fullName) {
      let member = container.cache[fullName];

      delete container.factoryCache[fullName];

      if (member) {
        delete container.cache[fullName];

        if (member.destroy) {
          member.destroy();
        }
      }
    }

    export function buildFakeContainerWithDeprecations(container) {
      let fakeContainer = {};
      let propertyMappings = {
        lookup: 'lookup',
        lookupFactory: '_lookupFactory'
      };

      for (let containerProperty in propertyMappings) {
        fakeContainer[containerProperty] = buildFakeContainerFunction(container, containerProperty, propertyMappings[containerProperty]);
      }

      return fakeContainer;
    }

    function buildFakeContainerFunction(container, containerProperty, ownerProperty) {
      return function() {
        deprecate(
          `Using the injected \`container\` is deprecated. Please use the \`getOwner\` helper to access the owner of this object and then call \`${ownerProperty}\` instead.`,
          false,
          {
            id: 'ember-application.injected-container',
            until: '3.0.0',
            url: 'http://emberjs.com/deprecations/v2.x#toc_injected-container-access'
          }
        );
        return container[containerProperty](...arguments);
      };
    }