初次使用angularjs做项目,但是发现angularjs在路由配置后会一次性加载所有依赖文件,这对于大一点的项目来说是不可接受的,使用requirejs也不能阻止路由配置处angularjs自己去加载文件。
然后我找到了angularAMD,在angularjs和angular-ui-router的环境下是可以实现template、controller的动态加载(也就是进入哪个页面就加载改页面相关内容)。 但是由于不想自己折腾太多的UI,我使用了很火的框架ionic,他对路由模块可能坐了自己的封装,使用了<ion-nav-view>而不是<ui-view>,然后导致控制器能按需加载,模板却一次性加载。 我谷歌了这些内容,发现相关话题非常少,难道是我理解有误?angular不需要动态加载模块吗,但是对于有很多个页面的项目,这肯定是不合理的,譬如我们要做的是hybird app,页面量很大。不知道大家在使用angular时都是怎么对待这个需求的
按投票排序
按时间排序
10 个回答楼上的几个答案我都看了一下 1、 把单页面应用做成应用中所有页面都加载并且重新初始化Angularjs框架的行为在我个人的角度是无法接受的,不论是从交互体验的角度上还是从技术追求 的角度。当然,如果从公司的角度,加上其他因素的影响,最后根据各方面实际情况(开发周期、团队技术储备、产品经理交互需求等等)的妥协与折衷,这样做其 实也是可以的。 2、使用oclazyload的方案我调研了一下,是可行的,但是想说这个方案有些缺点,比如每次动态加载需要的脚本、模 版资源会有很多不必要的网络开销,路由定义比较复杂(多了一些配置项,其实不能算复杂,而是繁琐),对于大型复杂业务应用,路由众多,耗费的精力不可忽 视。在实际做对外开放的产品时,我们一般会把使用requirejs管理依赖关系的代码打包压缩,加版本号,同时根据项目情况决定要不要按照业务模块做拆 分打包&异步按需加载。 3、不知道楼主开发的产品是不是移动端的单页面应用,不知道楼主的应用是否业务复杂以及脚本文件的大小在 什么量级,所以我下面讲解的技术方案可能在某些地方并不适合楼主的场景,但是原理是相通的,楼主可以参考一下然后看看是否可以解决你的项目中面临的问题。 如果需要,可以随时找我一起讨论你所面临的问题以及采用哪种方式解决最好。 4、占位,晚上有空更新。 ===========2015-06-08更新============ 最近一直比较忙,今天抽空更新一部分。 看了楼主的评论回复,移动端和PC浏览器端,对于Angular本身而言没有区别,所以我说的这些应该也适用于移动端Angularjs应用。 首先说一下Angularjs的启动原理,就知道为什么很少有人做Angular代码的异步加载了。 好像知乎不支持markdown格式,写起来好难受,只能拿 “等号”玩了 ========================Angular框架启动原理分析============================== 我们现在一般配合Requirejs 做代码依赖管理的情况下,都是在RequireJs的入口文件中,执行以下代码来启动Angular框架:
在上面的bootstrap方法里面,我们找到了一行非常关键的代码,调用了createInjector方法,接下来我们看下createInjector方法里面干了什么事情(不重要的代码统统省略号)。
然后再来看 loadModules里面的关键语句(loadModules函数其实就定义在了 createInjector函数里面)
代码扒到这里就差不多了。如果你看明白了整个angular.bootstrap的时候的来龙去脉,就会 发现,假设我当前页面已经加载了相关资源,Angular框架已经运行起来(执行了angualr.bootstrap方法),那么我后续通过按需加载引 入的javascript脚本文件中的那些 xxxModule.controller("xxx",['yyy',function(yyy){}])、 xxxModule.directive("zzz",['www',function(www){}]) 代 码都虽然会在requirejs引入的时候被执行一遍,但是执行的结果仅仅是把这些controller和directive和factory等等函数放 在了一个invokeLater的数组里面,我们的前端路由激活的时候,去通过angular寻找对应路由(视图)的controller的时候,发现根 本没有这个controller,原因就是在这里:Angular框架启动以后(执行了bootstrap方法之后),它读取controller构造函 数、directive构造函数等的地方和我们执行 controller方法、directive方法所注册进去的代码不是一个地方(或者说不是缓存在一个变量里面),所以,在按需加载的情况下,虽然我们 的代码看起来执行了,但是真正Angular的部分却并没有真正的执行,而是仅仅被放在一个地方等待着“invokeLater”(其实再等多久也没用, 因为不能再执行angular.bootstrap方法了)。 =======================异步按需加载方案分析====================== AngularJs框架启动原理分析完以后,就可以分析异步按需加载方案了。首先我们来看一下我们做异步按需加载方案需要解决哪些问题: 1、angularjs框架启动后,调用 各种api无法真正注册相关构造函数的问题 2、路由激活时,加载当前路由需要的脚本资源(考虑防止重复加载、考虑最大化利用客户端缓存等等问题) 3、当前单页面应用模块划分和打包(当上面两个技术问题解决以后,就要考虑这个偏向业务的问题了) 我们先来看第一个问题怎么解决: 1、解决异步按需加载代码后Angular原生代码无法真正注册各种构造函数的问题 经 过上面的加载原理分析以后,我们就知道该怎么办了:把angular.module("xxx")的实例的factory、controller、 directive、value、filter 等等方法都“变换”掉,让我们的代码执行这些方法的时候,直接把我们的函数放在运行期的对应的缓存的对象里面,这样一来异步加载的代码就会在执行的时候真 正被注册到Angular运行时可以读取的地方(Angular运行时具体缓存各个构造函数的地方自己扒源码吧,懒得贴出来了),这样在路由激活的时候, 就可以找到对应的controller,然后执行。 这个办法可以参考下面的代码(可以参考前面同学回答的oclazyload这个框架里面的代码,但是这个框架作了很多其他的事情,导致最核心的思想没有很清晰的体现,不过也可以读一读,挺有意思):
解决这个问题以后,我们来看第2个按路由加载代码的问题如何解决:(精力有限,暂时更新到这里,预计本周 还会更新一次,先给个提示:按照路由去加载所需的代码方案有很多种,常见的是就在定义路由的时候定义一个 resolve,在里面做资源的加载,但是这种方法需要在路由定义的敌方写比较多的东西,不是很喜欢这种方式。题主你的手机端应用建议你做好合理的模块或 者说页面的划分,我初步的建议是你每个页面内部的资源全部都打包成一个脚本包括视图模板也打包进去,最终看文件的大小是不是在移动端单次web请求允许的 范围之内,然后angularjs相关的和公共代码打包成一个,应用启动时加载这个公共的脚本,切换到其他页面或者路由的时候公共的资源脚本已经运行起 来,不需要再加载,而只需加载对应页面或者路由的脚本就可以了。初步可以这样,后续再详细更新说明具体的方案和原因。) ===========2015-06-21更新============ 抱歉最近非常忙,更新时间略长 之 前的内容讲解了Angularjs支持动态加载脚本相关的局限性(动态引入到浏览器里面的代码中,没有经过处理的话,所有的 xxxModule.directive()、xxxModule.controller() 执行过后并没有真正立刻被实例化,而是放在了一个缓存中等待app bootstrap的时候去执行,可是由于这些代码是动态引入的,在它们引入之前,app 已经bootstrap过了。),现在开始讲如何配合 requirejs 做相关的处理来支持动态加载: 先看下有几个问题需要解决: 1、确定按需加载的静态资源引入页面的时机。 2、按需加载的静态资源打包方式。 3、其他一些问题的整体考虑。 依次回答上面三个问题,加上之前更新的内容,然后关于AngularJs动态加载脚本的问题和相关的原理基本上可以说明白了。 1: 毫无疑问,在用户浏览某个“页面”的时候,应用会激活对应的路由,然后去加载对应的资源脚本。在我们实际开发过程中,一个业务模块下面会有n个相关的子页 面来共同服务于某个业务需求,这种情况下,我们会给这n个子页面定义一个最基本的父路由,在进入该父路由的时候,去判断当前路由相关的资源脚本是否加载, 如果没有加载的话,则利用requirejs去 获取资源脚本文件,等拿到并执行以后,再执行相关的业务逻辑(比如激活某个页面的路由、实例化对应的controller函数);如果已经加载过了,则和 正常的路由解析过程一致,直接依次执行其子路由直到用户想要访问的页面对应的路由激活。所以说,在不引入其他第三方库的情况下(或者已经引入了某个库,不 再适合引入其他库的情况下),我们可能需要在路由定义的地方做手脚,加入自己的静态资源加载服务。 2:由于每个业务模块有n个页面,所以 一定有大量的其他代码一起配合完成相关的业务逻辑,比如有很多directive,有很多filter,这些脚本有的是我们单页面应用里面其他业务模块用 不到的,有的是其他业务模块可以共用的,所以我们在开发过程中,除了以业务模块为维度统一划分开发文件结构以外,还会多出一个 common 或者 base 这样类型的文件夹,专门负责放置应用内部各个业务模块都使用的公共代码或者公共组件等等。这种情况下,我们的项目中,app文件夹下面的一级文件夹就是 common + n 个业务模块文件夹。在这种项目文件结构下,我们会把 common 和所有用到的第三方组件资源脚本打包、压缩在一个文件中,在用户打开浏览器加载整个单页面应用的时候最先加载并执行,其他n个业务模块,每一个文件夹下的 脚本资源单独打包压缩成一个js文件,每个业务模块文件夹下需要一个种子文件去引用依赖当前项目文件夹中的其他资源脚本,这样在使用r.js做打包的时 候,就可以以这个文件为种子文件将其依赖的脚本打包到一个文件里面。在打包业务模块的脚本的时候,一定会有某些脚本引用了 common 里的文件或者第三方组件中的文件,所以需要在打包配置项中,做一些配置,告诉打包工具不把声明的这些文件打包到当前文件中。具体的配置项可以参考 r.js 中的 path配置项,其中某个子项如果使用 xxx:“empty:” 这样的写法的话,则该声明意为不把 xxx 打包到当前打包的文件中。可以参考r.js的示例配置文件:https://github.com/jrburke/r.js/blob/master/build/example.build.js#L39 这样,打包部分的方式和方法也说明了,具体相关配置不太熟悉的同学可以看requirejs的官方文档或者直接看 r.js 的github项目r.js/example.build.js at master · jrburke/r.js · GitHub 大部分同学应该是使用 grunt-requirejs做打包的,其实和r.js是一样的,所以看上面的链接地址的代码和注释就可以了。 3: 在做这样的技术改造或者技术升级的时候,需要考虑很多很多因素,而不是仅仅去找几个相同的例子然后把自己的代码改成那样就好了,而是需要考虑已有代码做升 级的时候,如何才能改动最小,对已有代码侵入最少,还要考虑到对开发人员的友好性,考虑到测试和线上调试的方便程度,还要考虑到开发团队内部其他成员的接 受程度等等。以题主目前的处境来看,其实你的业务代码的按需加载已经由你采用的框架帮你实现,现在你的困惑主要集中在模板部分不能按需加载,所以,我们可 以把目光直接聚焦到如何解决 模板不能按需加载的问题上,而不用再去考虑如何做业务脚本的按需加载的整体实现。所以,针对题主你的问题,我建议如下(假设你的HTML模板都已经打包到 了一个文件中):1、使用 grunt-html2js 打包转换HTML模板为js文件 2、打包后的模板缓存文件和业务模块代码打包到一起 3、业务脚本动态引入的时候,自动引入HTML模板的js文件,这样就实现了HTML模板的动态引入。原理:Angularjs 的指令、State 等等在处理 templateUrl 的时候,会去服务端按照templateUrl的地址发起请求,但是在请求真正发起之前会使用这个Url作为key去 $templateCache 中查找有没有该key对应的缓存,如果有的话,则不会真的发起请求,而是直接采用缓存中的HTML字符串。所以我们的 grunt-html2js会生成js文件,这个文件里面,每个HTML模板都会以某个Url(这个Url取决于自己的配置)作为自己的key,然后把模 板内容转义为字符串,存入$templateCache中。所以,题主,你可以按照这个思路重新调整一下模板相关的部分,多做debug,观察每个视图激 活的过程,看下templateUrl的处理,就知道该怎么做了。 http://www.zhihu.com/question/30624377 |
|
来自: someoneknow > 《ionic》