module中定义的高层API现在已经介绍的差不多了,本文就把后面剩下的几个能介绍的先介绍了(不能介绍的还有蛮多的,比如filter,controller,directive,这些使我们后面讨论的内容,敬请期待 :) )。

和依赖注入关系比较紧密的剩下2个方法分别是value和decorator。

Value

在angular中,比较常见的问题除了service,factory和provider三者之间有何区别和联系之外(想知道答案就去看依赖注入 — Factory vs Service),还有一个问题也算比较热门。那就是constant和value有什么区别?

光从定义方式上看,它们两者好像是没有什么区别:

module.constant('a','aConstant');
module.value('b','bValue');

它们的定义方式都很简单,就是一个键值对。然而angular为什么要弄两个API出来呢?让我们从代码层面上看看:

// value在module中的定义
value: invokelater('$provide','value'),// constant在module中的定义
constant: invokelater('$provide','constant','unshift')

可以发现,它们的区别在于一个对应注入器内部的value函数,另一个对应constant函数:

function value(name,val) { 
  return factory(name,valueFn(val),false); 
}

// factory函数的定义
function factory(name,factoryFn,enforce) {
  return provider(name,{
    $get: enforce !== false ? enforceReturnValue(name,factoryFn) : factoryFn
  });
}

// valueFn的定义
function valueFn(value) {
  return function valueRef() {
    return value;
  };
}

所以value本质上也是基于factory来实现的,通过valueFn将一个值封装成一个返回它自身的函数。而这里也利用到了factory函数的第三个参数:enforce。将它设置为false表示value可以被定义为undefined,也就是说下面的定义是合法的,尽管我们不会无聊到去这样做:

module.value('a',undefined);

正式因为value本质上也是依赖于factory的,所以value和constant的区别也就一目了然了。constant对于两个注入器都是可见的,因为在constant的实现中将constant的值同时放入到了两个注入器的缓存中。而value则不然,它的值仅对于实例注入器可见。所以在provider的构造函数以及config队列定义的任务中可以将constant声明为依赖,但是不可将value声明为依赖。因为provider的构造函数以及config队列是通过provider注入器来调用的。

但是在实际开发中,用到value的场合并不太多。至少对于我而言很少用到它,因为value提供的功能用constant基本上都能够满足。而constant由于其对两个注入器都是可见的,所以使用起来更方便,只要支持依赖注入的地方就可以使用,不用区分实例注入器和provider注入器。

Decorator

装饰器算是依赖注入部分最后一个要介绍的概念。顾名思义,装饰器实现了装饰器模式(Decorator Pattern)。利用这种模式能够给已经存在的对象附加一些额外的功能或者改变现有的功能,也就是”装饰”它。

那么在angular依赖注入这一上下文环境中,装饰器的作用又是什么呢?

在开发angular应用的时候,我们都引用过其它的第三方模块。引入第三方模块,从依赖注入的观点来看,就是将第三方模块中定义的各种服务都注册一遍,然后在需要的时候将这些服务实例化,放到注入器的缓存中保管并将它们注入到被需要的地方。绝大多数时候第三方的服务都能够很好的工作,但是偶尔我们也想改动一下它们使之能够更好的契合业务需求。这个时候有两条路可以选:

  1. fork这个第三方模块然后直接改动其源代码
  2. 使用decorator对它进行改动

毫无疑问,使用第一种方式在多数情况下并不可取,fork一个模块的代价可能是放弃这个模块后续的版本,毕竟你的改动让这个模块和它后续的版本可能不再兼容。所以为了解决这个问题,angular也提供了装饰器模式的实现decorator:

decorator: invokelaterAndSetModuleName('$provide','decorator')

可见decorator在定义方式上和其它的factory,service并没有什么本质区别。唯一的区别就是它的”蓝图”是$provide中的decorator函数:

function decorator(serviceName,decorFn) {
  // 直接从provider注入器的缓存中取得相应provider
  var origProvider = providerInjector.get(serviceName + providerSuffix),orig$get = origProvider.$get;  // 将原$get方法保存起来,准备覆盖

  // 覆盖$get方法
  origProvider.$get = function() {
    // 得到未经装饰的实例对象
    var origInstance = instanceInjector.invoke(orig$get,origProvider);

    // 利用实例注入器调用装饰器函数,未经装饰的实例以$delegate参数传入
    return instanceInjector.invoke(decorFn,null,{$delegate: origInstance});
  };
}

首先来看看函数的参数是什么意思:

serviceName:表示的是需要装饰的服务名字。
decorFn:装饰函数,用以实现具体的装饰行为。

从decorator函数的实现来看,需要明确两个问题:

  1. 为什么可以直接拿到service相对应的provider?
  2. invoke方法的第三个参数如何工作?

先来回答第一个问题。

decorator函数的第一个参数serviceName其实是一个泛指,并不一定非要是一个service,其实factory,value等等都是可以的(因为它们都有对应的provider;而constant不行,因为它并没有对应的provider)。而我们知道不管是service也好,factory也好,还是value也好。它们最终都是依赖于provider的,而注入器内部的provider函数实现如下:

function provider(name,provider_) {
  assertNotHasOwnProperty(name,'service');
  if (isFunction(provider_) || isArray(provider_)) {
    provider_ = providerInjector.instantiate(provider_);
  }
  if (!provider_.$get) {
    throw $injectorminerr('pget',"Provider '{0}' must define $get factory method.",name);
  }

  // 将创建得到的provider以name + "Provider"的键值命名方式存入到provider注入器缓存中
  return providerCache[name + providerSuffix] = provider_;
}

该函数的最后一行是关键:将创建得到的provider以name + “Provider”的键值命名方式存入到provider注入器缓存中。所以origProvider = providerInjector.get(serviceName + providerSuffix)是可以正常工作的。

下面回答第二个问题。其实这个问题在以前介绍注入器的基础知识的时候就已经讨论过了,这个参数的名字叫做locals,它用来提供额外的依赖或者是覆盖现有的依赖。下面再回顾一下invoke的调用过程就算是加深印象吧。

拿到了provider,就相当于拿到了创建一个依赖对象的”蓝图”,在装饰器中就可以先将它创建出来,然后再随心所欲的修改它了。修改时调用的:

// 第三个参数是locals
instanceInjector.invoke(decorFn,{$delegate: origInstance})

// 注入器的invoke函数实现
function invoke(fn,self,locals,serviceName) {
  if (typeof locals === 'string') {
    serviceName = locals;
    locals = null;
  }

  // 将声明的参数注入到函数中
  var args = injectionArgs(fn,serviceName);
  if (isArray(fn)) {
    fn = fn[fn.length - 1];
  }

  if (!isClass(fn)) {
    return fn.apply(self,args);
  } else {
    args.unshift(null);
    return new (Function.prototype.bind.apply(fn,args))();
  }
}

// 准备依赖对象
function injectionArgs(fn,serviceName) {
  var args = [],$inject = createInjector.$$annotate(fn,strictDi,serviceName);

  for (var i = 0,length = $inject.length; i < length; i++) {
    var key = $inject[i];
    if (typeof key !== 'string') {
      throw $injectorminerr('itkn','Incorrect injection token! Expected service name as string,got {0}',key);
    }

    // 如果locals中含有key相同的属性,就使用locals中的属性作为依赖
    args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
                                                     getService(key,serviceName));
  }
  return args;
}

这里完成了将未经修改的实例以$delegate传入到装饰器函数中。比如下面这段代码就实现了一个简单的装饰,给一个factory的返回实例增加了一个字段:

module.factory('aFactory',function() {
  return {
    a: 'aConstant'
  };
});

module.decorator('aFactory',function($delegate) {
  $delegate.b = 'bConstant';
});

module.run(function(aFactory) {
  console.log(aFactory.a);  // 输出:aConstant
  console.log(aFactory.b);  // 输出:bConstant
});

decorator使用的场合并不是那么多,但是了解它的用法有时候能够帮上大忙。

至此,angular中有关依赖注入的讨论就告一段落了。一共有好些篇文章来介绍这个特性,毕竟这个特性是angular中一个不可或缺同时也是非常吸引人的特性。它可以说是angular框架的中枢神经,在它的帮助下,才得以实现诸多强大的特性,比如前面介绍过的scope,以及未来要介绍的directive等等。

文章写的可能存在一些问题,如果有任何疑问欢迎提出来一起探讨。 谢谢。

[AngularJS面面观] 24. 依赖注入 --- Value以及Decorator的更多相关文章

  1. Swift设计模式之装饰模式

    转自Swift设计模式原文Design-Patterns-In-Swift

  2. swift设计模式学习 - 装饰模式

    移动端访问不佳,请访问我的个人博客设计模式学习的demo地址,欢迎大家学习交流装饰模式在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。装饰模式的特点装饰对象和真实对象有相同的接口。

  3. Android Studio是否支持用于Android UI设计的AngularJS?

    我对AndroidStudio有疑问:AS在设计XML文件时是否支持AngularJS代码,例如:对于小动画或效果?

  4. android – 如何使用ClientID和ClientSecret在Phonegap中使用Angularjs登录Google OAuth2

    我正尝试使用Angularjs(使用IonicFramework)通过GoogleOAuth2从我的Phonegap应用程序登录.目前我正在使用http://phonegap-tips.com/articles/google-api-oauth-with-phonegaps-inappbrowser.html进行登录.但是当我使用Angular-UI-RouterforIonic时,它正在创建非常

  5. 利用require.js与angular搭建spa应用的方法实例

    这篇文章主要给大家介绍了关于利用require.js与angular搭建spa应用的方法实例,文中通过示例代码给大家介绍的非常详细,对大家的理解和学习具有一定的参考学习价值,需要的朋友们下面跟着小编来一起看看吧。

  6. 详解Angular动态组件

    本文主要介绍了Angular动态组件,对此感兴趣的同学,可以亲自实验一下。

  7. 详解如何使用webpack+es6开发angular1.x

    本篇文章主要介绍了详解如何使用webpack+es6开发angular1.x,具有一定的参考价值,有兴趣的可以了解一下

  8. angular2系列之路由转场动画的示例代码

    本篇文章主要介绍了angular2系列之路由转场动画的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  9. 一种angular的方法级的缓存注解(装饰器)

    本篇文章主要介绍了一种angular的方法级的缓存注解(装饰器),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  10. 动手写一个angular版本的Message组件的方法

    本篇文章主要介绍了动手写一个angular版本的Message组件的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

随机推荐

  1. Angular2 innerHtml删除样式

    我正在使用innerHtml并在我的cms中设置html,响应似乎没问题,如果我这样打印:{{poi.content}}它给了我正确的内容:``但是当我使用[innerHtml]=“poi.content”时,它会给我这个html:当我使用[innerHtml]时,有谁知道为什么它会剥离我的样式Angular2清理动态添加的HTML,样式,……

  2. 为Angular根组件/模块指定@Input()参数

    我有3个根组件,由根AppModule引导.你如何为其中一个组件指定@input()参数?也不由AppModalComponent获取:它是未定义的.据我所知,你不能将@input()传递给bootstraped组件.但您可以使用其他方法来做到这一点–将值作为属性传递.index.html:app.component.ts:

  3. angular-ui-bootstrap – 如何为angular ui-bootstrap tabs指令指定href参数

    我正在使用角度ui-bootstrap库,但我不知道如何为每个选项卡指定自定义href.在角度ui-bootstrap文档中,指定了一个可选参数select(),但我不知道如何使用它来自定义每个选项卡的链接另一种重新定义问题的方法是如何使用带有角度ui-bootstrap选项卡的路由我希望现在还不算太晚,但我今天遇到了同样的问题.你可以通过以下方式实现:1)在控制器中定义选项卡href:2)声明一个函数来改变控制器中的散列:3)使用以下标记:我不确定这是否是最好的方法,我很乐意听取别人的意见.

  4. 离子框架 – 标签内部的ng-click不起作用

    >为什么标签标签内的按钮不起作用?>但是标签外的按钮(登陆)工作正常,为什么?>请帮我解决这个问题.我需要在点击时做出回复按钮workingdemo解决方案就是不要为物品使用标签.而只是使用divHTML

  5. Angular 2:将值传递给路由数据解析

    我正在尝试编写一个DataResolver服务,允许Angular2路由器在初始化组件之前预加载数据.解析器需要调用不同的API端点来获取适合于正在加载的路由的数据.我正在构建一个通用解析器,而不是为我的许多组件中的每个组件设置一个解析器.因此,我想在路由定义中传递指向正确端点的自定义输入.例如,考虑以下路线:app.routes.ts在第一个实例中,解析器需要调用/path/to/resourc

  6. angularjs – 解释ngModel管道,解析器,格式化程序,viewChangeListeners和$watchers的顺序

    换句话说:如果在模型更新之前触发了“ng-change”,我可以理解,但是我很难理解在更新模型之后以及在完成填充更改之前触发函数绑定属性.如果您读到这里:祝贺并感谢您的耐心等待!

  7. 角度5模板形式检测形式有效性状态的变化

    为了拥有一个可以监听其包含的表单的有效性状态的变化的组件并执行某些组件的方法,是reactiveforms的方法吗?

  8. Angular 2 CSV文件下载

    我在springboot应用程序中有我的后端,从那里我返回一个.csv文件WheniamhittingtheURLinbrowsercsvfileisgettingdownloaded.现在我试图从我的角度2应用程序中点击此URL,代码是这样的:零件:服务:我正在下载文件,但它像ActuallyitshouldbeBook.csv请指导我缺少的东西.有一种解决方法,但您需要创建一个页面上的元

  9. angularjs – Angular UI-Grid:过滤后如何获取总项数

    提前致谢:)你应该避免使用jQuery并与API进行交互.首先需要在网格创建事件中保存对API的引用.您应该已经知道总行数.您可以使用以下命令获取可见/已过滤行数:要么您可以使用以下命令获取所选行的数量:

  10. angularjs – 迁移gulp进程以包含typescript

    或者我应该使用tsc作为我的主要构建工具,让它解决依赖关系,创建映射文件并制作捆绑包?

返回
顶部