公司一些管理后台的前端页面,使用的是angular开发的,得益于angular的双向绑定和模块化controller使得构建pc端的CRUD应用简单了不少。angular有很多比较难理解的概念,上手起来没有vue简单,不过对着模板项目、看看tutorial、阅读项目代码再仿照项目代码写一些业务功能还是可行的。如果想要用到一些高级功能那就要下一定功夫学习才行。
遇到的问题
在开发的时候遇到了这么一个问题,先上代码
'use strict'; var domain = 'http://localhost:1337'; //开发环境下的服务端地址, var MY_DOMAIN = 'http://production.com'; // 生成环境的网站地址 angular.module('adminApp').run(function($location) { if ($location.host() !== 'localhost') { domain = MY_DOMAIN; } }) .constant('myConfig',{ host: domain,domain: domain,api: this.domain + '/admin',//项目中请求服务端异步获取数据的接口 }
上面的代码,乍一看是自动切换生产和开发环境的服务端地址,可是当部署之后发现这段代码好像并没有生效,domain始终是'http://localhost:1337',并没有通过判断host而被赋值为MY_DOMAIN。
在没有对angular的运行机制有所了解的情况下,我会认为代码会自上而下的执行,这样在.constant的代码执行之前,会先执行.run里面的方法。然而代码的最终执行结果表明,.constant内的代码运行应该是先于.run里面的代码。于是阅读angular的文档来找找原因。
constant和run是什么
angular比较核心的一个概念就是依赖注入,angular的模块化以及模块间的依赖管理都是基于此的。而这些依赖都是从哪里来的或者怎么自建一些依赖呢?这就需要自己定义一些Providers,angular提供了5种Provider recipe(恕我不知道怎么翻译这个概念):factory、service、value、constant、provider,这里我们只关心constant。官方文档描述constant是用来为配置阶段(config phase)和运行阶段(run phase)提供没有依赖的简单对象,也就是说我们在constant里面定义的对象或基本类型可以在run和config里面注入:
angular.module('adminApp') .constant('myObj',{ name: 'angular' }) .constant('myStr','hello') .config(function(myObj) { console.log(myObj.name) // angular }) .run(function(myStr) { console.log(myStr) //hello })
那什么是运行和配置阶段呢?官方文档这样说:
A module is a collection of configuration and run blocks which get applied to the application during the bootstrap process. In its simplest form the module consists of a collection of two kinds of blocks:
1.Configuration blocks - get executed during the provider registrations and configuration phase. Only providers and constants can be injected into configuration blocks. This is to prevent accidental instantiation of services before they have been fully configured.
2.Run blocks - get executed after the injector is created and are used to kickstart the application. Only instances and constants can be injected into run blocks. This is to prevent further system configuration during application run time.
上面介绍angular的模块实际上是配置块和运行块的集合,这些blocks是在angular的启动(bootstrap)过程中被添加进模块的。Configuration blocks和Run blocks可以理解为队列,一个模块可以写多个.run和.config,最后被依次添加进相应的blocks中。config只能注入provider 和constant recipe,run只能注入实例(不是providers)和constant recipe。但我测试value recipe是可以注入到run的,好吧我承认angular文档真的不好理解。
总之我觉得就一句话概况就是:constant可以理解为是angular用来为模块提供可注入的所有模块共享的常量(无依赖的简单对象或类型),并且在config和run阶段之前定义好的。 (仅仅是个人理解)
执行顺序
上面说了constant应该是在run之前被执行,可这只是程序运行的表象,为什么会这样呢,于是就搜索了一下angular的启动过程,其中这篇对启动过程的[源码分析](http://liuwanlin.info/angular...),给了我一些启发。
里面介绍了setupModuleLoader方法,该函数返回了一系列的API,用于Angular组织模块,注册指令、服务、控制器。
能够看到刚才前面介绍的configBlocks和runBlocks,这里要说明的是constant使用了unshift,将constant插入到队列的首部,这也就保证了constant在配置、运行之前能够在其他所有块中被注入。
再看下loadModules方法,这个方法用于加载模块,即返回需要运行的块,之前提到的constant和provider其实就是被加入了invokequeue之中,这只是注册并没有执行,在这个函数中调用runInovequeue才真正执行生成实例,也可以看出config是在run之前运行的:
解决问题
上面大致解释了一下constant先于run被执行的原因,这也是文章最开始写的代码没有按照预期执行的原因。知道了原因又知道.run里面可以注入已经定义的constant,那么我们就知道只要稍微改一下代码就可以得到想要的结果:
'use strict'; var domain = 'http://localhost:1337'; //开发环境下的服务端地址, var MY_DOMAIN = 'http://production.com'; // 生成环境的网站地址 angular.module('adminApp').run(function($location,myConfig) { if ($location.host() !== 'localhost') { myConfig.domain = MY_DOMAIN; myConfig.api = myConfig.domain + '/admin'; //这里不要期望myConfig里面的domain会跟随者domain变量的变化而变化,对象一旦建立,它的属性值就是固定的了,想修改只能通过对象访问属性修改。 } }) .constant('myConfig',//项目中请求服务端异步获取数据的接口 }
参考文档:
angular providers
angular modular
dependency injection
AngularJS中几种Providers(Factory,Service,Provider)的区别
AngularJS源码阅读1:启动过程