可以用$ provide.decorator装饰Angular $注入器吗?

时间:2013-12-12 18:24:25

标签: angularjs

也许这是一个可怕的想法,但如果是,那么请告诉我为什么,然后假装这是一个学术练习,不会在生产中看到光明。

我想为Angular $ injector服务添加一些逻辑,以监视何时将某些服务注入其他服务。由于Angular似乎提供了一种装饰服务的机制,我认为这将是一种方法。但是,以下代码会引发错误。

(function () {
    'use strict';

    var app = angular.module('app');

    app.config(['$provide', function ($provide) {
        $provide.decorator('$injector', ['$log', '$delegate', addLoggingToInjector]);
    }]);

    function addLoggingToInjector($log, $delegate) {
        var baseInstantiate = $delegate.instantiate;
        var baseInvoke = $delegate.invoke;

        $delegate.instantiate = function (type, locals) {
            // $log.debug('Calling $injector.instantiate');

            baseInstantiate(type, locals);
        };

        $delegate.invoke = function (fn, self, locals) {
            // $log.debug('Calling $injector.invoke');

            baseInvoke(fn, self, locals);
        };

        return $delegate;
    };
})();

具体错误是:

  

未捕获错误:[$ injector:modulerr]无法实例化模块应用   由于:错误:[$ injector:unpr]未知提供者:$ injectorProvider

2 个答案:

答案 0 :(得分:13)

答案是:不。


$provide.decorator用于拦截服务创建 - 这就是为什么从.config块调用它时,仍有时间配置所有服务,因为没有他们已经创建了。 $provide.decorator基本上获得服务的Provider,并使用新发送的$get交换decorFn

$injector与其他服务不同。它是{{1>} bootstrapping应用程序的第一个步骤创建的,在调用app.config之前。 [查看角度源代码中的函数:bootstrapcreateInjector]

但是,嘿,你可以通过稍微调整一下源代码很容易实现你的目标:-)特别要看function invoke(fn, self, locals)


更新我从@KayakDave获得了一些灵感。实际上你不必深入挖掘源代码本身。您可以使用以下模式观察对$injector方法的任何调用:

 app.config(['$injector', function ($injector) {

      $injector.proper =
      {
          get : $injector.get,
          invoke : $injector.invoke,
          instantiate : $injector.instantiate,
          annotate : $injector.annotate,
          has : $injector.has
      }

      function getDecorator(serviceName)
      {
          console.log("injector GET: ", serviceName);
          return this.proper.get(serviceName);
      }

      function invokeDecorator(fn, self, locals)
      {
          console.log("injector INVOKE: ", fn, self, locals);
          return this.proper.invoke(fn, self, locals);
      }

      function instantiateDecorator(Type, locals)
      {
          console.log("injector INSTANTIATE: ", Type, locals);
          return this.proper.instantiate(Type, locals);
      }

      function annotateDecorator (fn)
      {
          console.log("injector ANNOTATE: ", fn);
          return this.proper.annotate(fn);
      }

      function hasDecorator(name)
      {
          console.log("injector HAS: ", name);
          return this.proper.has(name);
      }

      $injector.get = getDecorator;
      $injector.invoke = invokeDecorator;
      $injector.instantiate = instantiateDecorator;
      $injector.annotate = annotateDecorator;
      $injector.has = hasDecorator;
  }]);

PLNKR

答案 1 :(得分:12)

您不能在$ injector上使用Angular装饰器服务。正如Artur所说,$injector与其他服务有点不同。但我们可以创建自己的装饰器。

为什么我们不能使用Angular的装饰器

在代码级别,问题是$injector没有构造函数 - 没有$injectorProvider

例如,这两个都返回true:

$injector.has('$location');
$injector.has('$locationProvider') 

但是,这会返回true:

$injector.has('$injector')

这会返回false:

$injector.has('$injectorProvider')

当我们查看Angular decorator函数时,我们看到了重要性:

function decorator(serviceName, decorFn) {
   var origProvider = providerInjector.get(serviceName + providerSuffix),
       orig$get = origProvider.$get;

   origProvider.$get = function() {
      var origInstance = instanceInjector.invoke(orig$get, origProvider);
     return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
   };
}

providerSuffix = 'Provider'

因此Angular装饰器希望对服务的构造函数(serviceName + providerSuffix)进行操作。实际上,因为我们没有$injectorProvider我们不能使用装饰

解决方案

我们可以做的是通过将注入器的默认get替换为调用原始的Angular定义的get后跟我们的函数来替换Angular注入器的get function

我们会将其应用于$injector而非不存在的$injectorProvider,如下所示:

app.config(['$provide','$injector', function ($provide,$injector) {

    // The function we'll add to the injector
    myFunc = function () {
        console.log("injector called ", arguments);
    };

    // Get a copy of the injector's get function
    var origProvider = $injector,
        origGet = origProvider.get;

    //Override injector's get with our own
    origProvider.get = function() {

        // Call the original get function 
        var returnValue = origGet.apply(this, arguments);

        // Call our function
        myFunc.apply(this,arguments);

        return returnValue;
    }
}]);

您将看到正在注入的提供程序是第一个扩充,因此app.value('aValue', 'something');会生成以下日志语句:

injector called  ["aValueProvider"]

Demo fiddle