框架技术细节说明 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. 数据库的优化(表优化和sql语句优化)

    在这里主要是分为表设计优化和sql语句优化两方面来实现. 首先的是表设计优化: 1.数据行的长度不要超过8020字节.如果是超过这个长度的话这条数据会占用两行,减低查询的效率. 2.能用数字类型就不要 ...

  2. iKcamp出品|微信小程序|工具安装+目录说明|基于最新版1.0开发者工具初中级教程分享

    iKcamp官网:http://www.ikcamp.com 访问官网更快阅读全部免费分享课程:<iKcamp出品|全网最新|微信小程序|基于最新版1.0开发者工具之初中级培训教程分享>. ...

  3. iOS多线程基本使用

    大家都知道,在开发过程中应该尽可能减少用户等待时间,让程序尽可能快的完成运算.可是无论是哪种语言开发的程序最终往往转换成汇编语言进而解释成机器码来执行.但是机器码是按顺序执行的,一个复杂的多步操作只能 ...

  4. Java IO(IO流)-1

    IO流 第一部分 (outputStream/InputStream Writer/Redaer) IO流对象中输入和输出是相辅相成的,输出什么,就可以输入什么. IO的命名方式为前半部分能干什么,后 ...

  5. C# post提交

    WebForm 前台 <asp:Button ID="Button1" runat="server" Text="Button" On ...

  6. AOP的实现的几种方式

    1.静态代理,实现代码如下,实际上是对装饰器模式的一种应用 interface UserManager { public void addUser(); } class UserManagerImpl ...

  7. Python 3 使用venv创建虚拟环境

    Python 3.3以上使用venv来代替了原来Python2使用的virtualenv创建虚拟环境. 虚拟环境的作用是使得不同项目的Python包之间不会相互干扰,避免了由此产生的各种问题. 现在演 ...

  8. 使用Xmanager通过XDMCP连接远程Centos 7 (摘自xmanager官方博客)

    Using Xmanager to connect to remote CentOS 7 via XDMCP Gnome in CentOS 7 tries to use local hardware ...

  9. Internal类

    C#中一个类中的成员有四种修饰级别: public:完全开放,谁都能访问. private:完全封闭,只有类自身可以访问. internal:只对相同程序集,或使用InternalVisibleToA ...

  10. 使用SimpleXML解析xml文件数据

    最近工作要求从一个XML文档中批量读取APK应用数据,自然想到用SimpleXML.经过一段时间摸索,终于成功解析,现在将思路以及代码做下记录: xml文件格式大致如下: <?xml versi ...