序言
目的
這將是一個系列文灸拍,動機是為了希望給公司及項目組其他的新人同學同事作為入門教材使用做祝。
整個系列將從零開始構(gòu)建一個基于完整工具鏈構(gòu)建的angular應(yīng)用。使用的工具包括且不僅限于:
- nodejs/npm
- gulp
- sass/scss
我們將完成什么
我們使用Angular做一個很簡單的鸡岗,包括登陸與一些基礎(chǔ)增刪改查的應(yīng)用混槐。
Vol.1 使用npm,bower,gulp構(gòu)建最基本的angular開發(fā)環(huán)境
介紹
本文面向有一定基礎(chǔ)的Angular開發(fā)者,如果您是第一次接觸angular轩性,我建議您先去看下大漠窮秋先生寫的基礎(chǔ)教程書籍或者通過使用yoeman進行構(gòu)建声登,對angular的工程化編寫有一個初步的認識。
本次使用的組件與相應(yīng)演示代碼
- github代碼 https://github.com/akirapanda/angular-with-gulp
- npm
- bower
- angular
- angular-ui-router
- gulp
- gulp-connect
使用npm與bower進行第三方組件包的管理
什么是npm
npm是nodejs提供的包管理工具(package management),對于js應(yīng)用的開發(fā)類似于java的mvn,ruby的gem揣苏。用于管理應(yīng)用相關(guān)的第三方組件與工具悯嗓,而不是“上古”時期,開發(fā)人自己下載解壓縮放到指定目錄卸察,再通過<script>標簽進行引入脯厨。
比如最常見的我們引入jquery,我們除了引入了文件還需要進行版本的控制。
<script src="https://code.jquery.com/jquery-2.2.3.min.js"></script>
而使用npm進行管理則是通過描述文件進行相關(guān)的版本控制管理坑质,將第三方組件管理排除與應(yīng)用代碼之外合武,為工程化管理代碼提供便利。
除此以外npm還提供了許多nodejs周邊cli工具的包管理涡扼,比如nodejs自己本身稼跳。
什么是bower
bower是由twitter開發(fā)的一套客戶端包管理工具。其思想與npm基本是一致的吃沪,為什么會有兩套不同的管理體系一直是一個懸而未解的謎團汤善。
通常來說客戶端的包依賴我們會通過bower進行管理,而服務(wù)端(如果存在)則會使用npm進行管理依賴。
因為客戶端的包大部分都是js形式的第三方組件包红淡,而npm包又不少是需要進行本地編譯的cli工具包卸伞。
不過最近已經(jīng)開始有開始提倡所有第三方包依賴都通過npm進行管理的趨勢了。
那么什么是gulp
gulp是js的又一種比較流行的锉屈、基于任務(wù)的構(gòu)建工具。類似C語言的make垮耳,Java的ant颈渊、maven,Ruby的rake终佛。
而gulp的插件機制又提供給了開發(fā)者許多遍歷俊嗽,使js存在一個“編譯期“的概念。
這部分我們先不展開铃彰。
初始化npm和bower
開始前請確定您已經(jīng)安裝了最新版本的nodejs,我使用的版本是mac的v4.2.1
我們先全局安裝一下bower
npm install -g bower
完成后,我們新建工程文件夾绍豁,并且命名為news。
其次我們依次輸入初始化命令:
npm init
bower init
之后我們并會得到兩個文件,package.json與bower.json,兩個文件分別用于描述我們依賴的第三方包清單牙捉。
通過npm安裝gulp
與安裝bower一樣竹揍,我們需要安裝一下gulp,但有所區(qū)別的是,我們希望將來所有使用我們這份代碼的人都會可以通過npm自動安裝下gulp邪铲。則我們需要比剛才的命令多打一個后綴參數(shù):
npm install gulp --save-dev //npm下載后會將相關(guān)組件信息記錄與package.json
鍵入完命令后芬位,npm會做兩件事情:
- 下載gulp的最新版本至node_modules文件夾下
- 將gulp的組件記錄插入至package.json
我們查看下最新的package.json其內(nèi)容將會出現(xiàn)gulp的信息。
"devDependencies": {
"gulp": "^3.9.1"
}
通過bower下載angualrjs
與npm安裝gulp一樣,我們通過bower的cli命令來安裝angular
bower install angular --save
- bower會下載angular最新版本至bower_components文件夾下带到,
- 相關(guān)信息記錄與bower.json中
\\bower.json會出現(xiàn)angular信息
"dependencies": {
"angular": "^1.5.4"
}
至此昧碉,相當文件夾下的目錄結(jié)構(gòu)便是
-news
--node_modules
--bower_components
-package.json
-bower.json
進入angular環(huán)節(jié)
這次我們就會比較簡單的做一個只有一頁的angualr應(yīng)用,我們首先在news目錄下新建一個app目錄用于存儲所有我們自行編寫應(yīng)用代碼揽惹。其目錄結(jié)構(gòu)為
-news
--app
--scripts //所有js代碼
--styles //所有樣式代碼
-home.html //頁面
-index.hmtl //入口頁面
第一個應(yīng)用
這次我們目的是做一個歡迎的頁面被饿,其訪問的url為/home,用戶在下面輸入自己姓名的后搪搏,上面的歡迎信息會自動發(fā)生變化的小應(yīng)用狭握。
安裝angular-ui-router
angular-ui-router是angular中一套比較常用的路由控制庫。什么是路由控制呢慕嚷?也就是把url當成狀態(tài)入口與相關(guān)控制器哥牍、視圖進行分發(fā)綁定的組件。
我們通過bower進行安裝,
bower install angular-ui-router --save
編寫我們的入口index.html
我們的目的是編寫一個spa(單頁應(yīng)用)喝检,則index.html則負責在第一次的時候為用戶訪問整個系統(tǒng)提供入口嗅辣,下載相關(guān)的依賴資源。
\\index.html
<!doctype html>
<html ng-app="app" >
<head>
<meta charset="utf-8">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
</head>
<body >
<div ui-view>
</div>
<!-- 將bower下載關(guān)聯(lián)的組件js引入-->
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
</body>
</html>
如何開發(fā)運行應(yīng)用呢挠说?
如果之前沒有接觸過web開發(fā)和nodejs的同學在這里多半會將整個工程放到apache的htdoc目錄下進行訪問測試了吧澡谭?
而知道http-server的同學在news目錄下運行了http-server又發(fā)現(xiàn),上文中的script引入bower組件的地址是不正確的,并且地址欄上多一個app的目錄路徑蛙奖。
推薦的做法是使用gulp-connect來管理開發(fā)環(huán)境的服務(wù)器啟動潘酗。
通過npm安裝gulp-connect
我們一樣通過npm來裝gulp-connect組件
npm install gulp-connect --save-dev
然后我們新建一個gulpfile.js文件來定義開發(fā)服務(wù)器的啟動任務(wù):
\\gulpfile.js
'user strict';
var gulp = require('gulp'); //require node_modules中的gulp包
var path = require("path");
var connect = require("gulp-connect"); //require node_modules中的gulp-connect包
var ROOT_PATH = path.resolve(__dirname); //項目根目錄
var APP_PATH = path.resolve(ROOT_PATH,"app"); //應(yīng)用代碼目錄
gulp.task("connect",function(){
connect.server({
root: ["app"], //使用哪個目錄作為啟動的根目錄
livereload:true, //實施加載,可理解為熱部署
middleware: function(connect) {
return [connect().use('/bower_components', connect.static('bower_components'))]; //見下文
}
});
});
gulp.task("default",["connect"]); // gulp如缺省目標task則使用connect作為task
我們通過在gulp中新建了一個connect任務(wù)雁仲,使用connect.server來啟動一個開發(fā)部署的http服務(wù)器進行開發(fā)仔夺,其中root,livereload參數(shù)都不難理解。那么middleware的目的是什么呢攒砖?
這里要再次拿我們代碼目錄拿出來說一下缸兔,現(xiàn)在我們的代碼目錄應(yīng)該是這樣的
-news
--node_modules
--bower_components
-package.json
-bower.json
--app <---http啟動加載的文件根目錄
--scripts
--styles
-index.html
-home.html
則當服務(wù)器啟動后index.html,我們是無法訪問到在http目錄以外的bower_compments目錄中的第三方組件包的吹艇。我們做middelware的動機就是讓connect額外的將/bower_compmenets文件夾加載到我們的應(yīng)用服務(wù)器的內(nèi)容中惰蜜,就好比我們復(fù)制了一個備份到/app/bower_compmenets文件夾中。
當我們現(xiàn)在在應(yīng)用目錄下鍵入gulp命令后受神,便可看到控制端有以下的提示信息
我們便可在瀏覽器輸入地址127.0.0.1:8080訪問到我們index.html文件抛猖,并且也可以正確的引入相關(guān)的angular組件。
編寫Module
可能很多同學想到是先去寫一個controller來實現(xiàn)我們預(yù)期的功能鼻听,但我們提倡的是around module的開發(fā)模式财著,即所有的controller,service等angular組件全部圍繞著module進行組織。故我們這里先將全局的module編寫精算。
\\ app/scripts/index.module.js
(function ()
{
'use strict';
angular
.module('app', [
'ui.router'
])
.config(routeConfig);
routeConfig.$inject = ['$stateProvider', '$urlRouterProvider','$locationProvider'];
function routeConfig($stateProvider, $urlRouterProvider, $locationProvider)
{
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'home.html',
controller: 'IndexController',
controllerAs: 'vm'
});
}
})();
我們使用的這種寫法有幾個地方需要注意:
- 通過IIFE風格的編碼瓢宦,使代碼在聲明后立刻被運行;
(function(){
})()
- 只在xxx.module文件中對angular.module進行setter操作
angular
.module('app', [
'ui.router'
])
module('name',[]) 為setter灰羽,即聲明一個新的module至angular上下文
- 通過module.config,controlle,service方法將關(guān)聯(lián)組件加入對應(yīng)的module中
angular
.module('app', [
'ui.router'
])
.config(routeConfig);
// 等價于angular.module('app').config(routeConfig)
- 將相關(guān)的實現(xiàn)函數(shù)單獨放下而不是通過config(function(){})進行聲明驮履,增加可讀性
- 通過$inject方法來控制注入,而不是通過參數(shù)對比注入
//一般我們的做法可能是
function routeConfig(['$stateProvider', '$urlRouterProvider', '$locationProvider'],$stateProvider, $urlRouterProvider, $locationProvider){
}
這樣通過參數(shù)控制的注入廉嚼,一般會有順序問題玫镐,參數(shù)一多,每次找的問題的時候必須先要進行“排排坐吃果果”的比較怠噪。推薦使用$inject來控制注入
routeConfig.$inject = ['$stateProvider', '$urlRouterProvider','$locationProvider'];
在實際聲明部分便需要需要再次聲明注入的形式的
function routeConfig($stateProvider, $urlRouterProvider, $locationProvider)
{
}
通過 ui-router來創(chuàng)建一個/home的路由
我們通過ui-router的stateProvider來增加一個新的state home,其訪問的url為/home
function routeConfig($stateProvider, $urlRouterProvider, $locationProvider)
{
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'home.html',
controller: 'IndexController',
controllerAs: 'vm' //使用vm而不是$scope
});
}
而home.html的內(nèi)容為,這里我們使用了controller as vm的方法來訪問IndexController中的實例變量恐似,而不是使用$scope。至于這是為了什么傍念,可以閱讀我之前的說明或者谷歌一些相關(guān)的討論矫夷。
視圖中我們將在h1便簽部分顯示controller中的name變量的值,并且將其綁定與input的文本輸入框中憋槐,當用戶填寫新的文本值時双藕,會出修改name的值,從而上面h1顯示的值也會發(fā)生變化阳仔。
<h1>Hello {{vm.name}}</h1>
<label>input your name:</label>
<input ng-model="vm.name"></input>
那么對應(yīng)的我們也要寫一個IndexController
\\ app/scripts/index.controller.js
(function ()
{
'use strict';
angular
.module('app')
.controller('IndexController', IndexController);
function IndexController()
{
var vm = this; // 將this指針更名為vm
vm.name = "unknown"; //初始化頁面的name 為 unknown
}
})();
在index.html中引入module與contoller
<body>
<div ui-view>
</div>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
<script src="scripts/index.module.js"></script>
<script src="scripts/index.controller.js"></script>
</body>
訪問 127.0.0.1:8080/#/home
這時便可看到頁面顯示了預(yù)期的效果忧陪,每當我們在文本框輸入新的值上方的顯示值則同步更新。這就是angular雙向綁定的優(yōu)勢之處。
下一期我們會做什么
我們會使用angular中的service來封裝一些外部的API編寫一個顯示當前天氣信息的小應(yīng)用嘶摊。