Skip to content

Latest commit

 

History

History
172 lines (110 loc) · 7.27 KB

01.md

File metadata and controls

172 lines (110 loc) · 7.27 KB

基于AngularJS构建Web应用门户

我们常常遇到有构建Web应用门户的需求,

大型单页应用要做的第一件事就是分层架构。分层架构有利于把职责清晰化,每块可以单独做测试。

数据模型层必须独立出来,所以有些Angular文章推荐给所有service建一个module,然后把一切包含数据模型的service都放进去。可以这么做,也可以再细分。

分层做完,就要考虑懒加载的问题了。

我们看看一个典型的场景,一个工作台,或者说门户界面,上面能够放很多小部件,类似iGoogle那样,用户可以任意加已有的部件,这些部件都是基于某种约定,由第三方开发人员完成。这种如果在单页应用里,该怎么实现呢?

企业应用门户的设计,其实是一个很考验规划水准的事。因为它首先要集成别人,还要考虑如何被别人集成。它的设计思路直接影响到一大堆代码按照什么规范进行开发。挂在这个门户上的业务模块,有的很简单,不会影响别人,有的可能会影响别人,要想个办法把它隔离起来,还有的本身就要跟别人通讯。

先来看部件有些什么可以做的事。他可以有界面,有逻辑,有样式。这些都可以分别动态加载出来,比如HTML片段可以ng-include或者$get过来append,js文件可以require,css可以行间也可以动态加rule,因为这些部件是要跟我们主界面在同一个页面作用域内,所以要尽量营造隔离的环境。

#1. 无逻辑的界面部件

如果是纯HTML话很好办,它直接拿来放在某容器里就可以了,互相影响不到,直接搞个ng-include把它包含到主界面就可以了。

<div ng-include src="'partial/simple.html'"></div>

我们来写个directive专门做部件加载器吧。先来个最简单的:

portal.directive("htmlLoader", function ($http) {
	return function (scope, element, attrs) {
		var url = attrs.url;
		$http.get(url).success(function (result) {
			element.html(result);
		});
	};
});

用的时候也很简单:

<div html-loader url="partial/simple.html"></div>

可以看到,纯HTML模版加载非常简单,只要取过来放置到元素上就好了。

#2. 带行间逻辑的界面部件

什么是行间逻辑呢?意思是这一段JavaScript逻辑只作用于当前界面片段,出于某些原因,这些逻辑必须紧跟当前的界面,需要在全页面加载出来之前就能执行,比如某些搜索,只要搜索框一出来就应当能操作,这就是一种典型的需求。

我们看看刚才的这种加载器,里面用了element.html(result),这么做显然不能执行这个result中含有的JavaScript代码,怎么办呢?

我们想个办法来把js分离出来:

#3. 有独立命名空间的界面部件

JavaScript的话,最基本的就是避免全局变量,在Angular体系中,还需要作些特殊的考虑。我们知道,Angular里面,第一级组织单元是module,但它这个module的概念跟AMD那种module的不同,如果说AMD的module相当于Java Class的级别,Angular的要相当于package了。

这就有了我们的第一个问题:部件里面的JavaScript代码跟主界面的在不在一个module内?如果是别的框架,这不是个问题,但这货是Angular,有些纠结,这里面还分两种情况:

  • 部件很独立,相互无任何协作关系
  • 部件之间有协作,包括可能共享代码或者有通信

第一种很好办,我们可以让这个部件自己定义ng-app,这样它就是一个独立隔离的环境了。来写一下代码:

portal.module("mis").directive("appLoader", function ($http) {
	return function (scope, element, attrs) {
		var module = attrs.module;
		var url = attrs.url;
		var scripts = attrs.scripts.split(",") || [];

		$script(scripts, function () {
			scope.$apply(function () {
				$http.get(url).success(function (result) {
					var elem = angular.element(result);
					angular.bootstrap(elem, [module]);
					element.append(elem);
				});
			});
		});
	};
});

部件代码:

clock.html

<div ng-controller="ClockCtrl">
	<span ng-bind="now"></span>
</div>

clock.js

angular.module("widgets", []);

angular.module("widgets").controller("ClockCtrl", function($timeout, $scope) {
	$scope.now = new Date();
	updateLater();

	var timeoutId;
	function updateLater() {
		$scope.now = new Date();
		timeoutId = $timeout(function() {
			updateLater();
		}, 1000);
	}
});

使用的时候:

<div app-loader url="partial/clock.html" module="widgets" scripts="js/widgets/clock.js"></div>

分析这段代码,可以看到,这里要做两件事:加载依赖项的js文件,加载html片段,然后简单粗暴地调用了Angular的bootstrap方法,这是什么意思呢?

Angular可以用两种方式来初始化,第一种是通过在标签上写ng-app,然后页面加载的时候会自动做这个事情,第二种是直接调用angular.bootstrap方法,手动初始化。

我们新加载的这个部件因为拥有独立命名空间,所以它的js代码里面也会很独立,在clock.js里面新定义了一个module叫做widgets,整个部件都运行在这个命名空间下。

到这里,事情也没结束,因为我们这里只演示了一个

#4. 跟主界面共享命名空间的部件

第二种很麻烦,因为Angular的module依赖关系想要动态加很复杂,所以我们只能约定已有的module,然后让部件的js从这个module上加自己的代码。

portal.directive("partialLoader", function ($http, $compile) {
	return function (scope, element, attrs) {
		var module = attrs.module;
		var url = attrs.url;
		var scripts = attrs.scripts.split(",") || [];

		$script(scripts, function () {
			scope.$apply(function () {
				$http.get(url).success(function (result) {
					element.html(result);
					$compile(element.contents())(scope);
				});
			});
		});
	};
});

使用的时候:

<div partial-loader url="partial/goods.html" scripts="js/order/goods.js"></div>

有时候会有

#5. 已载入文件的缓存

#6. 样式的隔离

CSS的隔离也比麻烦,因为样式是全局生效的,我们必须约定某种规则,让第三方开发者的样式只能挂在他这个部件下,无论是行间样式还是引入的外部样式,都不能起到这个限制的作用。所以,必须使用更苛刻的手段。

思路肯定是要从某种元素往下写的,我们给他一个特定元素,他以这个为基准,所有自己的CSS都定义在这个元素的选择符之下。那么,这些不同的部件之间,又要如何区分呢?

想想,部件之间其实只有很少东西能用于区分,比如说全局唯一的部件名,所以,我们以此为依据,这样来约定:

  • 在主界面中包含部件的容器,生成一个如下的元素:

这样

<div data-appname="sampleApp"></div>

真实的部件HTML代码会被放置在其中,举例来说,这个部件的HTML是:

<ul>
    <li>Apple</li>
    <li>Pear</li>
</ul>

它的CSS就可以这么写:

div[data-appname="sampleApp"] ul {

}