前后端分离实践AngularJS/ReactJS/VueJS组件化开发示例与对比(二)——Angular构建项目示例

Angular 1.5x以上增加了Component方法专门用于构建组件,使Angular下真正意义上的组件化开发得到支持。在Angular下利用Component方法来构建组件非常简单,Component方法的具体使用可参考《Angular 1.5x Component 组件化开发》

本实例将使用Angular和Angular的Component方法构建一个典型企业站的架构和新闻中心模块。

一、程序结构

1、页面

index.html:新闻模块首页(新闻列表页)入口文件,引入资源文件,指定Angular模块,引入新闻列表页组件;

ngnewscontent.html:新闻详情页文件,引入资源文件,指定Angular模块,引入新闻详情页组件;

main.js:js逻辑文件,定义Angular模块,定义组件,新闻列表页和新闻详情页的控制器和逻辑等。

main.css:样式文件

注:这里主要讲的是Angular组件化构建项目实例及与React、Vue框架的对比,为了代码简洁,没有引入路由,因此,新闻列表页和新闻详情页都是一个独立的入口文件,在实际项目开发,会结合网站其他模块,利用路由来实现页面间的跳转和数据流通。

 

2、资源和库依赖

Angular、ngResource、ngSanitize、pure.css

 

二、项目页面构建

1、新闻列表页

<!doctype html>
<html ng-app="myNgApp">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Angular</title>
        <meta name="description" content="">
        <link rel="apple-touch-icon" href="apple-touch-icon.png">
        <link rel='stylesheet' href='lib/css/pure-min.css'>
        <link rel="stylesheet" href="css/main.css">
    </head>
    <body ng-controller="mainCtrl">
        <div class="container">
            <header-ng-com callback="search(searchText)"></header-ng-com>   
            <menu-ng-com menus="menus"></menu-ng-com>
            <div class="main">
                <article class="newsList">
                    <section class="newsItem" ng-show="issearch">
                        <p>共为您找到<span>{{count}}</span>个符合关键词"<span>{{searchText}}</span>"的条目</p>
                    </section>
                    <newslist-ng-com news="news" class="newsList" hasmore="hasmore" callback="loadmore(page)"></newslist-ng-com>
                </article>
                <sidebar-ng-com random-news="randomNews"></sidebar-ng-com>
            </div>
            <footer-ng-com></footer-ng-com>
        </div>
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22lib%2Fjs%2Fangular%2Fangular.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22lib%2Fjs%2Fangular%2Fangular-resource.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22lib%2Fjs%2Fangular%2Fangular-sanitize.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22js%2Fangular%2Fmain.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
    </body>
</html>

上面代码第14-27行是主要布局和展示实现代码,其中包含了头部组件header-ng-com、菜单组件menu-ng-com、新闻列表组件newslist-ng-com、边栏组件sicebar-ng-com以及底部组件footer-ng-com。每个组件根据组件内部要求和功能不同传递了一些参数。代码第28-31行引入了依赖库文件和主JS文件。

 

2、新闻详情页

<!doctype html>
<html ng-app="myNgApp">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title></title>
        <meta name="description" content="">
        <link rel="apple-touch-icon" href="apple-touch-icon.png">
        <link rel='stylesheet' href='lib/css/pure-min.css'>
        <link rel="stylesheet" href="css/main.css">
    </head>
    <body ng-controller="mainCtrl">
        <div class="container">
            <header-ng-com callback="search(searchText)"></header-ng-com>   
            <menu-ng-com menus="menus"></menu-ng-com>
            <div class="main">
                <article class="newsContent" ng-controller="contentCtrl">
                    <news-content newscontent="newsContent"></news-content>
                </article>
                <sidebar-ng-com random-news="randomNews"></sidebar-ng-com>
            </div>
            <footer-ng-com></footer-ng-com>
        </div>
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22lib%2Fjs%2Fangular%2Fangular.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22lib%2Fjs%2Fangular%2Fangular-resource.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22lib%2Fjs%2Fangular%2Fangular-sanitize.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22js%2Fangular%2Fmain.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
    </body>
</html>

正如前面所说,本示例不使用路由,因此,新闻详情页面与新闻列表页面类似,都是一个独立的文件,不同之处在于主内容区域变为单个特定新闻详情,使用news-content组件来显示。

 

3、样式文件

body{background-color: #f8f8f8;margin: 0;}
.container{margin:0 auto; width:1000px;}

img{display: block;}
a{text-decoration: none;}

/************************************************************/
/************************************************************/
/************************************************************/
/************************************************************/
/********************* 添加新闻资讯 *************************/
/************************************************************/
/************************************************************/
/************************************************************/
/************************************************************/
h1{width:100%; height: 90px; background-color: #dd3333; color:#fff; line-height: 90px; text-align: center; margin:0;font-family: '微软雅黑'; font-weight: normal; margin-bottom: 20px;}
.pure-control-group{display: flex; justify-content: center; align-items: center;}
.pure-form-aligned .pure-control-group label{width:4rem;}
.pure-control-group input{flex:0.96;}
.pure-control-group textarea{flex:0.96;}
.richTextEditor{flex:0.92;border:1px solid #eee;}
.pure-form button{margin: 0 auto;}

.news-content{padding:20px;}
trix-editor{min-height: 200px !important;}

/************************************************************/
/************************************************************/
/************************************************************/
/************************************************************/
/********************* 前台页面布局 *************************/
/************************************************************/
/************************************************************/
/************************************************************/
/************************************************************/
.main{display: flex;flex-direction: row; justify-content: space-between;margin-bottom: 10px;}
header{width:100%;overflow: hidden;height: 100px;display: flex; justify-content: space-between; align-items: center;}
nav{width:100%;overflow: hidden;height: 50px;background-color: #444; margin:10px 0 20px 0;}
article{flex:1;min-height: 600px;}
aside{width:300px; margin-left: 10px;}
footer{width:100%;overflow: hidden;height: 150px;background-color: #444;display: flex;justify-content: center; align-items: center;flex-direction: column;}

header .ngLogo{width:225px;margin-left: 10px;}
nav ul{margin:0;display: flex;flex-direction: row;padding:0;padding-left: 50px; padding-right: 50px;}
nav li{list-style: none;display: inline-block;flex:1;}
nav li a{display:block;height:50px; line-height:50px;color:#fff;flex:1; text-decoration: none;text-align: center;}
nav li a:hover{background-color: #dd3333;}
nav li .active{background-color: #dd3333;}

footer .footerLogo{display: block;width:200px;height: auto;}
footer p{text-align: center; color:#ddd;}

/*搜索框*/
.cf:before, .cf:after{content:""; display:table;}
.cf:after{clear:both;}
.cf{zoom:1;}    
 /* Form wrapper styling */
.search-wrapper {width: 280px;	margin-right: 10px;	box-shadow: 0 1px 1px rgba(0, 0, 0, .4) inset, 0 1px 0 rgba(255, 255, 255, .2);}
/* Form text input */
.search-wrapper input {width: 198px;height: 20px;padding: 10px 5px;	float: left;font: bold 15px 'lucida sans', 'trebuchet MS', 'Tahoma';border: 0;background: #444;	color:#fff;	border-radius: 3px 0 0 3px;}
.search-wrapper input:focus {outline: 0;background: #444;box-shadow: 0 0 2px rgba(0,0,0,.8) inset;}
.search-wrapper input::-webkit-input-placeholder {color: #eee;font-weight: normal;font-style: italic;}
.search-wrapper input:-moz-placeholder {color: #eee;font-weight: normal; font-style: italic;}
.search-wrapper input:-ms-input-placeholder {color: #eee;font-weight: normal; font-style: italic;}    
/* Form submit button */
.search-wrapper button {overflow: visible;position: relative;float: right;border: 0;padding: 0;cursor: pointer;	height: 40px;width: 72px;font: bold 15px/40px 'lucida sans', 'trebuchet MS', 'Tahoma';	color: white;text-transform: uppercase;	background: #D83C3C;border-radius: 0 3px 3px 0;text-shadow: 0 -1px 0 rgba(0, 0, 0, .3);}
.search-wrapper button:hover{background: #e54040;}   
.search-wrapper button:active,.search-wrapper button:focus{background: #c42f2f; outline: 0;}
.search-wrapper button:before {content: ''; position: absolute; border-width: 8px 8px 8px 0;border-style: solid solid solid none;border-color: transparent #d83c3c transparent;top: 12px;left: -6px;}
.search-wrapper button:hover:before{border-right-color: #e54040;}
.search-wrapper button:focus:before,.search-wrapper button:active:before{border-right-color: #c42f2f;}      
.search-wrapper button::-moz-focus-inner {border: 0; padding: 0;}    

.newsList{flex:1;}
.newsList .newsItem{display: flex;flex-direction: row;padding:10px; border-bottom:1px solid #eee;}
.newsList .newsItem img{width: 220px; height: 122px; margin-right: 16px;}
.newsList .newsItem div{flex:1; display: flex;flex-direction: column; padding-right: 10px;}
.newsList h3,.newsList h4, .newsList h5{margin:0; padding:0; font-weight: normal;}
.newsList h3{font-family: '微软雅黑'; color:#333;max-height:52px;text-overflow: -o-ellipsis-lastline;overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: 2;-webkit-box-orient: vertical;}
.newsList .newsItem h4{margin:10px 0;color:#666;font-size:14px;height:32px;text-overflow: -o-ellipsis-lastline;overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: 2;-webkit-box-orient: vertical;}
.newsList .newsItem h5{font-size: 12px;color:#666;}
.newsItem span{color:#dd3333; font-weight: 600;}
.newsList .loadmore{text-align: center;height:40px;line-height: 40px;margin-top: 10px;cursor: pointer; background-color: #eee;}
.newsList a, .newsList a:visited{color:#444;}
.newsList a:hover{color:#dd3333 !important;}

.newsContent{flex:1;padding-right: 30px;margin-bottom: 2rem;}
.newsContent h3 {font-size: 30px;}
.newsContent h4, .newsContent h5{display: inline;font-weight: normal;margin-right: 10px;}
.newsContent hr{ height:0px; border-top:1px solid #ddd; border-right:0px; border-bottom:0px; border-left:0px; }
.newsContent .content{margin-top: 2rem;margin-bottom: 2rem;text-justify : auto; text-align:justify;}
.newsContent .keyword span{margin-right:10px; background-color: #dd3333; color: #fff; padding:6px 10px; text-align: center; border-radius: 3px; line-height: 100%;}

.randomNews{width:100%;margin-top: 10px; border:1px solid #eee;}
.randomNews .newsItem{display: flex; padding:10px 0 10px 10px; border-bottom: 1px solid #eee;}
.randomNews img{width: 80px; height: 45px; border-radius: 4px; margin-right: 10px;}
.randomNews div{flex:1;flex-direction: row;padding-right: 10px;}
.randomNews h3,.randomNews h4, .randomNews h5{margin:0; padding:0; font-weight: normal;}
.randomNews h3{font-family: '微软雅黑'; background-color: #444;color:#fff;padding:6px 10px;}
.randomNews h4{font-size: 16px;}
.randomNews h5{color:#444;}
.randomNews a, .randomNews a:visited{color:#444;}

 

三、自定义组件及逻辑实现

逻辑文件主要做包含以下部分:

1、定义Angular模块,注入库依赖

angular.module('myNgApp', ['ngResource', 'ngSanitize'])

 

2、定义后台请求的URL常量

.constant('APPURL', 'http://localhost:8080')

 

3、自定义格式化日期过滤器

.filter('formatDate', function(){
	return function(dateStr){
		var dateObj = new Date(Date.parse(dateStr.replace(/-/g, "/")));
		return dateObj.getFullYear() + '/' + (dateObj.getMonth()+1) + '/' + dateObj.getDate();
	}
})

 

4、自定义组件

组件部分一共定义了头部组件、菜单组件、新闻列表组件、边栏组件、底部组件和新闻详情组件。这里的组件内部动态信息都是经由控制器中从远程获取后,以传值的方式传到组件内部显示的,因此,只需要定义好组件与外部沟通共享信息的属性和数据格式,就可以非常灵活方便的复用组件。

(1)头部组件:

.component('headerNgCom',{
	bindings: {
		callback: '&'
	},
	template: `
		<header>
	        <img class="ngLogo" src="img/angular.png" />
	        <div class="search-wrapper cf">
	            <input type="text" placeholder="请输入搜索关键词..." ng-model="vm.searchText" />
	            <button ng-click="vm.callback({searchText: vm.searchText})" ng-disabled="!vm.searchText">搜索</button>
	        </div>
	    </header>
	`,
	controller: function(){},
	controllerAs:'vm'
})

头部组件比较简单,定义了一个template用来渲染组件,其中有一个文本框和一个按钮来实现搜索功能,利用controllerAs定义组件内部作用域,绑定搜索功能的搜索关键词vm.searchText,将该搜索关键词传递给搜索按钮点击事件,然后定义bindings,回调一个指定函数(使用’&’),将按钮点击事件回调到控制器中定义的指定方法。

在时,只要在调用这个组件时,使用属性callback指定一个方法,并在控制器下定义这个方法即可完成响应。

<header-ng-com callback=”search(searchText)”></header-ng-com>

这里的search方法将在该作用域相应的控制器中作定义。后面讲控制器的时候会涉及。

 

(2)底部组件

底部组件没有数据交互,只作静态内容展示。

.component('footerNgCom', {
	template: `
		<footer>
			<img class="footerLogo" src="img/angular.png" />
            <p>AngularJS 组件化开发示例</p>
        </footer>
	`
})

 

(3)菜单组件

菜单组件接收来自父级组件或页面的菜单项列表传值,并循环输出菜单项。

.component('menuNgCom', {
	bindings: {
		menus: '<'
	},
	template: `
		<nav>
			<ul>
	            <li ng-repeat="menu in vm.menus"><a ng-href="{{menu.link}}" ng-class="{true: 'active'}[menu.title == '新闻中心']">{{menu.title}}</a></li>
	        </ul>
	    </nav>
	`,
	controller: function(){
		this.searchText = '';
	},
	controllerAs:'vm'
})

 

(4)边栏组件

边栏组件接收父级组件或页面的随机新闻列表数组传值,并循环显示。

.component('sidebarNgCom', {
	bindings: {
		randomNews: '<'
	},
	template: `
		<aside>
            <img src="../img/banner.jpg" />
            <section class="randomNews">
                <h3>随机新闻</h3>
                <div class="newsItem" ng-repeat="randomNew in vm.randomNews">
                    <a ng-href="ngnewscontent.html?id={{randomNew.id}}"><img ng-src="{{randomNew.featured_img}}" alt="{{randomNew.title}}" /></a>
                    <div>
                    	<a ng-href="ngnewscontent.html?id={{randomNew.id}}">
	                        <h4>{{randomNew.title}}</h4>
	                        <h5>{{randomNew.create_time | formatDate}}</h5>
                        </a>
                    </div>
                </div>
            </section>
        </aside>
	`,
	controller: function(){},
	controllerAs:'vm'
})

 

(5)新闻列表组件:

新闻列表组件稍微复杂一点,先分析新闻列表组件中需要的要素,必须要新闻的列表,类型为数组,新闻列表的“加载更多”响应事件,(此处新闻列表显示分页处理,点击加载更多按钮加载更多的新闻条目),另外,还需要一个标识用来确定是否显示“加载更多”链接。因此,组件需要指定三个属性,news用来从控制器传递新闻列表数据给新闻列表组件,callback用于响应每次点击“加载更多”按钮,hasmore用于从控制器传递是否有更多新闻项目的逻辑标识,以确定是否在组件中显示“加载更多”按钮。

.component('newslistNgCom', {
	bindings: {
		callback: '&',
		news: '<',
		hasmore: '<'
	},
	template: `
		
			<section class="newsItem" ng-repeat="newsItem in vm.news">
		        <a ng-href="ngnewscontent.html?id={{newsItem.id}}"><img ng-src="{{newsItem.featured_img}}" alt="{{newsItem.title}}" /></a>
		        <div>
		        	<a ng-href="ngnewscontent.html?id={{newsItem.id}}">
			            <h3>{{newsItem.title}}</h3>
			            <h4>{{newsItem.description}}</h4>
			            <h5>{{newsItem.create_time | formatDate}}</h5>
		            </a>
		        </div>
		    </section>
		    <div class="loadmore" ng-click="vm.loadmore()" ng-show="vm.hasmore">加载更多</div>
	`,
	controller: function(){
		var vm = this;
		vm.page = 2;
		vm.loadmore = function(){
			vm.callback({page: vm.page});
			vm.page++;
		}
	},
	controllerAs:'vm'
})

新闻列表的调用如下:

<newslist-ng-com news=”news” class=”newsList” hasmore=”hasmore” callback=”loadmore(page)”></newslist-ng-com>

 

(6)新闻详情组件

新闻详情组件接收单条新闻对象数据,并按指定格式显示。

.component('newsContent', {
	bindings: {
		'newscontent': '<'
	},
	template: `
		<section>
            <h3>{{vm.newscontent.title}}</h3>
            <h4>{{vm.newscontent.source}}</h4>   <h5>{{vm.newscontent.create_time}}</h5>
            <hr />
            <div class="content" ng-bind-html="vm.newscontent.content"></div>
            <div class="keyword">
                <span ng-repeat="keyword in vm.newscontent.keywordArr">{{keyword}}</span>
            </div>
        </section>
	`,
	controller: function(){},
	controllerAs:'vm'
})

 

5、控制器实现

这个示例一共用到两个控制器,一个用于处理新闻列表页面逻辑,另一个用于处理单个新闻显示页面逻辑。

新闻列表页面控制器mainCtrl主要包括以下几部分:

A:初始化变量数据:

$scope.menus = [
	{link: '#', title:'首页'},
	{link: '#', title:'关于我们'},
	{link: 'index.html', title:'新闻中心'},
	{link: '#', title:'产品中心'},
	{link: '#', title:'服务范围'},
	{link: '#', title:'在线商店'},
	{link: '#', title:'联系我们'}
];

$scope.news = [];
$scope.randomNews = [];
$scope.hasmore = true;
$scope.issearch = false;
var page = 1;
var pagesize = 5;
var randomNewsCount = 5;

B:使用ngResource定义资源请求对象:

var News = $resource(APPURL + '/news', {}, {});
var RandomNews = $resource(APPURL + '/random/:num', {}, {});
var SearchNews = $resource(APPURL + '/search', {}, {});

C:获取随机新闻

RandomNews.get({num: randomNewsCount}, function(res){
	if(res.status){
		$scope.randomNews = res.data;
	}
});

D:获取新闻列表

//新闻列表
$scope.loadmore = function(page){
	News.get({page: page, pagesize: pagesize}, function(res){
		if(res.status){
			if(res.data.length){
				for(var i=0; i<res.data.length; i++){
					$scope.news.push(res.data[i]);
				}
				if(res.data.length < pagesize){
					$scope.hasmore = false;
				}
			}
		}else{
			$scope.hasmore = false;
		}
	});
}

E:定义搜索方法

//搜索
$scope.search = function(searchText){
	console.log(searchText);
	$scope.news = [];
	$scope.count = 0;
	$scope.issearch = true;
	$scope.hasmore = false;
	$scope.searchText = searchText;
	SearchNews.get({s: searchText}, function(res){
		if(res.status){
			$scope.count = res.data.length;
			$scope.news = res.data;
		}
	})
}

 

新闻详情页面控制器contentCtrl主要包括以下两部分:

A:定义获取新闻ID参数方法

function GetQueryString(name){
	var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
	var r = window.location.search.substr(1).match(reg);
	if(r!=null)return  unescape(r[2]); return null;
}

B:获取新闻详情

$scope.newsContent = {};
var id = GetQueryString('id');

if(id){
	var NewsContent = $resource(APPURL + '/news/' + id, {}, {});
	NewsContent.get({}, function(res){
		if(res.status){
			$scope.newsContent = res.data;
			$scope.newsContent.keywordArr = $scope.newsContent.keyword.split(',');
		}
	})
}

 

至此,逻辑文件已分析完毕,完整的逻辑文件代码如下:

angular.module('myNgApp', ['ngResource', 'ngSanitize'])

.constant('APPURL', 'http://localhost:8080')

.filter('formatDate', function(){
	return function(dateStr){
		var dateObj = new Date(Date.parse(dateStr.replace(/-/g, "/")));
		return dateObj.getFullYear() + '/' + (dateObj.getMonth()+1) + '/' + dateObj.getDate();
	}
})


//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////   组件定义  /////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////

//头部组件
.component('headerNgCom',{
	bindings: {
		callback: '&'
	},
	template: `
		<header>
	        <img class="ngLogo" src="img/angular.png" />
	        <div class="search-wrapper cf">
	            <input type="text" placeholder="请输入搜索关键词..." ng-model="vm.searchText" />
	            <button ng-click="vm.callback({searchText: vm.searchText})" ng-disabled="!vm.searchText">搜索</button>
	        </div>
	    </header>
	`,
	controller: function(){},
	controllerAs:'vm'
})

//底部组件
.component('footerNgCom', {
	template: `
		<footer>
			<img class="footerLogo" src="img/angular.png" />
            <p>AngularJS 组件化开发示例</p>
        </footer>
	`
})

//菜单组件
.component('menuNgCom', {
	bindings: {
		menus: '<'
	},
	template: `
		<nav>
			<ul>
	            <li ng-repeat="menu in vm.menus"><a ng-href="{{menu.link}}" ng-class="{true: 'active'}[menu.title == '新闻中心']">{{menu.title}}</a></li>
	        </ul>
	    </nav>
	`,
	controller: function(){
		this.searchText = '';
	},
	controllerAs:'vm'
})

//新闻列表组件
.component('newslistNgCom', {
	bindings: {
		callback: '&',
		news: '<',
		hasmore: '<'
	},
	template: `
		
			<section class="newsItem" ng-repeat="newsItem in vm.news">
		        <a ng-href="ngnewscontent.html?id={{newsItem.id}}"><img ng-src="{{newsItem.featured_img}}" alt="{{newsItem.title}}" /></a>
		        <div>
		        	<a ng-href="ngnewscontent.html?id={{newsItem.id}}">
			            <h3>{{newsItem.title}}</h3>
			            <h4>{{newsItem.description}}</h4>
			            <h5>{{newsItem.create_time | formatDate}}</h5>
		            </a>
		        </div>
		    </section>
		    <div class="loadmore" ng-click="vm.loadmore()" ng-show="vm.hasmore">加载更多</div>
	`,
	controller: function(){
		var vm = this;
		vm.page = 2;
		vm.loadmore = function(){
			vm.callback({page: vm.page});
			vm.page++;
		}
	},
	controllerAs:'vm'
})

//边栏组件
.component('sidebarNgCom', {
	bindings: {
		randomNews: '<'
	},
	template: `
		<aside>
            <img src="../img/banner.jpg" />
            <section class="randomNews">
                <h3>随机新闻</h3>
                <div class="newsItem" ng-repeat="randomNew in vm.randomNews">
                    <a ng-href="ngnewscontent.html?id={{randomNew.id}}"><img ng-src="{{randomNew.featured_img}}" alt="{{randomNew.title}}" /></a>
                    <div>
                    	<a ng-href="ngnewscontent.html?id={{randomNew.id}}">
	                        <h4>{{randomNew.title}}</h4>
	                        <h5>{{randomNew.create_time | formatDate}}</h5>
                        </a>
                    </div>
                </div>
            </section>
        </aside>
	`,
	controller: function(){},
	controllerAs:'vm'
})

//新闻详情组件
.component('newsContent', {
	bindings: {
		'newscontent': '<'
	},
	template: `
		<section>
            <h3>{{vm.newscontent.title}}</h3>
            <h4>{{vm.newscontent.source}}</h4>   <h5>{{vm.newscontent.create_time}}</h5>
            <hr />
            <div class="content" ng-bind-html="vm.newscontent.content"></div>
            <div class="keyword">
                <span ng-repeat="keyword in vm.newscontent.keywordArr">{{keyword}}</span>
            </div>
        </section>
	`,
	controller: function(){},
	controllerAs:'vm'
})


//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////    控制器   /////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////

.controller('mainCtrl', function($scope, $resource, APPURL){
	$scope.menus = [
		{link: '#', title:'首页'},
		{link: '#', title:'关于我们'},
		{link: 'index.html', title:'新闻中心'},
		{link: '#', title:'产品中心'},
		{link: '#', title:'服务范围'},
		{link: '#', title:'在线商店'},
		{link: '#', title:'联系我们'}
	];

	$scope.news = [];
	$scope.randomNews = [];
	$scope.hasmore = true;
	$scope.issearch = false;
	var page = 1;
	var pagesize = 5;
	var randomNewsCount = 5;

	var News = $resource(APPURL + '/news', {}, {});
	var RandomNews = $resource(APPURL + '/random/:num', {}, {});
	var SearchNews = $resource(APPURL + '/search', {}, {});
	
	//随机新闻
	RandomNews.get({num: randomNewsCount}, function(res){
		if(res.status){
			$scope.randomNews = res.data;
		}
	});

	//新闻列表
	$scope.loadmore = function(page){
		News.get({page: page, pagesize: pagesize}, function(res){
			if(res.status){
				if(res.data.length){
					for(var i=0; i<res.data.length; i++){
						$scope.news.push(res.data[i]);
					}
					if(res.data.length < pagesize){
						$scope.hasmore = false;
					}
				}
			}else{
				$scope.hasmore = false;
			}
		});
	}

	$scope.loadmore();

	//搜索
	$scope.search = function(searchText){
		console.log(searchText);
		$scope.news = [];
		$scope.count = 0;
		$scope.issearch = true;
		$scope.hasmore = false;
		$scope.searchText = searchText;
		SearchNews.get({s: searchText}, function(res){
			if(res.status){
				$scope.count = res.data.length;
				$scope.news = res.data;
			}
		})
	}
})

.controller('contentCtrl', function($scope, $resource, APPURL){
	function GetQueryString(name){
		var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
		var r = window.location.search.substr(1).match(reg);
		if(r!=null)return  unescape(r[2]); return null;
	}

	$scope.newsContent = {};
	var id = GetQueryString('id');

	if(id){
		var NewsContent = $resource(APPURL + '/news/' + id, {}, {});
		NewsContent.get({}, function(res){
			if(res.status){
				$scope.newsContent = res.data;
				$scope.newsContent.keywordArr = $scope.newsContent.keyword.split(',');
			}
		})
	}
})

 

四、运行效果

前后端分离实践AngularJS/ReactJS/VueJS组件化开发示例与对比

前后端分离实践AngularJS/ReactJS/VueJS组件化开发示例与对比

前后端分离实践AngularJS/ReactJS/VueJS组件化开发示例与对比

 

 

那时那我

jinyunblogadmin

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据