【问题标题】:Can the Angular $injector be decorated with $provide.decorator?Angular $injector 可以用 $provide.decorator 装饰吗?
【发布时间】:2013-12-17 05:04:32
【问题描述】:

也许这是一个糟糕的想法,但如果是这样,请告诉我原因,然后假装这是一项不会在生产中看到曙光的学术活动。

我想为 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

【问题讨论】:

  • 你找到方法来检测你注入的服务了吗?
  • 是的。我向 ng 模块添加了一个运行函数,该函数接受 $injector 作为参数并覆盖服务上的方法。我基本上遵循下面@KayakDave 提出的模式。
  • 您能否检测到,例如,您将$log 注入MyCtrl?或者只是你正在注入$log
  • 只有我在注入 $log。但是,如果你重写$controllerProvider的注册函数,你可以在injectable上记录控制器的名字。然后当注入器收到它时,您可以提取名称并知道您要注入的内容。但是,要使其正常工作,您需要在声明任何控制器之前覆盖 register 方法,因为这一切都发生在配置时。
  • 酷,谢谢!正在考虑自动将控制器信息添加到日志服务。

标签: angularjs


【解决方案1】:

答案是:没有。


$provide.decorator 用于拦截服务创建 - 这就是为什么它从.config 块调用,当还有时间配置所有服务时,因为它们都没有被创建. $provide.decorator 基本上获得了服务的Provider 并将其$get 与新交付的decorFn 交换。

$injector 不像其他服务。它被创建为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

【讨论】:

  • 您的更新非常酷,我很欣赏它的完整性。非常感谢!
【解决方案2】:

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

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

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

例如,这两个都返回 true:

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

但是,虽然这会返回 true:

$injector.has('$injector')

这返回假:

$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,所以我们不能使用装饰器

解决方案

我们可以做的是自己覆盖Angular注入器的get function,将注入器的默认get替换为调用原始的、Angular定义的get,后跟我们的函数。

我们将把它应用到$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

【讨论】:

  • 少数 cmets:$injector.get('$injector') 始终等于 $injector。 Angular decorator 覆盖了提供者的 $get——该函数用于创建服务的具体实例。 $injector 有一个名为get 的函数纯属巧合,所以我认为将来自提供者世界的$get 与来自实例世界的a_function 进行比较并不是最好的类比。 (或者请进一步解释你的意思?)。无论如何,非常感谢您的启发。 +1
  • @artur 关于 $injector 的好点——我从装饰器代码中提取了它,所以我将它并行化。但你绝对是对的,可以清理。我不认为get 是巧合。尽管我认为$getget 的构造函数版本是正确的,但它有一些含义。但我不认为他们在这里咬我们。在我看来,Angular 像服务一样使用 $injector(包括在其上使用 get),但由于它手动实例化它,因此不需要像其他所有服务那样的构造函数。但我只是根据我在代码中看到的内容进行猜测。
  • 100%: $get 是每个提供者的通用名称,用于创建服务/过滤器/等实例的方法(它存在于提供者世界中)vs. $injector 中的 get 是一种返回服务的方法(它存在于具体实例世界中)。也许你是对的,名称以某种方式连接:-) 我从装饰器代码中获取了它,所以我将它并行 decorator function 略有不同:首先他们get 具体提供者,然后他们装饰其$get 方法:-)
  • 在代码中戳了一下之后,我没有看到任何关于 get$get 的命名是否是巧合的确切信息。感觉好像和我有联系——但我不能指出一条线来显示它。无论如何,这是一次很好的探索——感谢您的推动。根据您的评论,我将在代码中更新origProvider。我们仍然需要两个变量,所以我们有指向新旧变量的指针——但现在它更干净了。谢谢! +1
  • 它可能更干净:var origGet = $injector.get; $injector.get = function() { myFunc.apply(this,arguments); return origGet.apply(this, arguments); }
猜你喜欢
  • 1970-01-01
  • 2020-05-20
  • 2012-02-05
  • 2016-06-07
  • 2016-01-13
  • 1970-01-01
  • 2023-03-16
  • 1970-01-01
  • 2019-05-01
相关资源
最近更新 更多