框架技术细节说明 must

该文档详细说明了基于Angular的机制及关键技术。

目录: - 路由机制 - 通过路由来切分页面模块 - Lazyload机制 - 指令 - 程序bootstrap - 数据绑定 - filters - 何时编写自定义指令 - controller之间的通信 - 依赖注入 - 统一的http请求拦截器 - 根据不同的页面,显示不一样的头部菜单选中 - 如何进行国际化 - 如何进行表单校验 - 动画 - 测试

路由机制

路由机制是指页面是如何根据url跳转的。如,访问#/home,该加载哪个页面模板,这个页面模板该哪个控制器来控制。

在SPA中,路由由前端控制,整个工程只有一个真正的页面。如,我们这只有一个index.html页面或者index.jsp页面。

<body ng-controller="CtrlApp" class="{{lang}}">
<div class="wrap">
<div ui-view="header" class="app-header"></div>
<div ui-view="body" class="app-body"></div>
</div>
<div ui-view="footer"></div>
</body>

在我们的工程中,前端路由交由ui-router组件控制。ui-router控制的路由其实是一个状态机,可以由一个状态跳转到另一个状态。每一个状态有url的映射,有views的映射。

// Home
$stateProvider.state('home', {
url: '/home',
views: {
'body': {
templateUrl: baseUrl + 'home/home.tpl.html',
},
header: headerConfig,
footer: footerConfig
}
});

上图,我们定义了一个状态home,对应的url是/home,加载的views有body、header、footer。再参照index.html,我们可以知道home.tpl模板会append到

中,header、footer也做相应的事情。 也就是说,当我们流转到home这个状态时,我们得到的是这样的页面。

<body>
<div class="wrap">
<div ui-view="header" class="app-header">
<!-- header content -->
</div>
<div ui-view="body" class="app-body">
<!-- home content -->
</div>
</div>
<div ui-view="footer">
<!-- footer content -->
</div>
</body>

如果需要Controller来控制页面,状态中还可以加上Controller依赖,如下

// Home
$stateProvider.state('home', {
url: '/home',
views: {
'body': {
templateUrl: baseUrl + 'home/home.tpl.html',
controller: 'CtrlHome',
resolve: {
ctrl: $couchPotatoProvider.resolveDependencies(['home/CtrlHome'])
}
},
header: headerConfig,
footer: footerConfig
}
});

为什么要加上controller: 'CtrlHome'还要加上$couchPotatoProvider.resolveDependencies(['home/CtrlHome'])这句呢,详见 Lazyload机制

通过路由来切分页面模块

有时候页面会比较复杂,我们希望将页面拆分。考虑下如下页面结构 

可以看到这个页面看似简单,但其实包含了很多内容:头部、轮播、数字统计、推荐创意、最热创意五个部分。内容很多,但相关性却没有。好,那我们不希望一团糟,所以我们做出改变。我们希望把每一块的模板和业务逻辑拆分开来,这里我们用路由帮我们做这件事。看下下面代码

// Home
$stateProvider.state('home', {
url: '/home',
abstract: true,
views: {
body: {
templateUrl: baseUrl + 'home/home.tpl.html',
controller: 'CtrlHome',
resolve: {
ctrl: $couchPotatoProvider.resolveDependencies(['home/CtrlHome'])
}
},
header: headerConfig,
footer: footerConfig
}
})
.state('home.facade', {
url: '',
views: {
'homeBanner': {
templateUrl: baseUrl + 'home/banner/homeBanner.tpl.html',
controller: 'CtrlHomeBanner',
resolve: {
ctrl: $couchPotatoProvider.resolveDependencies(['home/banner/CtrlHomeBanner'])
}
},
'homeCount': {
templateUrl: baseUrl + 'home/count/homeCount.tpl.html',
controller: 'CtrlHomeCount',
resolve: {
ctrl: $couchPotatoProvider.resolveDependencies(['home/count/CtrlHomeCount'])
}
}
});

我们可以看到,我们将home这个状态定义为一个抽象的状态abstract,然后定义一个home的子状态叫home.facade,url为'',这样访问#/home这样的url,就会触发home的路由,以及home.facade路由。注意到home.facade路由里定义了homeBannerhomeCount,在home.tpl.html这样定义

<div>
<div ui-view="homeBanner"></div>
<div ui-view="homeCount"></div>
</div>

这样,就能把页面拆分成子模块。如果在别的页面也需要引入这些模块,只需要在路由里面定义,然后在父级模板里面声明需要的ui-view,就能够复用了。

Lazyload机制

Lazyload机制可以使状态流转的时候去按需加载页面所需的资源,而不是在一开始就加载。AngularJS本身没有实现lazyload,这样的话就导致了webapp的性能受影响,在首次加载的时候就要加载全部的依赖。

//定义angular模块
var app = angular.module('app', ['scs.couch-potato', 'ui.router', 'ui.bootstrap', 'ui.bootstrap.tpls', 'chieffancypants.loadingBar']);

上述代码片段展示了Angular module的加载机制。而在实际应用中,特别是业务系统,一般是业务比较繁杂,功能模块比较多,在这种情况下,angular module的默认机制就不符合我们要求了。

因此,我们采用requirejs + angular-couch-patato来实现Lazyload。angular-couch-patato负责托管angular的各种注册,包括controller,directive,filter,service等。平时我们写

angular.module('app').controller(function () {
//ctrl code here
});

为了使用lazyload,现在我们要这样写

angular.module('app').registerController(function () {
//ctrl code here
});

将部件注册,这样所有的部件都交给couch-patato来管理,在需要的时候(下文会提到如何使用)。加上使用requirejs,app是统一管理的angular module,我们就这样写:

define(['app'], function(app) {
app.registerController(function () {
//ctrl code here
});
})

首先加载依赖app,app是之前定义好的angular module,如var app = angular.module('app', ['scs.couch-potato', 'ui.router', 'ui.bootstrap', 'ui.bootstrap.tpls', 'chieffancypants.loadingBar']);。然后在app上注册controller等。

上文路由机制部分提到,在注册路由的时候需要调用$couchPotatoProvider.resolveDependencies来实现lazyload。这里设计到couchPatato实现的一个关键,就是利用angular在路由里面的resolve机制,这里的resolve是指,路由在触发之前,必须拿到resolve里面定义的所有值。这样couchPatato就有机会去获取定义的依赖,而在每个依赖里面,会将controller注册到app,然后在路由触发后,定义的controller才得以调用。

指令

Directive。angular的一大特性,他可以定义一个标签,或者说赋予dom一定的功能,他也是angular程序里面唯一操作dom的地方。一处定义多处使用,是组件化的一个利器!


如何定义指令? javascript app.registerDirective('dragable', function() { return { link: function(scope, element, attrs) { //code make it dragable } } }); 上述代码定义了一个指令,这个指令有赋予拖动功能的能力。

如果在模板里面声明,说,需要这个能力,他就有了这个能力 html <div dragable></div> 这个div可以拖动了!

没错就是这么神奇,当你还在等待html5规范出来,当你还在等各个浏览器厂商都实现这个规范的时候,angular帮你做了这件事,在技术上解决了这问题。

angular的zen之一,声明式优于命令式!当你声明了,你就拥有了,这是多么的nice。具体的directive教程请到https://docs.angularjs.org/guide/directive。


常用的angular的内置指令 - ng-repeat 迭代 - ng-if 控制显示 - ng-model 数据模型绑定

程序bootstrap

angular是如何解析指令的,是如何获得控制权的?(因为什么还有入口,相信很多后端选手都没有这个概念,他们从来不需要关心main函数的东西。但web不一样,web是先去加载html,渲染完成之后加载js,然后js才能托管整个app)

angular的bootstrap分两种,手动初始化和自动初始化。

自动初始化方法,在html标签定义 ng-app指令,在angular.js加载完成之后,会compile整个html,将ng-app所在的dom作为根来生成动态的html。 html <html ng-app>

</html>

手动初始化 javascript angular.element(document).ready(function() { angular.bootstrap(document, ['myApp']); });

我们是选择在main.js里面手动初始化angular的,这样我们可以做更多的事情,比如在初始化加载一些我们需要的东西,比如权限,用户。

数据绑定

在angular里,数据绑定是指双向绑定。双向绑定是指视图改变的时候,对应的数据发生改变;反之亦然。数据绑定在很多mvc框架里面都有,但是angular里的数据绑定是好用的一种。看下我们需要写什么:

<div ng-controller="Ctrl">{{name}}</div>
function Ctrl($scope) {
$scope.name = '炸油条';
}

一旦name这个数据发生变化,就直接在视图上反映出来了,中间的过程全部交给angular来处理。

filters

filter用来format展现的数据,这样用

<div class="date">{{date | dateFilter}}</div>

dateFilter就是日期的过滤器,这样数据源就不用变,我们考虑的只是怎么展现,下面是个自定义filter的写法

app.registerFilter('PortionFilter', function () {
return function ( input ) {
if ( input <= 1 ) {
return ( input * 100 ).toFixed( 2 ) + '%';
}
return input;
};
});

何时编写自定义指令

  • 指令其实可以理解为组件化的思想,当有多个场景需要相同的功能时,应该编写一个指令。
  • 当不得不进行dom操作时,编写指令
  • 当需要扩展dom功能时,编写指令
  • 如果开源的社区有更好的相同功能的指令,使用或者参考之

controller之间的通信

有时候,ctrl之间需要通信。我们根据ctrl的不同关系做不同的处理。 当ctrl具有父子或者祖宗关系时,我们采用$broadcast,$emit来进行通信。$broadcast往下传播,$emit反之。 当不具有直接层级关系的时候,我们通过他们共同的父级ctrl来进行交互,因为无论ctrl如何分布,他们最终都有一个共同的root。具体的做法是在父级ctrl定义一个他们共同维护的变量,然后监听这一变量的变化,最终通过$broadcast,$emit来进行通信。父级ctrl等于做了个中间人。

使用. 来进行通信 https://egghead.io/lessons/angularjs-the-dot

依赖注入

angular最好用的特性之一。依赖注入也是声明式优于命令式的一个体现。看下面代码

function Ctrl($scope, $http) {
$scope.doSth();
$http.doSth();
}

我们可以看到,我们在想使用$http服务的时候,仅仅需要声明一下,我们就可以获得该引用。编写angular的service非常简单

app.registerService('util', function(){
return {
compress: function A(),
sthElse: function(){
console.log(1);
}
};
});

有了这个能力,我们就可以精简ctrl里面的代码,把可重用的或者冗长的业务代码抽离出来,使ctrl更多的关注业务流程,具体的业务代码维护在service里面。

统一的http请求拦截器

angular允许你注册http请求拦截器。有什么用么?有,在想统一对某类请求做统一处理的时候非常有用。如希望后端返回404的情况下,跳转到404页面。这样,就不用在每一个http请求的处理函数都加上这段。还可以针对ie做防缓存处理,及判断,如果是ie低版本浏览器,在每个get请求都加上时间戳,nice!

play ground:youtube的顶部加载效果!

根据不同的页面,显示不一样的头部菜单选中

比如说在首页,命中的是首页,当到列表页的时候,头部命中的应该是列表。 如何实现? 1. 得在相应的controller里面定义一套命中规则,规则规定了哪个路由应该高亮哪个item 2. 定义一个service来管理头部选中,在每个路由级的控制器中调用service来改变高亮

如何进行国际化

什么是国际化,国际化是根据不同的语种,来显示不同的文案。思路,我们根据不同的语种来加载不同的资源文件。如,当是中文的时候我们使用zh-cn.json,英文的时候我们使用us-en.json。

在angular中,filter负责显示。所以我们在filter上做文章。对于静态的文本,我们用一个key来代替如 '语言',我们用 'lang'来替换,在html我们这样写 {{'lang' | translate}}, translate的filter的作用就是从我们之前加载的语言包找到'lang'这个key所对应的文本。

{
"lang": "中文哦"
}

那对于动态的数据应该怎么做呢,这个就需要跟后端做一定的约定。 - 值列表,我们直接去翻译好的i18n数据 - 枚举型,约定返回具有意义的字符串,如'success','failed'。然后我们在前端做一定的拼接,如{{'type' + type | translate}},然后我们在资源文件里加入typesuccess和type_failed的key,就ok了~ - 这里我们鼓励rd返回具有意义的字符串,而不是0,1,2这种


另外,国际化不仅仅影响了文本,还可能影响布局,就是说不同的语言环境,页面长得不一样,这种情况下,我们的处理类似换肤,我们在body上绑定一个语言环境的class,针对特殊的需求,我们做不同的处理。

如果国际化影响了业务逻辑,那建议对模块进行语言环境的逻辑判断。

如何进行表单校验

表单校验是个世纪难题,同学们可能都用过各种jquery表单校验的插件,是不是没几个好用的。。

表单校验为什么难,是因为你不仅仅需要校验,而且你需要不同的校验的展示方式。所以往往会根据具体情况来进行处理,下面说一种比较常见的校验处理方法。

<div class="material-url form-group">
<label class="control-label col-md-2">
url:
</label>
<div ng-class="{'has-error':e.materialValidator && formAd.materialUrl.$error.required}">
<div class="col-md-3">
<input placeholder="" type="url" class="form-control" name="materialUrl"
ng-model="e.adSolutionContent.materialUrl" />
</div>
<div class="col-md-3 help-block" ng-show="formAd.materialUrl.$error.url">
{{'AD_CONTENT_NO_URL' | translate}}
</div>
</div>
</div>

input需要输入合法的url,url不合法的会马上在右侧显示错误提示。这点angular非常轻松的就做到了。。。然后我们可以让提交按钮无效

<button ng-show="form.$valid" />

但是变态的需求又来了,按钮永远可以点。。。好,那我们在提交的时候做判断。我们同样通过form.$valid很容易的判断form有没有ok,如果ok,那ok,否则我们要focus到第一个出错的input。那我们就只能悲催的挨个判断input是否合法,然后写个指令控制下focus动作。

综上表单校验,根据不同的需求做不同的处理。

动画

angular推荐的是用css3来进行动画处理,因为angular可以很方便的操纵数据,也就能方便的切换css,再经由css3来动起来。https://docs.angularjs.org/guide/animations ,通过angular的教程可以搭建list的简单动画。

测试

angular程序是可以做测试的,这非常神奇,原因是angular将所有的dom操作代码都限制在了指令上,而dom操作代码是很难进行测试的。前面提到的尽量将逻辑处理的代码封装到service,这样就可以对service进行测试,保证代码的质量。

angular的测试还得力于他的依赖注入,因为这正是跑测试的关键。测试用例会首先创建一个angular-mock,假的壳,然后利用里面的$injector.get()来加载想要测试的service,这个时候已经能获得service的实例了,就可以进行测试。 angular推荐的是jasmine。

angular 使用概术的更多相关文章

  1. Zabbix概术及基础介绍(一)

    一.Zabbix介绍 Zabbix 是由Alexei Vladishev创建,目前由Zabbix SIA在持续开发和支持.Zabbix 是一个企业级的分布式开源监控方案.Zabbix是一款能够监控各种 ...

  2. spring cloud 2.x版本 Gateway熔断、限流教程

    前言 本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka ...

  3. spring cloud 2.x版本 Gateway自定义过滤器教程

    前言 本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka ...

  4. 从flask视角学习angular(一)整体对比

    写在前面 前端框架完全不懂. 看着angular中文官网的英雄编辑器教程和核心知识,用偷懒的类比法,从flask django的角度 记录一下自己对angular的理解. 作为工科的武曲,自己的体会是 ...

  5. Angular 4+ 修仙之路

    Angular 4.x 快速入门 Angular 4 快速入门 涉及 Angular 简介.环境搭建.插件表达式.自定义组件.表单模块.Http 模块等 Angular 4 基础教程 涉及 Angul ...

  6. Angular杂谈系列1-如何在Angular2中使用jQuery及其插件

    jQuery,让我们对dom的操作更加便捷.由于其易用性和可扩展性,jQuer也迅速风靡全球,各种插件也是目不暇接. 我相信很多人并不能直接远离jQuery去做前端,因为它太好用了,我们以前做的东西大 ...

  7. Angular企业级开发(5)-项目框架搭建

    1.AngularJS Seed项目目录结构 AngularJS官方网站提供了一个angular-phonecat项目,另外一个就是Angular-Seed项目.所以大多数团队会基于Angular-S ...

  8. TypeScript: Angular 2 的秘密武器(译)

    本文整理自Dan Wahlin在ng-conf上的talk.原视频地址: https://www.youtube.com/watch?v=e3djIqAGqZo 开场白 开场白主要分为三部分: 感谢了 ...

  9. angular实现统一的消息服务

    后台API返回的消息怎么显示更优雅,怎么处理才更简洁?看看这个效果怎么样? 自定义指令和服务实现 自定义指令和服务实现消息自动显示在页面的顶部,3秒之后消失 1. 显示消息 这种显示消息的方式是不是有 ...

随机推荐

  1. Linux硬链接和软连接详解

    硬链接: 硬链接是通过索引节点inode来进行链接的(关于inode,http://www.cnblogs.com/ZGreMount/p/7653307.html).在Linux(ext2,ext3 ...

  2. 深度学习入门篇--手把手教你用 TensorFlow 训练模型

    欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~ 作者:付越 导语 Tensorflow在更新1.0版本之后多了很多新功能,其中放出了很多用tf框架写的深度网络结构(https://git ...

  3. cocos2dx - Sqlite简单封装使用

    前言: 一般游戏需要在手机上记录一些简单的信息,用来保存游戏的进度,玩家的分数等.SQLite作为轻量级.跨平台的关系型数据库,相当适合用于游戏数据的存储. 由于没有加密,有安全性问题,数据上还需要自 ...

  4. spring cloud+dotnet core搭建微服务架构:配置中心续(五)

    前言 上一章最后讲了,更新配置以后需要重启客户端才能生效,这在实际的场景中是不可取的.由于目前Steeltoe配置的重载只能由客户端发起,没有实现处理程序侦听服务器更改事件,所以还没办法实现彻底实现这 ...

  5. (10.19)Java小作业

    在java的学习过程中数组的版块也是十分重要的,包括一些教程也会在这个知识点花上更多的时间来讲解,足以证明 这个知识点的重要性,今天想和大家分享一道学习数组过程中不可避免的求最值题. 已知一个整形数组 ...

  6. configparser模块(拷贝)

    该模块适用于配置文件的格式与windows ini文件类似,可以包含一个或多个节(section),每个节可以有多个参数(键=值). 创建文件 来看一个好多软件的常见文档格式如下: [DEFAULT] ...

  7. JavaScript 开发人员需要知道的简写技巧

    本文来源于多年的 JavaScript 编码技术经验,适合所有正在使用 JavaScript 编程的开发人员阅读. 本文的目的在于帮助大家更加熟练的运用 JavaScript 语言来进行开发工作. 文 ...

  8. 【转】C语言产生随机数

    原文地址:http://www.cnblogs.com/xianghang123/archive/2011/08/24/2152404.html 在C语言中,rand()函数可以用来产生随机数,但是这 ...

  9. PHP四种基本排序算法

    PHP的四种基本排序算法为:冒泡排序.插入排序.选择排序和快速排序. 下面是我整理出来的算法代码: 1. 冒泡排序: 思路:对数组进行多轮冒泡,每一轮对数组中的元素两两比较,调整位置,冒出一个最大的数 ...

  10. LeetCode 202. Happy Number (快乐数字)

    Write an algorithm to determine if a number is "happy". A happy number is a number defined ...