Yeoman自动构建 Angularjs 项目

Yeoman是什么?

Yeoman按照官方说法,它不只是一个工具,还是一个工作流。它其实包括了三个部分yo、grunt、bower,分别用于项目的启动、文件操作、包管理。

Yo: Yo是一个项目初始化工具,可以生成一套启动某类项目必需的项目文件。

Bower: 一个客户端技术的软件包管理器,它可用于搜索、安装和卸载如JavaScript、HTML、CSS之类的网络资源。

GruntJS: GruntJS是基于JavaScript的命令行构建工具,它可以帮助开发者们自动化重复性的工作。

安装

基于nodejs,需要先安装node,安装时要确保 Add to PATH 被安装进去,然后安装依赖的包。
–安装git,http://git-scm.com/downloads,我是windows版本,安装的时候需要注意勾选Run git from the Windows Command prompt (从Windows命令提示符下运行git)项。
–安装gruntjs,参考之前的文章:一步一步安装Grunt
–Bower是一个客户端技术的软件包管理器,它可用于搜索、安装和卸载如JavaScript、HTML、CSS之类的网络资源。安装bower:
npm install -g bower

安装完成后执行:
npm install -g yo grunt-cli bower
其中 -g 代表要把 yo , grunt-cli , bower 这三个套件安装到全域 (global)

创建项目:
在命名行下,进入相应的目录后,输入:
npm install -g generator-angular
等执行完成后,输入:
yo angular
会出现由符号拼成的Yeoman的标志人物,及一些选择项。
根据需要进行选择操作,yo 会在最后开始完成所有的安装工作。这个命令执行要2分钟左右,会自动下载很多的依赖包。

AngularJS项目结构(Yeoman)
.tmp:临时目录
app:开发的源代码的目录
dist:生成用于发布的项目
node_modules:nodejs依赖包
test:测试文件的目录
.bowerrc:bower属性
.editooconfig:对开发工具的属性配置
.gitattributes:git属性的配置
.gitignore:git管理文件的配置
.jshintr:JSHint配置
.travis.yml:travis-ci持续集成的配置
bower.json:bower依赖管理
Gruntfile.js:grunt开发过程管理
karma.conf.js:karma自动化测试
karma-e2e.conf.js:karma端到端自动化测试
package.json:项目依赖文件

启动项目:
grunt server

好吧,遇到问题了,提示”unable to find local grunt”,原因大概是grunt需要安装在项目目录中。输入:
npm install grunt –save-dev
将安装包放在 ./node_modules 下(运行npm时所在的目录)

再次执行grunt server,就会发现浏览器被自动打开:http://localhost:9000/#/

执行:

grunt --force

生成用于部署的目录dist。

至此结束,还有一些问题。但总算是跑起来了~

参考资料:
http://blog.miniasp.com/post/2013/08/11/Yeoman-1-0-Installation-and-Usage-on-Windows.aspx 较为全面,从node.js for Windows安装开始~ 包括:Git for Windows安装~ ruby执行环境安装~ 等。
http://blog.fens.me/angularjs-yeoman-project/ 介绍构建AngularJS项目的三种方式。

AngularJS入门教程(8~完结)笔记

数据
使用json文件。每一个都用相同的数据结构描述了一部手机的不同属性。

为了构造HTTP请求的URL,我们需要$route服务提供的当前路由中抽$routeParams.phoneId。

function PhoneDetailCtrl($scope, $routeParams, $http) {
  $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
    $scope.phone = data;
  });
}

模板phone-detail.html文件中,我们使用AngularJS的{{表达式}}标记和ngRepeat来在视图中渲染数据模型。

定制过滤器
为了创建一个新的过滤器,先创建一个phonecatFilters模块,并且将定制的过滤器注册给这个模块。

angular.module('phonecatFilters', []).filter('checkmark', function() {
  return function(input) {
    return input ? '\u2713' : '\u2718';
  };
});

在AngularJS模板中使用过滤器的语法是:{{ expression | filter }}

    
Infrared
{{phone.connectivity.infrared | checkmark}}
GPS
{{phone.connectivity.gps | checkmark}}

AngularJS内置过滤器
http://code.angularjs.org/1.1.0/docs/api/ng.$filter

手机详细信息视图展示了一幅当前手机的大号图片,以及几个小一点的缩略图。如果用户点击缩略图就能把那张大的替换成自己那就更好了。现在我们来看看如何用AngularJS来实现它。

phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http',
  function($scope, $routeParams, $http) {
    $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
      $scope.phone = data;
      $scope.mainImageUrl = data.images[0];
    });

    $scope.setImage = function(imageUrl) {
      $scope.mainImageUrl = imageUrl;
    }
  }]);

在PhoneDetailCtrl控制器中,我们创建了mainImageUrl模型属性,并且把它的默认值设为第一个手机图片的URL。



...

我们把大图片的ngSrc指令绑定到mainImageUrl属性上。
同时我们注册一个ngClick处理器到缩略图上。当一个用户点击缩略图的任意一个时,这个处理器会使用setImage事件处理函数来把mainImageUrl属性设置成选定缩略图的URL。

$resource服务使得用短短的几行代码就可以创建一个RESTful客户端。有了这个客户端我们可以用一种更简单的方式来发送XHR请求,而不用去关心更底层的$http服务(API、HTTP方法和URL)。

angular.module('phonecatServices', ['ngResource']).
    factory('Phone', function($resource){
      return $resource('phones/:phoneId.json', {}, {
        query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
      });
    });

我们使用模块API通过一个工厂方法注册了一个定制服务。我们传入服务的名字Phone和工厂函数。工厂函数和控制器构造函数差不多,它们都通过函数参数声明依赖服务。Phone服务声明了它依赖于$resource服务。
通过重构掉底层的$http服务,把它放在一个新的服务Phone中,我们可以大大简化子控制器(PhoneListCtrl和PhoneDetailCtrl)。AngularJS的$resource相比于$http更加适合于与RESTful数据源交互。而且现在我们更容易理解控制器这些代码在干什么了。

var phonecatControllers = angular.module('phonecatControllers', []);

phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone',
  function($scope, Phone) {
    $scope.phones = Phone.query();
    $scope.orderProp = 'age';
  }]);

phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone',
  function($scope, $routeParams, Phone) {
    $scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
      $scope.mainImageUrl = phone.images[0];
    });

    $scope.setImage = function(imageUrl) {
      $scope.mainImageUrl = imageUrl;
    }
  }]);

在上面的代码里面,当调用Phone服务的方法是我们并没有传递任何回调函数。尽管这看起来结果是同步返回的,其实根本就不是。同步返回的是一个future对象,当XHR相应返回的时候会填充进数据。鉴于AngularJS的数据绑定,我们可以使用future并且把它绑定到我们的模板上。然后,当数据到达时,我们的视图会自动更新。

单单依赖future对象和数据绑定不足以满足我们的需求,所以在这些情况下,我们需要添加一个回调函数来处理服务器的响应。PhoneDetailCtrl控制器通过在一个回调函数中设置mainImageUrl就是一个解释。

$resource服务通过添加更新和删除资源的方法来增强响应得到的对象。

一些更多的例子,请参照Cookbook

当你准备好使用AngularJS创建一个新项目时,我们推荐使用AngularJS种子项目来引导你的开发。

课程地址:http://www.ituring.com.cn/minibook/303

AngularJS入门教程(1~7)笔记

以下代码中包含了AngularJS的关键元素,正是我们需要学习的。




    
    My HTML File
    
    
    


Nothing here {{'yet' + '!'}}

代码在做什么呢?
在DOMContentLoaded事件触发时执行angular.js脚本,运行后将会寻找含有ng-app指令的标签,该标签即定义了AngularJS应用的作用域。在html中添加ng-app属性即说明整个html都是AngularJS脚本作用域。开发者也可以在局部使用ng-app指令,如在div上添加ng-app,则AngularJS脚本仅在该div中运行。

表达式,使用双大括号绑定:

Nothing here {{'yet' + '!'}}

AngularJS表达式Angular expression是一种类似于JavaScript的代码片段,AngularJS表达式仅在AngularJS的作用域中运行,而不是在整个DOM中运行。

小技巧:
表达式要出现在HTML的页面标题上。需要使用ngBind或者ngBindTemplate指令,因为它们在页面加载时对用户是不可见的:

Google Phone Gallery

侦听机制:一旦AngularJS应用引导完毕,它将继续侦听浏览器的HTML触发事件,如鼠标点击事件、按键事件、HTTP传入响应等改变DOM模型的事件。这类事件一旦发生,AngularJS将会自动检测变化,并作出相应的处理及更新。

AngularJS测试:
使得在AngularJS可以轻易地构建测试,来鼓励开发者多写它们。

写测试的时候,AngularJS的开发者倾向于使用Jasmine行为驱动开发(BBD)框架中的语法。尽管AngularJS没有强迫你使用Jasmine,但是我们在教程里面所有的测试都使用Jasmine编写。你可以在Jasmine的官方主页或者Jasmine Wiki上获得相关知识。

数据绑定:
这是AngularJS的一个核心特性。当页面加载的时候,AngularJS会根据输入框的属性值名字,将其与数据模型中相同名字的变量绑定在一起,以确保两者的同步性。
数据绑定和迭代器的时候所说的一样,无论什么时候数据模型发生了改变(比如用户在下拉菜单中选了不同的顺序),AngularJS的数据绑定会让视图自动更新。



  • 使用AngularJS的 $filter 函数来处理ngRepeat指令的输入。
    使用filter过滤器:filter函数使用query的值来创建一个只包含匹配query记录的新数组。

    ngRepeat会根据filter过滤器生成的记录数据数组来自动更新视图。整个过程对于开发者来说都是透明的。搜索特性是完全通过模板和数据绑定实现的。
    orderBy过滤器以一个数组作为输入,复制一份副本,然后把副本重排序再输出到迭代器。

    AngularJS在select元素和orderProp模型之间创建了一个双向绑定。而后,orderProp会被用作orderBy过滤器的输入。

    双向数据绑定:
    在控制器中把orderProp设置成了‘age’。所以绑定在从我们模型到用户界面的方向上起作用–即数据从模型到视图的绑定。
    现在当你在下拉菜单中选择“Alphabetically”,数据模型会被同时更新,并且手机列表数组会被重新排序。这个时候数据绑定从另一个方向产生了作用–即数据从视图到模型的绑定。

    服务:
    服务是通过AngularJS的依赖注入DI子系统来管理的。依赖注入服务可以使你的Web应用良好构建(比如分离表现层、数据和控制三者的部件)并且松耦合(一个部件自己不需要解决部件之间的依赖问题,它们都被DI子系统所处理)。
    $http仅仅是AngularJS众多内建服务中之一,这些服务可以处理一些Web应用的通用操作。AngularJS能将这些服务注入到任何你需要它们的地方。
    $http向Web服务器发起一个HTTP GET请求,服务器用json文件中的数据作为响应。(这个响应或许是实时从后端服务器动态产生的。但是对于浏览器来说,它们看起来都是一样的。

    为了使用AngularJS的服务,你只需要在控制器的构造函数里面作为参数声明出所需服务的名字,就像这样:

    function PhoneListCtrl($scope, $http) {...}
    

    当控制器构造的时候,AngularJS的依赖注入器会将这些服务注入到你的控制器中。当然,依赖注入器也会处理所需服务可能存在的任何传递性依赖(一个服务通常会依赖于其他的服务)。

    小提示:
    1、注意到参数名字非常重要,因为注入器会用他们去寻找相应的依赖。
    ‘$’前缀命名习惯,作为一个命名习惯,AngularJS内建服务,作用域方法,以及一些其他的AngularJS API都在名字前面使用一个‘$’前缀。不要使用‘$’前缀来命名你自己的服务和模型,否则可能会产生名字冲突。

    2、为了克服压缩引起的问题,只要在控制器函数里面给$inject属性赋值一个依赖服务标识符的数组,就像被注释掉那段最后一行那样:

    PhoneListCtrl.$inject = ['$scope', '$http'];
    

    3、只显示列表的前五。
    在PhoneListCtrl控制器中,把HTTP应答预处理一下,使得只显示手机列表的前五个。
    在$http回调函数里面使用如下代码:

    $scope.phones = data.splice(0, 5);
    

    ngSrc指令:
    为每条记录添加手机图片,如果我们仅仅用一个正常src属性来进行绑定(),浏览器会把AngularJS的{{ 表达式 }}标记直接进行字面解释,并且发起一个向非法urlhttp://localhost:8000/app/{{phone.imageUrl}}的请求。

    有了这个ngSrc指令会避免产生这种情况,使用ngSrc指令防止浏览器产生一个指向非法地址的请求。这个问题是由于浏览器会在遇到img标签的时候立刻向还未得到编译的URL地址发送一个请求,AngularJS只有在页面载入完毕后才开始编译表达式从而得到正确的图片URL地址。

    我们要把index.html模板转变成“布局模板”。这是我们应用所有视图的通用模板。其他的“局部布局模板”随后根据当前的“路由”被充填入,从而形成一个完整视图展示给用户。

    AngularJS中应用的路由通过$routeProvider来声明,它是$route服务的提供者。这项服务使得控制器、视图模板与当前浏览器的URL可以轻易集成。应用这个特性我们就可以实现深链接,它允许我们使用浏览器的历史(回退或者前进导航)和书签。

    依赖注入
    当应用引导时,AngularJS会创建一个注入器,我们应用后面所有依赖注入的服务都会需要它。这个注入器自己并不知道$http和$route是干什么的,实际上除非它在模块定义的时候被配置过,否则它根本都不知道这些服务的存在。注入器唯一的职责是载入指定的服务模块,在这些模块中注册所有定义的服务提供者,并且当需要时给一个指定的函数注入依赖(服务)。这些依赖通过它们的提供者“懒惰式”(需要时才加载)实例化。

    提供者是提供(创建)服务实例并且对外提供API接口的对象,它可以被用来控制一个服务的创建和运行时行为。对于$route服务来说,$routeProvider对外提供了API接口,通过API接口允许你为你的应用定义路由规则。

    App 模块
    AngularJS模块解决了从应用中删除全局状态和提供方法来配置注入器这两个问题。

    使用configAPI,我们请求把$routeProvider注入到我们的配置函数并且使用$routeProvider.whenAPI来定义我们的路由规则。

    phonecatApp.config(['$routeProvider',
      function($routeProvider) {
        $routeProvider.
          when('/phones', {
            templateUrl: 'partials/phone-list.html',
            controller: 'PhoneListCtrl'
          }).
          when('/phones/:phoneId', {
            templateUrl: 'partials/phone-detail.html',
            controller: 'PhoneDetailCtrl'
          }).
          otherwise({
            redirectTo: '/phones'
          });
    }]);
    

    在注入器配置阶段,提供者也可以同时被注入,但是一旦注入器被创建并且开始创建服务实例的时候,他们就不再会被外界所获取到。
    路由规则定义如下:
    当URL 映射段为/phones时,手机列表视图会被显示出来。为了构造这个视图,AngularJS会使用phone-list.html模板和PhoneListCtrl控制器。
    当URL 映射段为/phone/:phoneId时,手机详细信息视图被显示出来。这里:phoneId是URL的变量部分。为了构造手机详细视图,AngularJS会使用phone-detail.html模板和PhoneDetailCtrl控制器。
    $route.otherwise({redirectTo: ‘/phones’})语句使得当浏览器地址不能匹配我们任何一个路由规则时,触发重定向到/phones。

    $route服务使用路由声明/phones/:phoneId作为一个匹配当前URL的模板。所有以:符号声明的变量(此处变量为phoneId)都会被提取,然后存放在$routeParams对象中。

    为了让我们的应用引导我们新创建的模块,我们同时需要在ngApp指令的值上指明模块的名字。

    模板
    $route服务通常和ngView指令一起使用。ngView指令的角色是为当前路由把对应的视图模板载入到布局模板中。

    布局模板中没再添加PhoneListCtrl或PhoneDetailCtrl控制器属性!

    课程地址:http://www.ituring.com.cn/minibook/303