<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>seem (seemtim)</title>
    <link>https://ruby-china.org/seem</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>使用 AngularJS 从零构建大型应用</title>
      <description>&lt;ul&gt;
&lt;li&gt;0、导言&lt;/li&gt;
&lt;li&gt;1、准备工作&lt;/li&gt;
&lt;li&gt;2、构建框架&lt;/li&gt;
&lt;li&gt;3、丰富你的 directives&lt;/li&gt;
&lt;li&gt;4、公用的 services&lt;/li&gt;
&lt;li&gt;5、用 controllers 组织业务&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="导言"&gt;导言&lt;/h2&gt;
&lt;p&gt;纵览线上各种 AngularJS 教程，大部分都是基础与一些技巧分析。
如果你已经能运行你的 ng-app，但又找不到实际案例可以参考。那么本文应该对您有所帮助。
本文将以电商产品：&lt;a href="https://youhaosuda.com/" rel="nofollow" target="_blank" title=""&gt;友好速搭&lt;/a&gt; 其中的 &lt;a href="https://youhaosuda.com/account/login" rel="nofollow" target="_blank" title=""&gt;店铺后台&lt;/a&gt; 作为的实际案例，裸奔展示如何从零构建 &lt;del&gt;“自以为大型的”&lt;/del&gt; AngularJS 应用。
应用基于 AngularJS 1.2.24 版本。&lt;/p&gt;
&lt;h2 id="准备工作"&gt;准备工作&lt;/h2&gt;
&lt;p&gt;1、我们使用了以类型优先的目录结构。
├── js
│&amp;nbsp;&amp;nbsp; ├── app.js
│&amp;nbsp;&amp;nbsp; ├── directives.js
│&amp;nbsp;&amp;nbsp; ├── services.js
│&amp;nbsp;&amp;nbsp; ├── controllers
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── BaseController.js // controller 基类
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── Customer.js       // 顾客管理
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── Product.js        // 商品管理
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── Order.js          // 订单管理
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── Domain.js         // 域名管理
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── Payment.js        // 收款方式
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; └── ...               // 其他各种 controller
│&amp;nbsp;&amp;nbsp; ├── directives
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── ysBtn.js       // 按钮组件
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── ysCalendar.js  // 日历组件
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── ysImgeditor.js // 图片编辑器
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── ysPopWindow.js // 模态弹窗组件
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; └── ...            // 其他各种组件
│&amp;nbsp;&amp;nbsp; └── services
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── Graphic.js // 图片文件处理 service
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── Popup.js   // 弹层 service
│&amp;nbsp;&amp;nbsp; │&amp;nbsp; &amp;nbsp;├── Uri.js     // 与 Uri 相关操作 service ($http 等 ajax 操作封装于此)
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;  └── ...        // 其他各种 service
│&amp;nbsp;&amp;nbsp; ├── config
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── Navigation.js    // 主导航配置
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── Route.js         // 路由配置
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── RouteProvider.js // 配置$routeProvider
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── OnRootScope.js   // 为$rootScope 配置$onRootScope 方法
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── SceDelegate.js   // 配置$sceDelegateProvider
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── Uri.js           // 后端服务 API 配置
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; └── ...              // 其他各种 config
├── lib
│&amp;nbsp;&amp;nbsp; ├── bower_components
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── angular
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── angular.min.js
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; └── ...
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── angular-route
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── angular-sanitize
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; ├── angular-route
│&amp;nbsp;&amp;nbsp; │&amp;nbsp;&amp;nbsp; └── ... // 其他 bower 管理的 lib
│&amp;nbsp;&amp;nbsp; ├── URI.min.js
│&amp;nbsp;&amp;nbsp; ├── class.js
│&amp;nbsp;&amp;nbsp; └──  ... // 其他手动管理的 lib
├── img
├── css
└── html  // controllers 对应的 views 的模板&lt;/p&gt;

&lt;p&gt;2、使用 less 作为 css 的预处理器。
3、使用 bower 管理依赖的 JS 库。
4、使用 grunt 作为项目打包工具。
5、使用 fiddler4 作为 http 请求调试工具。
6、为了可以使用庞大的 jquery 插件库，我们也引入了 jquery。
7、controllers、directives、services 等部分的设计参考自： &lt;a href="http://trochette.github.io/Angular-Design-Patterns-Best-Practices/" rel="nofollow" target="_blank"&gt;http://trochette.github.io/Angular-Design-Patterns-Best-Practices/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;准备了半天，访问如下的 index 页面，angular 的应用是时候跑起来了。
&lt;img src="https://asset.ibanquan.com/image/54d0d7d0aaba8c703b000002/24.png?v=1422972880" title="" alt="应用代码展示"&gt;&lt;/p&gt;

&lt;p&gt;一个实际的案例看起来总是要比教程复杂些。下面是项目启动后&lt;a href="https://youhaosuda.com/" rel="nofollow" target="_blank" title=""&gt;友好速搭&lt;/a&gt;店铺后台的概貌。
&lt;img src="https://asset.ibanquan.com/image/54d07e47aaba8c3e13000008/2.png?v=1422949959" title="" alt="店铺后台"&gt;&lt;/p&gt;

&lt;p&gt;什么？不是说好的从零开始构建吗？怎么就跑起来了？
好吧，请把上面这个预览图当做设计稿。我们开始构建框架。&lt;/p&gt;
&lt;h2 id="构建框架"&gt;构建框架&lt;/h2&gt;
&lt;p&gt;根据设计，从结构上将页面划分为&lt;/p&gt;

&lt;p&gt;顶部 &lt;/p&gt;
导航 
内容 
导航的点击会改变浏览器当前的 url，内容区域渲染对应模块的内容。

&lt;p&gt;下面开始配置 angularJS，来达到上面的目标。
在 js/app.js 配置 YeeshopManager 这个 module，并将它的引用赋值到全局变量 YeeshopManagerModule，方便后续继续对其进行配置。
&lt;img src="https://asset.ibanquan.com/image/54d07e478757db4103000005/3.png?v=1422949959" title="" alt="配置module"&gt;&lt;/p&gt;

&lt;p&gt;为了方便管理众多 directives 与 services。我们分别创建了
js/directives.js  集合所有 directives 的 module - YeeshopManager.directives
&lt;img src="https://asset.ibanquan.com/image/54d07e484812f23e1a000002/4.png?v=1422949960" title="" alt="配置directives"&gt;&lt;/p&gt;

&lt;p&gt;js/services.js  集合所有 services 的 module - YeeshopManager.services
&lt;img src="https://asset.ibanquan.com/image/54d07e498757db41c8000002/5.png?v=1422949961" title="" alt="配置services"&gt;&lt;/p&gt;

&lt;p&gt;接下来，配置应用为不同路径的请求调用对应的 controller 与模板
在 config/Route.js 中先定义好的规则
&lt;img src="https://asset.ibanquan.com/image/54d07e4a4812f23e1a000005/6.png?v=1422949962" title="" alt="配置Route.js"&gt;&lt;/p&gt;

&lt;p&gt;在 config/RouteProvider.js 中配置 $routeProvider
&lt;img src="https://asset.ibanquan.com/image/54d07e4b8757db4103000008/7.png?v=1422949963" title="" alt="配置$routeProvider"&gt;&lt;/p&gt;

&lt;p&gt;当我们使用 #/guide 路径访问&lt;a href="https://youhaosuda.com/" rel="nofollow" target="_blank" title=""&gt;友好速搭&lt;/a&gt;店铺后台的新手引导页面的时候，
&lt;img src="https://asset.ibanquan.com/image/54d07e4baaba8c3e2c000003/8.png?v=1422949963" title="" alt="ngview"&gt;
ngRoute 将为这个 index 文件上带有 ng-view 的节点渲染对应的模板 guide.html 并且运行对应的 GuideController。&lt;/p&gt;

&lt;p&gt;结构上划分的三个区域对应的 controller 的关系
顶部 -&amp;gt; TopbarController
导航 -&amp;gt; ysNavigationController
内容 -&amp;gt; 由 ngRoute 动态的根据当前 url 和配置，加载对应模块的内容。
至此，大体的结构已经完成。&lt;/p&gt;

&lt;p&gt;每个 Controller 创建的$scope 都能独立很好的运行，但有时$scope 之间也需要通讯。这时，我们需要为$rootScope 配置一个方法，来完成这个工作。&lt;/p&gt;

&lt;p&gt;在 config/OnRootScope.js 中配置 $rootScope
&lt;img src="https://asset.ibanquan.com/image/54d07e4c4812f23e1a000008/9.png?v=1422949964" title="" alt="配置rootScope"&gt;
使用$provide 的 decorator 方法在$rootScope 注册的时候，注入一个 $onRootScope 的方法。&lt;/p&gt;

&lt;p&gt;需要被通知的$scope 调用 $onRootScope 来监听事件'notification',
发出通知的 controllers, directives，或 services 中，只需要注入 $rootScope 服务。就可以很方便的进行通知。
&lt;img src="https://asset.ibanquan.com/image/54d07e4d4812f23e0a000002/10.png?v=1422949965" title="" alt="收到通知"&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youhaosuda.com/" rel="nofollow" target="_blank" title=""&gt;友好速搭&lt;/a&gt; 作为一款 SAAS 产品，支持商家自行绑定域名，让用户更好的记住您的域名和品牌。&lt;/p&gt;

&lt;p&gt;angularJS 构建的应用程序，需要将静态资源部署在 CDN 上，来保证用户访问的快速流畅。
那么，如果我绑定了 &lt;a href="http://myshop.com/" rel="nofollow" target="_blank"&gt;http://myshop.com/&lt;/a&gt; ，店铺后台的地址就会是 &lt;a href="http://myshop.com/admin" rel="nofollow" target="_blank"&gt;http://myshop.com/admin&lt;/a&gt;，
这时 CDN 静态资源的地址会是形如 &lt;a href="http://cdn.com/js/app.js" rel="nofollow" target="_blank"&gt;http://cdn.com/js/app.js&lt;/a&gt;。
当应用的 host &lt;a href="http://myshop.com/" rel="nofollow" target="_blank"&gt;http://myshop.com/&lt;/a&gt; 和引用资源的 host &lt;a href="http://cdn.com/" rel="nofollow" target="_blank"&gt;http://cdn.com/&lt;/a&gt; 不一致时，
angular 会告诉你 Error: [$sce:insecurl] ,资源因安全策略而加载失败。&lt;/p&gt;

&lt;p&gt;配置资源白名单$sceDelegateProvider.resourceUrlWhitelist，允许 angular 跨域请求指定的 url 的资源。
&lt;img src="https://asset.ibanquan.com/image/54d07e4e8757db410300000e/11.png?v=1422949966" title="" alt="白名单"&gt;&lt;/p&gt;

&lt;p&gt;至此，我们完成对 YeeshopManagerModule 的配置。&lt;/p&gt;
&lt;h2 id="丰富你的directives"&gt;丰富你的 directives&lt;/h2&gt;
&lt;p&gt;框架构建完成了，参考第二节中的设计稿，接下来我们需要为系统添加各种 UI 组件。比如图标、按钮、下拉菜单、弹窗。依照 angularJS 创始人 Misko Hevery 设计的初衷：
“构建 UI 应该是声明式的。”
那么，我们也设计自己的规则，来声明 UI 组件：&lt;/p&gt;

&lt;p&gt;图标 
按钮 
下拉菜单 
弹窗 
……&lt;/p&gt;

&lt;p&gt;完成了以上的思 (yi) 考 (yin)，我们着手来声明图标组件。
&lt;img src="https://asset.ibanquan.com/image/54d0d0e2aaba8c6c81000002/12.png?v=1422971106" title="" alt="声明图标组件"&gt;&lt;/p&gt;

&lt;p&gt;声明完成后，我们在模板里面进行调用。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ys-ico type="trade"&amp;gt;&amp;lt;/ys-ico&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;经过 angularJS 的编译之后输出的节点与实际效果。
&lt;img src="https://asset.ibanquan.com/image/54d0d0e38757db7158000002/13.png?v=1422971107" title="" alt="图标效果"&gt;&lt;/p&gt;

&lt;p&gt;对于这个通用的自定义标签，我们习惯用“组件”来称呼他。在 angularJS 中，称为一个 directive。官方文档中的定义请&lt;a href="https://docs.angularjs.org/guide/directive" rel="nofollow" target="_blank" title=""&gt;越过山丘 虽然已白了头~~&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;使用强大的 directive。我们可以将系统中需要复用的所有组件、甚至一个复用的行为全部抽象出来。当你需要使用的时候，你只是需要声明他。
例如，一个旋转的图标：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ys-ico keepRotating type="trade"&amp;gt;&amp;lt;/ys-ico&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://youhaosuda.com/" rel="nofollow" target="_blank" title=""&gt;友好速搭&lt;/a&gt;项目早期规划的 directive
&lt;img src="https://asset.ibanquan.com/image/54d0d0e58757db7158000005/15.png?v=1422971109" title="" alt="展示"&gt;&lt;/p&gt;

&lt;p&gt;按照需求增加了水印编辑器 directive
&lt;img src="https://asset.ibanquan.com/image/54d0d0e5aaba8c6c81000005/16.png?v=1422971110" title="" alt="水印编辑器"&gt;&lt;/p&gt;

&lt;p&gt;方便商户编辑图片，增加了图片编辑器 directive
&lt;img src="https://asset.ibanquan.com/image/54d0d0e68757db7158000008/17.png?v=1422971110" title="" alt="图片编辑器"&gt;&lt;/p&gt;

&lt;p&gt;这时候，前端工程师的问题就来了：“还有多少 directives？”
&lt;img src="https://asset.ibanquan.com/image/54d0d0e74812f26d5f000005/18.png?v=1422971111" title="" alt="暴走"&gt;
答：纵观整个项目的生命周期，所有的 directive 不可能在初始便全部设计并构建好。请在项目进行的过程中，按需的增加或修改、丰富你的 directive，不断提升你构建 view 的效率。&lt;/p&gt;

&lt;p&gt;依照上文构建与调用 directive 的经验。在实际项目中，我们在模块中需要 10 个 ico，那么我们便在其模板中调用十次的标签。&lt;/p&gt;

&lt;p&gt;那么前端工程师的问题又来了：“如果模块的交互需要弹窗，难不成我要先算好有多少个？然后全部先在模块里面声明，并使用 ng-show="false"全部隐藏起来？”
&lt;img src="https://asset.ibanquan.com/image/54d0d0e74812f26d5f000005/18.png?v=1422971111" title="" alt="暴走"&gt;&lt;/p&gt;

&lt;p&gt;Don't worry.不要被 &lt;a href="http://stackoverflow.com/questions/14994391/thinking-in-angularjs-if-i-have-a-jquery-background" rel="nofollow" target="_blank" title=""&gt;Thinking-in-AngularJs&lt;/a&gt; 限制了您的思维。换一个姿势 Thinking in jquery。
当我们需要一个弹窗的时候，按照往常的做法，便是在你页面架构预设好的位置（例如页面底部），插入弹窗的 dom 结构。
没错，弹窗还是这样实现，不同的是。我们插入的是弹窗的 directive。
类似以下（简化代码未测试，仅为示例）：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$('body').append($compile('&amp;lt;ys-popwindow data="PopupModalData"&amp;gt;#{content}&amp;lt;/ys-popwindow&amp;gt;')($scope));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们通过动态插入 directive 解决了上面这个问题。但我总不能每个模块都写弹窗 dom 的插入吧。
这个时候，我们需要 service。&lt;/p&gt;
&lt;h2 id="公用的services"&gt;公用的 services&lt;/h2&gt;
&lt;p&gt;各种 services 为&lt;a href="https://youhaosuda.com/" rel="nofollow" target="_blank" title=""&gt;友好速搭&lt;/a&gt;店铺后台提供了统一的 api 接口调用，图像处理，弹层处理，实用工具等。
通过上面的目录，我们可以看到 services 文件夹下，有定义$Popup 的 Popup.js。
（简化代码未测试，仅为示例）
&lt;img src="https://asset.ibanquan.com/image/54d0d0e84812f26c14000002/19.png?v=1422971112" title="" alt="service代码"&gt;&lt;/p&gt;

&lt;p&gt;注入到您所需的模块后，愉快的调用吧！&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$Popup.modal({text : '弹窗标题'});
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="用controllers组织业务"&gt;用 controllers 组织业务&lt;/h2&gt;
&lt;p&gt;一切就绪，开始堆业务代码！
&lt;img src="https://asset.ibanquan.com/image/54d0d0eaaaba8c6ceb000002/21.png?v=1422971114" title="" alt="用controllers图片"&gt;
&lt;img src="https://asset.ibanquan.com/image/54d0d641aaba8c70b6000002/22.png?v=1422972481" title="" alt="用controllers代码"&gt;&lt;/p&gt;

&lt;p&gt;裸奔展示到此结束~有劳看官，若有任何错漏~烦请指正~&lt;/p&gt;</description>
      <author>seem</author>
      <pubDate>Wed, 04 Feb 2015 10:52:15 +0800</pubDate>
      <link>https://ruby-china.org/topics/24111</link>
      <guid>https://ruby-china.org/topics/24111</guid>
    </item>
  </channel>
</rss>
