http://www.html-js.com/article/2126javascript
AngularJS之因此吸引了不少开发者的关注,很大一部分缘由是由于它为你如何架构你的应用提供了一种选择。一般来讲,不少的选择并非什么好事,由于以为多数开发者并不但愿你把一个“正确的”应用架构模式强加给他们。html
就JavaScript来讲,存在的一种状况是有至关数量的一部分人都等待着有人 – 随即是谁都好 – 为他们提供一种关于由大型或者人员变化频繁的团队建立的企业级别应用的扩展和维护。总而言之,咱们不只仅须要一个基础概念,咱们须要的是一个构建计划。java
angularjs提供的蓝图很是的基础并且简单 – javascript没有一个模块系统,所以AngularJS为你提供了一个。AngularJS确保了当你的应用在运行时,全部的代码已经加载完毕,而且可用。AngularJS主要经过依赖注入完成这项工做。jquery
如今假设咱们有一个很是简单的应用。其中只有一个子视图。它有一个对应的控制器。这个控制器反过来又为这个子视图注入一个服务来提供数据接入。当应用运行起来时,AngularJS可以确保全部的“字符串”表明的都是须要注入的实际模型。angularjs
代码以下所示:bootstrap
// 使用Angular Kendo UI来编写UI组件和进行数据层抽象 (function () { var app = angular.module('app', ['ngRoute']); // routeProvider在这里被注入(须要require Angular.Route) app.config(['$routeProvider', function ($routeProvider) { $routeProvider.when('/home', { templateUrl: 'partials/home.html', controller: 'HomeController' }) .otherwise( { redirectTo: '/home' }) }]); app.controller('HomeController', ['$scope', 'productsDataSource', function($scope, $productsDataSource) { $scope.title = 'Home'; $scope.productsDataSource = $productsDataSource; $scope.listViewTemplate = '<p>{{ ShipCity }}</p>'; }]); app.factory('productsDataSource', function () { new kendo.data.DataSource({ type: 'odata', transport: { read: 'http://demos.telerik.com/kendo-ui/service/Northwind.svc/Orders' }, pageSize: 20, serverPaging: true }); }); }());
上面的代码作了很多事情:浏览器
在AngularJS中,一个很是棒的设计就是它并不关心上面这些事情声明的顺序。服务器
只要第一个app模块存在,你就能够以任何顺序建立任何后续的工厂服务,控制器,路由等等东西。AngularJS在后续会帮助你检查并载入依赖项,即使你在一个依赖模块声明以前就指明了依赖项。若是你写过一段时间的JavaScript,你必定可以明白它为你解决了一个多么大的问题。架构
目前来看的话咱们的代码整洁度还能够使人接受。而后,上面这个应用的代码太罗嗦了,并且它基本上来讲什么也没作。你可以想象一个真实世界中的应用代码看起来是什么样子吗?app
下一个逻辑步骤将会把全部的控制器、服务、以及全部可以分开的东西分隔到不一样的文件中。这是一种物理项目架构,它可让每一个文件中的代码整洁一下。摆在咱们面前的选择有两个 – Browserify 和 RequireJS。
“app”对象是Angular应用正常运行的关键。在通常的使用中,Angular会假设在应用“启动”时文档已经加载完毕。根据AngularJS的文档,Angular会在DOMContentLoaded事件发生时完成“自动初始化”。
换句话说:在document.readState被设置为complete时,angular.js脚本会被调用。在实际运行中,当DOM已经加载完成以后,Angular都会进行的步骤以下:
以上的步骤就是通常状况下Angular会作的事情。只要全部的脚本在DOMContentLoaded事件以前加载完毕(能够将它看做document.ready),一切都能正常运行。这使得Browserify能够很轻易的将Angular应用分红不一样的物理文件。
针对上面的例子,咱们能够讲文件分隔为下面的结构:
app
partials
home.html controllers homeController.js services productsDataSource.js app.js
Browserify容许在浏览器中使用CommonJS模块。这意味着每个模块须要将它本身经过exports暴露给外界,以便其余模块可以require它。
homeController.js文件以下所示:
// controllers/homeController.js module.exports = function() { return function ($scope, $productsDataSource) { $scope.title = 'Home'; $scope.productsDataSource = $productsDataSource; $scope.listViewTemplate = '<p>#: ShipCity #</p>'; }; };
productsDataSource.js工厂服务的代码也相似:
// services/productsDataSource.js
module.exports = function () { // the productsDataSource 服务被注入到控制器中 return new kendo.data.DataSource({ type: 'odata', transport: { read: 'http://demos.telerik.com/kendo-ui/service/Northwind.svc/Orders' }, pageSize: 20, serverPaging: true }); };
而app.js文件就是全部魔法发生的地方:
// app.js // require 全部的核心库 require('../vendor/jquery/jquery.min'); require('../vendor/angular/angular.min'); require('../vendor/angular-route/angular-route.min'); require('../vendor/kendo-ui-core/js/kendo.ui.core.min'); require('../vendor/angular-kendo/angular-kendo'); // 拉出咱们全部咱们须要的模块(控制器,服务,等等) var homeController = require('./controllers/homeController'); var productsDataSource = require('./services/productsDataSource'); // 建立模块 var app = angular.module('app', [ 'ngRoute', 'kendo.directives' ]); // 配置路由 app.config(['$routeProvider', function($routeProvider) { $routeProvider .when('/home', { templateUrl: 'partials/home.html', controller: 'HomeController' }) .otherwise( { redirectTo: '/home' }); }]); // 建立工厂服务 app.factory('productsDataSource', productsDataSource); // 建立控制器 app.controller('HomeController', ['$scope', 'productsDataSource', homeController]);
接着,使用下面的命令行便可:
$> watchify js/app/**/*.js -o build/main.js
Watchify是一个小巧的功能插件,它可以监视文件夹并”browserify”你全部的代码。
对于Browserify来讲,你能够在app.js文件中直接require全部的库文件。除此以外,Browserify还能正确处理好你require这些文件的顺序。多么难以想象的事情!
可是另外一个事实是你依然须要手动的建立控制器、工厂服务以及那些不在app.js文件中的东西。我须要在某个模块中定义这些东西而后将它们在app.js中进行合并。总的来讲,我全部的Angular代码事实上都位于app.js文件中,同时全部的文件都仅仅只是JavaScript。固然,这是JavaScript,因此没有什么可抱怨的。
可是总的来讲,Angular和Browserify协同工做的很棒。
接下来,咱们要讨论一件不那么好的事情:在AngularJS中使用RequireJS。
RequireJS是一个好东西,可是若是和AngularJS一块儿使用,就会出现问题。核心问题在于Angular须要在DOM彻底加载以后开始运行,它并不想要玩异步游戏。因为RequireJS中一切都是异步的(AMD即异步模块定义),你很难将两者很好的结合起来。
因为脚本载入是异步的,全部的ng-app属性如今彻底不可用了。你不可使用它来指明Angular应用。
另外一个讨厌的事情是app模块。为了传递它你必须使用疯狂的环形依赖。
下面咱们来使用RequireJS进行文件架构:
app partials home.html controllers index.js module.js homeController.js services index.js modules.js productsDataSource.js app.js main.js routes.js
咱们先从main.js这个文件开始,它在其中配置了全部的库文件信息,而且对于不遵循AMD标准的模块进行了shim设置。该文件的目的是确保全部的js文件都能经过正确的路径载入。
require.config({ paths: { 'jquery': 'vendor/jquery/jquery', 'angular': 'vendor/angular/angular', 'kendo': 'vendor/kendo/kendo', 'angular-kendo': 'vendor/angular-kendo', 'app': 'app' }, shim: { // 确保kendo在angular-kendo以前载入 'angular-kendo': ['kendo'], // make sure that 'app': { deps: ['jquery', 'angular', 'kendo', 'angular-kendo'] } } }); define(['routes'], function () { // 使用bootstrap方法启动Angular应用 angular.bootstrap(document, ['app']); });
注意到在这里咱们须要来手动启动Angular应用。main.js这个文件完成的事情简单来讲就是:载入全部文件,而后在document上运行Angular并将ng-app属性设置为’app’。这些文件由于是由RequireJS异步载入,所以咱们须要来“手动启动”Angular应用。
在angular.bootstrap方法运行以前,全部的文件已经载入完毕了。这些依赖工做由RequireJS进行解析。注意到上面的代码中define函数对route.js文件发出了请求。RequireJS接着就会在执行angular.bootstrap方法以前载入该文件。
// routes.js define([ './app' ], function (app) { // app是Angular应用对象 return app.config(['$routeProvider', function ($routeProvider) { $routeProvider .when('/home', { templateUrl: '/app/partials/home.html', controller: 'homeController' }) .otherwise( { redirectTo: '/home' }); }]); });
route.js文件声明app.js为依赖项。app.js文件建立了一个Angular应用对象,而且将它暴露给外部以便于路由能够获取它。
// app.js define([ './controllers/index', './services/index' ], function (controllers, index) { // 返回真正的Angular应用对象,在声明时指明了依赖的项目 return angular.module('app', [ 'ngRoute', 'kendo.directives', 'app.controllers', 'app.services' ]); });
app.js文件建立了模型而且注入了全部所须要的依赖项。其中包含ngRoute服务,Angular Kendo UI 指定以及其余两个模块,这两个模块都在文件的顶部定义。咱们首先来看看”controllers/index.js”文件。
// controllers/index.js define([ './homeController' ], function () { });
除了载入依赖项之外,上面的这段代码没有作别的事。到目前为止,咱们只有一个控制器,可是随着应用的逐渐变大,咱们会有愈来愈多的控制器。全部的控制器都将在这个文件中被加载。每个控制器的代码又包含在一个单独的文件中。
// controllers/homeController.js define([ './module' ], function (module) { module.controller('homeController', ['$scope', '$productsDataSource', function ($scope, $productsDataSource) { $scope.title = 'Home'; $scope.productsDataSource = $productsDataSource; $scope.listViewTemplate = '<p>#: ShipCity #</p>'; }; ); });
这段代码和以前HomeController的代码很类似,可是它在运行以前还须要一个module.js文件。它的做用在于建立app.controller模块以便于咱们能在任何的控制器文件中使用它。
// controllers/module.js define([ ], function () { return angular.module('app.controllers', []); });
咱们如今来回顾一下从一开始到如今究竟发生了些什么:
“main.js” requires “routes.js” “routes.js” requires “app.js” “app.js” requires “controllers/index.js” “controllers/index.js” requires 全部的控制器 全部的控制器 require “module.js” “module.js” 建立了 “app.controllers” 模块
这有点像一颗过于庞大的依赖树,可是它的可扩展性确实很好。若是你想添加一个新的控制器,你只须要添加”controllers/nameController.js”文件,并在”controllers/index.js”文件中添加相同的依赖项便可。
服务的运做方式和控制器相似。app.js会require services/index.js文件,它require了全部的服务。全部的服务同时会require services/module.js文件,它可以简单的建立并提供app.services模块。
如今回到app.js文件,全部的项目都在其中被加载并传递给咱们建立的Angular应用模块。最后一件发生的事情是main.js文件中所发生的angular.bootstrap。简单来讲,咱们第一眼看到的代码其实在最后才会执行。
这实在是有点难以理解。
RequireJS会在应用运行以前加载全部的代码。这意味着咱们并无实现代码的延迟加载。
RequireJS在绝大多数的项目中确实很是好用,可是在Angular应用中,Browserify是一个更好的选择。
固然,RequireJS和Browserify并非仅有的选择,若是你有兴趣的话,能够去研究一下WebPack,它不经能使用AMD和CommonJS,同时也能在服务器端和浏览器端同时使用。它甚至可以处理一些预处理器例如LESS、CoffeeScript,Jade等等。
但愿本文可以帮助你建立出更加性感并健壮的AngularJS应用。
本文参考自Requiring vs Browserifying Angular,原文地址http://developer.telerik.com/featured/requiring-vs-browerifying-angular/