英文原文:Ultimate guide to learning AngularJS in one day
Angular是基于javaScript语言构建的一个MVC/MVVM框架,对创建现代化单WEB应用(甚至是整个网站)至关重要。这篇文章是基于我的经验,建议及最佳实践而编写的一个非常全面的速成课程。
Angular有一个较短的学习曲线,你会对它有一个彻底的了解,当你掌握了基本操作后。对它的学习主要是慢慢掌握一些术语的概念并且认真去理解MVC思想,即模型 - 视图 - 控制器。下面是有关angular的更高层次的核心APIs以及一些术语。
你可能曾听说过MVC,在许多编程语言里它被用作构建或设计应用程序/软件的一种手段。下面是对MVC的分解介绍:
模型:一端特定的应用程序中的数据结构,通常采用JSON进行数据传输。在你学习Angular之前,你最好仔细研究一下JSON。因为它在你的服务器与视图展示的数据交互中扮演者重要的角色。例如,一组用户标识可以采用如下的形式:
{
"users" : [{ "name": "Joe Bloggs", "id": "82047392" },{ "name": "John Doe", "id": "65198013" }] }
然后,你将能够通过XHR(XMLHTTP请求)从服务器获取这些数据,在jQuery里它就是$.ajax
方法而Angular则将它包装为 $http
方法,或者在页面解析(从数据存储区/数据库)时写入到您的代码里。再然后,您可以更新数据模型,并返回给服务器。
视图:视图很简单,它可以是你的HTML或着渲染后的输出结果。使用MVC框架,你可以仅仅更新你的模型数据框架会自动更新您的视图并在HTML中展示相关数据。
控制器:他们控制东西,但又是什么东西呢? 数据!控制器是从服务器到视图的直接沟通者,也就是中间人,因此您可以在服务器和客户端进行通讯并即时更新数据。
首先,我们实际上需要对Angular项目设置一些关键元素。再开始之前我们要注意,通常会有一个ng-app
指令来定义你的应用程序,一个Controller
来和你的视图交互,以及一些DOM
绑定并引入Angular。以下是最基本的设置:
一些带有ng-*
指令的HTML:
<div ng-app="myApp">
<div ng-controller="MainCtrl">
<!-- controller logic -->
</div>
</div>
一个Angular模块与控制器:
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$scope', function ($scope) {
// Controller magic
}]);
在更深入之前,我们需要创建一个Angular module
并将我们所有的控制逻辑绑定到上面,声明模块的方法很多,例如你可以链接你的控制逻辑像下面这样(然而我并不喜欢这种方法):
angular.module('myApp', [])
.controller('MainCtrl', ['$scope', function ($scope) {...}])
.controller('NavCtrl', ['$scope', function ($scope) {...}])
.controller('UserCtrl', ['$scope', function ($scope) {...}]);
在我接触的Angular项目中,创建一个全局模块被证明是最佳实践。由于缺少分号或者“程序链”的意外断开经常适得其反并且产生不必要的编译错误。所以像下面这样吧:
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$scope', function ($scope) {...}]);
myApp.controller('NavCtrl', ['$scope', function ($scope) {...}]);
myApp.controller('UserCtrl', ['$scope', function ($scope) {...}]);
我所创建的每个新文件都只是简单的引用了myApp
命名空间,然后他们就自动的注册进应用当中。是的,我正在为每个Controller, Directive, Factory
甚至任何其他的都创建这样的新文件(你会因此而感谢我的!)将他们统统连结起来并最小化,然后只引入单个脚本文件到DOM中(利用像Grunt/Gulp
这样的命令行构建工具)。
现在你已经掌握了MVC的基本概念和项目基本的构建,接下来让我们看看Angular关于如何开始使用Controller
的实现方式。
就拿上面的例子来说,我们可以更进一步从一个Controller
中向DOM推送一些数据。Angular采用了一种模板样式的语法{{ handlebars }}
来告诉HTML。理想情况下,你的HTML不应该包含物理文本或硬编码值以最大限度利用Angular的特性。下面是向DOM中推一个简单字符串的例子:
<div ng-app="myApp"> <div ng-controller="MainCtrl"> {{ text }} </div> </div>
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$scope', function ($scope) {
$scope.text = 'Hello, Angular fanatic.';
}]);
实时输出结果如下:
<iframe width="100%" height="230" src="//jsfiddle.net/toddmotto/mN7QB/embedded/result,js,html" allowfullscreen="allowfullscreen" frameborder="0">
</iframe>
这里的一个关键概念就是$scope
,利用它将你所有的方法放置到特定的Controller
,$scope
指的是DOM中当前的元素或区域(不,和DOM不一样)并且封装了一个完全在元素范围内保持数据和逻辑的灵活的作用域。它将JavaScript 公共/私有的范围界限带到DOM中,这简直太棒了。
$scope
的概念可能一开始看起来有点恐怖,但是它是你从服务器获取数据放入DOM中的方式(或者是静态数据,如果你也有的话!)这个例子演示了你该如何向DOM推送数据的基本理念。
让我们来看一些更具代表性的数据结构,假设这些数据是我们已经从服务器检索出的用来显示用户登录信息的数据。现在,我将使用静态数据;稍后我会告诉你如何获取动态的JSON数据。
首先我们将组织如下JavaScript :
var myApp = angular.module('myApp', []);
myApp.controller('UserCtrl', ['$scope', function ($scope) {
// Let's namespace the user details
// Also great for DOM visual aids too
$scope.user = {};
$scope.user.details = {
"username": "Todd Motto",
"id": "89101112"
};
}]);
然后将他植入DOM中来展示这些数据:
<div ng-app="myApp">
<div ng-controller="UserCtrl">
<p class="username">Welcome, {{ user.details.username }}</p>
<p class="id">User ID: {{ user.details.id }}</p>
</div>
</div>
输出:
<iframe width="100%" height="310" src="//jsfiddle.net/toddmotto/425KU/embedded/result,js,html" allowfullscreen="allowfullscreen" frameborder="0"></iframe>
重要的是要记住,控制器仅用于数据操作和创建函数(当然还有事件方法)同我们的服务器进行通信并且推/拉JSON数据。DOM操作不应该在这里做,所以把你的jQuery的工具包去除吧。指令适用于DOM操作,但那是下一节要介绍的。
高级技巧:Angular 官方文档(在写这篇博文时)的例子展示了这种创建控制器的方法:
var myApp = angular.module('myApp', []);
function MainCtrl ($scope) {
//...
};
。。。千万别这样做。这将你的所有方法暴露到了全局作用域并且使之不能很好的与你的应用程序融合。这也意味着,你不能方便的压缩代码或运行测试。不要污染全局命名空间,你应该保证你的控制器始终在你的应用范围内。
一个directive
(从这篇文档现有的脚本或插件程序中检索指令)的最简单的形式是一小段HTML模板,最好是在应用中被多次使用的片段。毫不费力地向你的应用中注入DOM或着执行自定义的DOM交互是一种简单的方法。指令其实一点也不简单,要想完全掌握它们将会经历一个难以置信的学习曲线,但是下一阶段将让你充满干劲的开始学习。
那么,指令究竟用来做什么呢?其实有很多事情,包括DOM组件,例如选项卡或导航元素 - 这完全取决于你的应用程序在UI层使用了什么。这样说吧,如果你使用了ng-show
或 ng-hide
,那这些就是指令(虽然它们并不会注入DOM)。
在此次练习中,我会始终保持它非常简单,并创建一个被注入了那些我非常讨厌的打出来的一些标记的自定义类型按钮(被称为CustomButton
)。有多种方法来定义DOM指令,这可能看起来像下面这样:
<!-- 1: as an attribute declaration -->
<a custom-button>Click me</a>
<!-- 2: as a custom element -->
<custom-button>Click me</custom-button>
<!-- 3: as a class (used for old IE compat) -->
<a class="custom-button">Click me</a>
<!-- 4: as a comment (not good for this demo, however) -->
<!-- directive: custom-button -->
我更喜欢将他们作为一个属性使用,在HTML5的未来Web组件下的自定义元素即将涌现,但是Angular显示着在一些老的浏览器中表现非常糟糕。
现在你已经知道了如何在那些需要使用或注入指令的地方对他们进行声明,那就让我们开始创建这个自定义的按钮吧。同样,我将其挂在我的全局命名空间myApp
内.这是一个指令最简单的形式:
myApp.directive('customButton', function () {
return {
link: function (scope, element, attrs) {
// DOM manipulation/events here!
}
};
});
我用.directive()
方法来定义我的指令,并传入该指令的名称CustomButton
。当你将指令名称的一个字母大写时,它的使用方式是在DOM中用连字符分割(如上)。
一个指令只需通过一个对象返回自身,并携带一系列参数。对我来说最重要的需要优先掌握的是,restrict, replace, transclude, template, templateUrl
当然还有link
属性。让我们添加这些属性:
myApp.directive('customButton', function () {
return {
restrict: 'A',
replace: true,
transclude: true,
template: '<a href="" class="myawesomebutton" ng-transclude>' +
'<i class="icon-ok-sign"></i>' +
'</a>',
link: function (scope, element, attrs) {
// DOM manipulation/events here!
}
};
});
输出:
<iframe width="100%" height="300" src="//jsfiddle.net/toddmotto/VC4H2/embedded/result,js,html" allowfullscreen="allowfullscreen" frameborder="0"></iframe>
请务必检查元素,看那些被注入的额外标记。是的,我知道,这里并没有引入图标,因为我从未引入Font Awesome,但你看到了它是如何工作的。下面是指令属质的解释:
restrict:这又回到它的使用上面来,我们如何限制元素的使用情况?如果你正在使用一个需要传统的IE浏览器支持的项目,你可能需要attribute/class
的声明。如果restrict
值为’A’,意味着你限制它作为一个属性;”E”为元素,’C’类和’M’评论。这些都默认为“EA”。是的,你可以限制为多个用例。
replace:这替换了DOM中用来定义指令的标记,在本例应用中,你会发现DOM最初是如何被替换为该指令的模板。
transclude:简单地说,使用transclude允许现有的DOM内容被复制到指令。在网页被渲染后你将看到 ‘Click me’被’转移’到指令中。
template:模板(如上)允许你定义用来注入的标记。仅将其应用到小的HTML片段上是一个好主意。注入的模板都被Angular所编译,这也意味着您可以在其中声明用于绑定的handlebar 模板标签。
templateUrl:与模板类似,但保留在它自己的文件或<script>
标签中。你可以用它指定一个模板的URL,您将乐意使用它,当需要可管理的HTML代码块被保留在自己的文件中时,只需指定路径和文件名,最好保存在他们自己的模板目录中:
myApp.directive('customButton', function () {
return {
templateUrl: 'templates/customButton.html'
// directive stuff...
};
});
在你的文件中(文件名一点都不重要)
<!-- inside customButton.html -->
<a href="" class="myawesomebutton" ng-transclude>
<i class="icon-ok-sign"></i>
</a>
这样做的好处是,浏览器将缓存html 文件,好极了!另一种不被缓存的方法是将模板声明放在<script>
标签中。
<script type="text/ng-template" id="customButton.html">
<a href="" class="myawesomebutton" ng-transclude> <i class="icon-ok-sign"></i> </a> </script>
你会告诉Angular,这是一个ng-template
并给它一个ID号。Angular将寻找ng-template
或*.html
文件,无论你喜欢使用那种方式。而我喜欢创建*.html
文件,因为他们非常易于管理,可以提高性能并保持DOM干净,你可能会使用到1或100个指令,你希望能够通过他们轻松导航。
Services 往往是一个令人困惑的问题。从以往的经验和研究,他们更多的是风格的设计模式,而不是提供很多功能上的差异。在深入钻研Angular的源码之后,他们期待通过相同的编译器来运行并且他们分享了很多功能。从我的研究来看,你应该使用单例模式启用Services 并且使用Factories完成更复杂的功能,如对象常量和更复杂的用例。
下面是一个Service 例子,它计算两个数字的积:
myApp.service('Math', function () {
this.multiply = function (x, y) {
return x * y;
};
});
你将在一个Controller中像下面这样调用它:
myApp.controller('MainCtrl', ['$scope', function ($scope) {
var a = 12;
var b = 24;
// outputs 288
var result = Math.multiply(a, b);
}]);
是的,乘法非常简单因此并不需要一个Service,但你得到了其要领。
当你要创建一个Service (or Factory),您需要使用依赖注入来告诉Angular它需要获取你的新服务的持有着 - 否则,你会得到一个编译错误,你的控制器也将被打破。你现在可能注意到了控制器声明中的function ($scope)
部分,这就是简单的依赖注入。给它注入代码!您还会注意到function ($scope)
之前的['$scope']
,我会晚一点再介绍它。以下是如何使用依赖注入来告诉Angular你需要你的服务:
// Pass in Math
myApp.controller('MainCtrl', ['$scope', 'Math', function ($scope, Math) {
var a = 12;
var b = 24;
// outputs 288
var result = Math.multiply(a, b);
}]);
从 Services 过渡 Factories 应该是很简单的。现在,我们可以在一个工厂内创建对象字面量或简单地提供一些更深入的方法:
myApp.factory('Server', ['$http', function ($http) {
return {
get: function(url) {
return $http.get(url);
},
post: function(url) {
return $http.post(url);
},
};
}]);
这里,我对Angular 的XHR进行了自定义的包装。将其依赖注入进一个Controller后,它的使用变得非常简单。
myApp.controller('MainCtrl', ['$scope', 'Server', function ($scope, Server) {
var jsonGet = '//myserver/getURL';
var jsonPost = '//myserver/postURL';
Server.get(jsonGet);
Server.post(jsonPost);
}]);
待续。。。