序言
本章将通过angular默认的ng-route组件来复用视图与前端的路由控制。
上一个章节的完成图
本章目的
在底部增加一个状态的过滤,可以分别显示“全部”,“未完成”和“已完成”不同状态下的清单。
1. 使用ng-route复用视图
1.1 调整index.html
首先我们先来审阅一下当年的index.html代码
<!doctype html>
<html lang="en" data-framework="angularjs">
<head>
<meta charset="utf-8">
<title>AngularJS • TodoMVC</title>
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
</head>
<body ng-app="todomvc" ng-controller="TodoController as vm">
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<form id="todo-form" ng-submit="vm.addTask()">
<input id="new-todo" placeholder="添加新的任务?" ng-model="vm.newTask" autofocus>
</form>
</header>
<section id="main" ng-cloak>
<ul id="todo-list">
<li ng-repeat="task in vm.tasks" ng-class="{completed: task.completed, editing: task === vm.editedTask}">
<div class="view">
<input class="toggle" type="checkbox" ng-model="task.completed">
<label ng-dblclick="vm.editTask(task)">{{task.title}}</label>
<button class="destroy" ng-click="vm.removeTask(task)"></button>
</div>
<form>
<input class="edit" ng-trim="false" ng-model="task.title" >
</form>
</li>
</ul>
</section>
</section>
<script src="node_modules/angular/angular.js"></script>
<script src="node_modules/angular-route/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers/todo.controller.js"></script>
</body>
</html>
body标签的section标签内的内容才是有有逻辑并且可分离的视图代码。
故我们使用ng-route来规划前端的url分发控制与对应的,通过对目标URL的撞他变化加载对应的视图与控制器文件。
1.2 分离业务视图至partials/todo.html文件
我们将index.html代码分离出来,新建一个partials/todo.html,其内容如下。
目录结构
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<form id="todo-form" ng-submit="vm.addTask()">
<input id="new-todo" placeholder="添加新的任务?" ng-model="vm.newTask" autofocus>
</form>
</header>
<section id="main" ng-cloak>
<ul id="todo-list">
<li ng-repeat="task in vm.tasks" ng-class="{completed: task.completed, editing: task === vm.editedTask}">
<div class="view">
<input class="toggle" type="checkbox" ng-model="task.completed">
<label ng-dblclick="vm.editTask(task)">{{task.title}}</label>
<button class="destroy" ng-click="vm.removeTask(task)"></button>
</div>
<form>
<input class="edit" ng-trim="false" ng-model="task.title" >
</form>
</li>
</ul>
</section>
</section>
消除这段代码后整个index.html就只剩下相关的入口及配置信息代码就变得干净很多。
因为使用了ng-route之后控制器的初始化就不需要我们显式的写在视图中。故我们删除掉body标签中的ng-controller属性。
<!--
<body ng-app="todomvc" ng-controller="TodoController as vm">
-->
<body ng-app="todomvc">
最后在body标签下引入ng-route的动态视图指令ng-view
<body ng-app="todomvc">
<ng-view />
<script src="node_modules/angular/angular.js"></script>
<script src="node_modules/angular-route/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers/todo.controller.js"></script>
</body>
1.3 引入route配置信息
我们重新编辑app.js文件增加根路径的视图与控制器加载逻辑。
(function(){
'use strict';
angular.module('todomvc', ['ngRoute'])
.config(routerConfig);
routerConfig.$inject = ['$routeProvider'];
function routerConfig($routeProvider){
$routeProvider
.when('/',{
controller: 'TodoController',
templateUrl: 'partials/todo.html',
controllerAs: 'vm'
})
};
})();
routeConfig中操作$routeProvider服务,向其添加路由规则。
.when('/',{
controller: 'TodoController', //控制器
templateUrl: 'partials/todo.html', //视图
controllerAs: 'vm' // 控制器As的别名语法,Angular1.2以后提倡的语法
})
这样我们就让angular在客户端维护了一个可以根据url做出影响的前端运行时环境,这个时候我们重新在浏览器刷新地址则发现应用与之前是没区别的,唯一的区别是路径上多了一个#符号。
默认模式下,浏览器都只访问index.html的入口页 /#/之后的路径为ng-route控制的url部分,这样即使前端便可以进行无刷新的页面url的跳转操作了。
2. 向应用中添加底部状态控制栏
这次我们希望通过不同的url来显示不同的任务清单列表:
- 在/路径下显示所有状态的任务清单
- 在/active路径下显示所有未完成的任务清单
- 在/completed路径下显示所有完成的任务清单
2.1 修改ng-route
添加新/:status路由,将active于completed作为参数传给controller做数据的筛选。
.when('/:status', {
controller: 'TodoController',
templateUrl: 'partials/todo.html',
controllerAs: 'vm'
})
2.2 在controller层获取路由参数
我们在Controller层想要获取路径上的参数,则需要向Controller注入$routeParams服务,在通过$routeParams.paramName的形式便可以获取对应的参数值。
我们修改我们TodoController使其可以对不同的status状态做出不同的过滤操作。
首先,我们编写一个函数可以根据不同的status值来过滤处理controller中的任务清单数组
function _filterDataByStatus(tasks,status){
if (status === 'active'){
return tasks.filter(function(task){
return (task.completed != true )
})
}else if(status === 'completed'){
return tasks.filter(function(task){
return (task.completed == true )
})
}else{
return tasks;
}
}
然后,调整TodoController中的初始化逻辑,通过$routeParam服务来获取路径中的status的值。
function init(){
var tasks = [
{
title: "第一个任务",
completed: true
},
{
title: "第二个任务",
completed: false
}];
vm.status = $routeParams.status||"";
vm.tasks = _filterDataByStatus(tasks,vm.status)
}
我们额外声明了一个vm.status变量用于记录当前的status的值,用于模板中的底部状态过滤标签对当前激活的过滤条件做高亮显示。
2.3 增加底部的状态切换区域
我们在todo.html中增加一段新的footer标签区域用于显示底部的状态切换的状态标签按钮。
<footer id="footer" ng-show="vm.tasks.length" ng-cloak>
<span id="todo-count"><strong>{{remainingCount}}</strong>
<ng-pluralize count="remainingCount" when="{ one: 'item left', other: 'items left' }"></ng-pluralize>
</span>
<ul id="filters">
<li>
<a ng-class="{selected: vm.status == ''} " href="#/">All</a>
</li>
<li>
<a ng-class="{selected: vm.status == 'active'}" href="#/active">Active</a>
</li>
<li>
<a ng-class="{selected: vm.status == 'completed'}" href="#/completed">Completed</a>
</li>
</ul>
</footer>
底部有三个按钮分别对应路径"/","/active"与"/completed"。并根据vm.status使用ng-class来高亮显示当前激活的过滤条件标签。
2.4 测试应用
我们分别测试不同状态下的清单显示情况
2.4.1 默认显示全部
2.4.2 待完成
2.4.3 已完成
2.5 问题
似乎看上去我们的过滤功能以前完成了,但是我们可以添加一个新的任务,再进行条件过滤,这个时候则会发现新建的任务清单“不见了”。
这是因为不同路由下的虽然为同名controller但是其中的实例变量还是会重新初始化。两个Controller实例没有进行数据的共享。
vm.tasks的赋值逻辑
var tasks = [
{
title: "第一个任务",
completed: true
},
{
title: "第二个任务",
completed: false
}];
vm.status = $routeParams.status||"";
//此处的进行双向绑定
vm.tasks = _filterDataByStatus(tasks,vm.status)
在下一章的文章中我们将介绍Angular中常见几种在controller中共享、传递数据的方法并选取一种来调整我们的代码。