一步一步构建Angular应用 Vol.1

序言

目的

这将是一个系列文,动机是为了希望给公司及项目组其他的新人同学同事作为入门教材使用。
整个系列将从零开始构建一个基于完整工具链构建的angular应用。使用的工具包括且不仅限于:

  • nodejs/npm
  • gulp
  • sass/scss

我们将完成什么

我们使用Angular做一个很简单的,包括登陆与一些基础增删改查的应用。

Vol.1 使用npm,bower,gulp构建最基本的angular开发环境

介绍

本文面向有一定基础的Angular开发者,如果您是第一次接触angular,我建议您先去看下大漠穷秋先生写的基础教程书籍或者通过使用yoeman进行构建,对angular的工程化编写有一个初步的认识。

本次使用的组件与相应演示代码

使用npm与bower进行第三方组件包的管理

什么是npm


npm是nodejs提供的包管理工具(package management),对于js应用的开发类似于java的mvn,ruby的gem。用于管理应用相关的第三方组件与工具,而不是“上古”时期,开发人自己下载解压缩放到指定目录,再通过<script>标签进行引入。
比如最常见的我们引入jquery,我们除了引入了文件还需要进行版本的控制。

<script src="https://code.jquery.com/jquery-2.2.3.min.js"></script>

而使用npm进行管理则是通过描述文件进行相关的版本控制管理,将第三方组件管理排除与应用代码之外,为工程化管理代码提供便利。
除此以外npm还提供了许多nodejs周边cli工具的包管理,比如nodejs自己本身。

什么是bower

bower主页图

bower是由twitter开发的一套客户端包管理工具。其思想与npm基本是一致的,为什么会有两套不同的管理体系一直是一个悬而未解的谜团。
通常来说客户端的包依赖我们会通过bower进行管理,而服务端(如果存在)则会使用npm进行管理依赖。
因为客户端的包大部分都是js形式的第三方组件包,而npm包又不少是需要进行本地编译的cli工具包。
不过最近已经开始有开始提倡所有第三方包依赖都通过npm进行管理的趋势了。

那么什么是gulp

gulp主页图

gulp是js的又一种比较流行的、基于任务的构建工具。类似C语言的make,Java的ant、maven,Ruby的rake。
而gulp的插件机制又提供给了开发者许多遍历,使js存在一个“编译期“的概念。
这部分我们先不展开。

初始化npm和bower

开始前请确定您已经安装了最新版本的nodejs,我使用的版本是mac的v4.2.1
我们先全局安装一下bower

 npm install -g bower

完成后,我们新建工程文件夹,并且命名为news。
其次我们依次输入初始化命令:

npm init
bower init

之后我们并会得到两个文件,package.json与bower.json,两个文件分别用于描述我们依赖的第三方包清单。

通过npm安装gulp

与安装bower一样,我们需要安装一下gulp,但有所区别的是,我们希望将来所有使用我们这份代码的人都会可以通过npm自动安装下gulp。则我们需要比刚才的命令多打一个后缀参数:

 npm install gulp --save-dev //npm下载后会将相关组件信息记录与package.json

键入完命令后,npm会做两件事情:

  1. 下载gulp的最新版本至node_modules文件夹下
  2. 将gulp的组件记录插入至package.json
    我们查看下最新的package.json其内容将会出现gulp的信息。
  "devDependencies": {
    "gulp": "^3.9.1"
  }

通过bower下载angualrjs

与npm安装gulp一样,我们通过bower的cli命令来安装angular

bower install angular --save 
  1. bower会下载angular最新版本至bower_components文件夹下,
  2. 相关信息记录与bower.json中
\\bower.json会出现angular信息
  "dependencies": {
    "angular": "^1.5.4"
  }

至此,相当文件夹下的目录结构便是

-news
  --node_modules
  --bower_components
  -package.json
  -bower.json

进入angular环节

这次我们就会比较简单的做一个只有一页的angualr应用,我们首先在news目录下新建一个app目录用于存储所有我们自行编写应用代码。其目录结构为

-news
  --app
    --scripts  //所有js代码
    --styles  //所有样式代码
    -home.html //页面
    -index.hmtl //入口页面

第一个应用

这次我们目的是做一个欢迎的页面,其访问的url为/home,用户在下面输入自己姓名的后,上面的欢迎信息会自动发生变化的小应用。

安装angular-ui-router

angular-ui-router是angular中一套比较常用的路由控制库。什么是路由控制呢?也就是把url当成状态入口与相关控制器、视图进行分发绑定的组件。
我们通过bower进行安装,

 bower install angular-ui-router --save

编写我们的入口index.html

我们的目的是编写一个spa(单页应用),则index.html则负责在第一次的时候为用户访问整个系统提供入口,下载相关的依赖资源。

\\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下载关联的组件js引入-->
    <script src="bower_components/angular/angular.js"></script>
    <script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
    </body>
</html>

如何开发运行应用呢?

如果之前没有接触过web开发和nodejs的同学在这里多半会将整个工程放到apache的htdoc目录下进行访问测试了吧?
而知道http-server的同学在news目录下运行了http-server又发现,上文中的script引入bower组件的地址是不正确的,并且地址栏上多一个app的目录路径。
推荐的做法是使用gulp-connect来管理开发环境的服务器启动。

通过npm安装gulp-connect

我们一样通过npm来装gulp-connect组件

 npm install gulp-connect --save-dev

然后我们新建一个gulpfile.js文件来定义开发服务器的启动任务:

\\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"); //应用代码目录

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任务,使用connect.server来启动一个开发部署的http服务器进行开发,其中root,livereload参数都不难理解。那么middleware的目的是什么呢?
这里要再次拿我们代码目录拿出来说一下,现在我们的代码目录应该是这样的

-news
  --node_modules
  --bower_components
  -package.json
  -bower.json
  --app   <---http启动加载的文件根目录
    --scripts
    --styles
    -index.html
    -home.html

则当服务器启动后index.html,我们是无法访问到在http目录以外的bower_compments目录中的第三方组件包的。我们做middelware的动机就是让connect额外的将/bower_compmenets文件夹加载到我们的应用服务器的内容中,就好比我们复制了一个备份到/app/bower_compmenets文件夹中。
当我们现在在应用目录下键入gulp命令后,便可看到控制端有以下的提示信息

服务器启动啦

我们便可在浏览器输入地址127.0.0.1:8080访问到我们index.html文件,并且也可以正确的引入相关的angular组件。

编写Module

可能很多同学想到是先去写一个controller来实现我们预期的功能,但我们提倡的是around module的开发模式,即所有的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'
                });
    }
})();

我们使用的这种写法有几个地方需要注意:

  1. 通过IIFE风格的编码,使代码在声明后立刻被运行;
(function(){
})()
  1. 只在xxx.module文件中对angular.module进行setter操作
    angular
        .module('app', [
            'ui.router'     
        ])

module('name',[]) 为setter,即声明一个新的module至angular上下文

  1. 通过module.config,controlle,service方法将关联组件加入对应的module中
       angular
        .module('app', [
            'ui.router'     
        ])
       .config(routeConfig);   
      // 等价于angular.module('app').config(routeConfig)
  1. 将相关的实现函数单独放下而不是通过config(function(){})进行声明,增加可读性
  2. 通过$inject方法来控制注入,而不是通过参数对比注入
//一般我们的做法可能是
    function routeConfig(['$stateProvider', '$urlRouterProvider', '$locationProvider'],$stateProvider, $urlRouterProvider, $locationProvider){
}

这样通过参数控制的注入,一般会有顺序问题,参数一多,每次找的问题的时候必须先要进行“排排坐吃果果”的比较。推荐使用$inject来控制注入

    routeConfig.$inject = ['$stateProvider', '$urlRouterProvider','$locationProvider'];  

在实际声明部分便需要需要再次声明注入的形式的

  function routeConfig($stateProvider, $urlRouterProvider, $locationProvider)
    {
    }

通过 ui-router来创建一个/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的内容为,这里我们使用了controller as vm的方法来访问IndexController中的实例变量,而不是使用$scope。至于这是为了什么,可以阅读我之前的说明或者谷歌一些相关的讨论。
视图中我们将在h1便签部分显示controller中的name变量的值,并且将其绑定与input的文本输入框中,当用户填写新的文本值时,会出修改name的值,从而上面h1显示的值也会发生变化。

<h1>Hello {{vm.name}}</h1>
<label>input your name:</label>
<input ng-model="vm.name"></input>

那么对应的我们也要写一个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

这时便可看到页面显示了预期的效果,每当我们在文本框输入新的值上方的显示值则同步更新。这就是angular双向绑定的优势之处。

下一期我们会做什么

我们会使用angular中的service来封装一些外部的API编写一个显示当前天气信息的小应用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容