学习 JavaScript 第 3 部分 - AngularJS 和 Langton's Ant
学习如何使用 Bower、Bootstrap 和 AngularJS 来用 JavaScript 创建 Langton's Ant 模拟程序。
引言
欢迎来到我的 JavaScript 学习系列第三部分。在这篇文章中,我将向您展示如何用 JavaScript 实现 Langton's Ant 模拟程序。我还将使用 AngularJS 框架来协助客户端逻辑。 您可以在这里查看最终结果。
我还会介绍 Bower 来处理客户端组件。
学习 JavaScript 系列
这是我学习 JavaScript 系列的第三部分。
- 学习 JavaScript 第一部分 - 创建星空
- 学习 JavaScript 第 2 部分 - 太空侵略者
- 学习 JavaScript 第 3 部分 - AngularJS 和 Langton's Ant
本系列旨在通过动手项目学习 JavaScript 和 HTML5 技术栈。
什么是 Langton's Ant?
Langton's Ant 是一个简单的数学模拟,有点像 Conway 的生命游戏。基本上,我们想象一个无限的二维平面,所有的格子都是白色的。蚂蚁位于中间的格子上。每次我们推进模拟,蚂蚁就会向前移动。如果蚂蚁离开一个白色的格子,它会向左转;如果它离开一个黑色的格子,它会向右转。当它离开格子时,它会在黑白之间切换。
我们可以通过包含更多的格子状态来扩展模拟。
这个模拟有趣之处在于什么?嗯,有趣的是我们在这个宇宙中所观察到的行为。它只有三个规则,但却展现出相当复杂的行为。在最初的几百步中,我们看到的是一种看起来像规律的模式,似乎存在对称性和秩序。过了一会儿,系统变得混乱——蚂蚁漫无目的地游走,扰乱了之前形成的图案。大约在 11000 步之后,出现了最终的行为状态——涌现的秩序。从之前的混乱中,蚂蚁构建了一个重复并缓慢向一个方向移动的模式。这被称为“高速公路”。
目前尚不清楚所有初始配置是否都会产生高速公路。令人着迷的是,我们知道这个宇宙的“万物理论”,但我们仍然有很多未知——所有配置是否都会导致高速公路?了解一个系统的所有规则并不足以理解它。
第一部分 - 入门
首先,我们将一起构建应用程序的结构。我们将拥有一个如下所示的文件和文件夹结构:
-langtonsant/
-client/
-index.htm
-js/
-langtonsant.js
index.html 文件将包含模拟及其控件的呈现——langtonsant.js 将包含一个代表模拟的类,并提供启动/停止等功能。
我们还需要什么?嗯,我们将使用两个第三方库。
Twitter Bootstrap
Bootstrap 是最流行的 Web 开发包之一。其核心是一组 CSS 样式,通过使用更干净的字体、更好的段落间距、更好的链接样式等,大大地美化了“标准”HTML。然后,它通过提供大量可以放入 HTML 的 UI 组件(如选项卡和轮播)来进一步增强。我写了一篇名为 使用 Twitter Bootstrap 创建干净的网页 的文章,如果您想了解更多。
我们将使用 Bootstrap 来提供干净的文本和表单控件样式,以及“手风琴”组件,它可以展开以显示更多 UI。
AngularJS
AngularJS 是一个用于在客户端构建 HTML/JS 应用程序的框架。它支持数据绑定等功能,这意味着您可以更改 JavaScript 对象的属性,UI 也会相应更新。
AngularJS 是一个庞大的话题,我们将只使用其中的一些功能。我的网站上有一个关于 AngularJS 的完整系列,实用 AngularJS - 如果您从未听说过 AngularJS,我推荐 AngularJS 入门。
第二部分 - 使用 Bower 安装客户端组件
我们将使用 Bower 为我们安装 Angular 和 Bootstrap。Bower 是什么?Bower 是一个 Web 包管理器——如果您使用 C#,它就像 Nuget;如果您使用 Ruby,它就像 Gem;如果您使用 Python,它就像 Pip 等等。
要安装 Bower,请确保您已安装 NodeJS。如果您以前从未用过或听说过 NodeJS,请不用担心——就我们要做的事情而言,它只是提供了一种安装 Bower 的方式。我计划做一个关于 Node 的大型系列。
现在,使用以下命令行命令将 Bower 安装为全局 NodeJS 包:
npm install -g bower
“-g
”标志表示该包应全局安装——我们希望从任何位置都能使用 bower。
现在来到最精彩的部分。导航到 langtonsant 中的“client”文件夹,然后运行以下命令:
bower init
bower install angular#1.2.x --save
bower install bootstrap#3.0.x --save
当我们使用“bower install
”时,我们会将跟随的包安装到当前目录。Bower 会创建一个“bower_components”文件夹并将所需文件放在其中。我们可以使用包名称后的哈希值来使用特定版本——在这种情况下,我知道我想要 Angular 1.2 和 Bootstrap 3.0,并且我乐于接受错误修复和次要的非破坏性更新(这就是我使用“x
”的原因),但不想进行任何大型更新。
在命令中包含“--save
”标志意味着 Bower 会在文件夹中创建一个 bower.json 文件,其中列出了我安装的包,这意味着下一个使用该代码的人只需使用:
bower install
然后 Angular 和 Bootstrap 将被安装,因为它们在 bower.json 文件中。简单!
由于我们已经安装了这些包,现在可以在我们的 index.html 文件中引用它们了:
<!DOCTYPE html>
<html >
<head>
<title>Langton's Ant</title>
<link rel="stylesheet" type="text/css"
href="bower_components/bootstrap/dist/css/bootstrap.min.css">
<script src="bower_components/angular/angular.min.js"></script>
</head>
<body>
<!-- We'll put everything here! -->
<script src="bower_components/jquery/jquery.min.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
</body>
</html>
我们知道我们所有的第三方内容都位于 bower_components 文件夹中,干净地独立于我们自己的代码。
第二部分 - 创建模拟
我们需要一个对象来表示模拟。在之前的两篇文章中,我们已经详细讨论了如何在 JavaScript 中创建对象,所以对于这部分,我将非常迅速地进行——我们不会深入剖析模拟的每一个部分。我将展示代码并突出关键点,然后我们将继续讨论本系列新内容。
让我们开始创建 langtonsant.js 文件并创建一个类:
/*
Langton's Ant
The Langton's Ant class represents a Langton's Ant simulation. It is
initialised with a call to 'Initialise', providing all configuration
and erasing any state. The simulation can then be 'ticked', forwards
or backwards.
*/
function LangtonsAnt() {
// The position of the ant
this.antPosition = {x: 0, y: 0};
// The direction of the ant, in degrees clockwise from north
this.antDirection = 0;
// A set of all tiles. The value for each tile is its state index.
// We also have a set of tile states.
this.tiles = [];
this.states = [];
// The bounds of the system
this.bounds = {
xMin: 0,
xMax: 0,
yMin: 0,
yMax: 0
};
// The number of ticks
this.ticks = 0;
// The offset and current zoom factor
this.offsetX = 0;
this.offsetY = 0;
this.zoomFactor = 1.0;
这定义了 LangtonsAnt
类及其将具有的状态。我们将需要蚂蚁的位置、蚂蚁的方向、一个格子数组以及系统的边界。
为什么我们会有边界和格子数组?嗯,我们不想限制系统的特定大小,所以我们将假设宇宙是无限的,并且当我们获取任何格子的状态时,它是白色的。当我们改变格子的状态时,我们将它保存在“tiles
”数组中。这意味着 tiles
数组是稀疏的——如果我们有一个 100x100 格子的宇宙,我们不需要 10000 个格子,只需要那些状态不是默认状态的格子。
我们在一个格子里存储什么?仅仅是状态的索引。每个格子可以有两种状态,白色或黑色,但我们可以拥有更复杂的模拟,具有更多的格子状态,在这种情况下,索引会更大。这允许我们定义 next
函数,该函数初始化宇宙。
// Initialises a universe. If we include a configuration
// value, we can override the states.
this.initialise = function (configuration) {
// Reset the tiles, ant and states
this.antPosition = {
x: 0,
y: 0
};
this.antDirection = 0;
this.tiles = [];
this.bounds = {
xMin: 0,
xMax: 0,
yMin: 0,
yMax: 0
};
this.states = [];
this.offsetX = 0;
this.offsetY = 0;
// If we have no states, create our own.
if(configuration.states !== undefined) {
this.states = configuration.states;
} else {
this.states = [
{direction: 'L', colour: '#FFFFFF'},
{direction: 'R', colour: '#000000'}
];
}
};
初始化宇宙必须重置所有值,因为我们可能会在已创建的宇宙上调用它。如果我们传递了一个状态数组,我们就使用它,否则我们就创建一个默认的状态数组——一个白色格子(在那里我们向左转)和一个黑色格子(在那里我们向右转)。
从这里开始,定义获取格子状态索引或格子状态的辅助函数就很容易了:
// Gets a tile state index. If we don't have a state, return the
// default (zero), otherwise return the state from the tiles array.
this.getTileStateIndex = function(x, y) {
if(this.tiles[x] === undefined) {
this.tiles[x] = [];
}
var stateIndex = this.tiles[x][y];
return stateIndex === undefined ? 0 : stateIndex;
};
// Gets a tile state
this.getTileState = function(x, y) {
return this.states[this.getTileStateIndex(x, y)];
};
到目前为止一切顺利——现在我们可以使用辅助函数来设置格子状态了。
// Set a tile state index
this.setTileStateIndex = function(x, y, stateIndex) {
if(this.tiles[x] === undefined) {
this.tiles[x] = [];
}
this.tiles[x][y] = stateIndex;
// Update the bounds of the system
if(x < this.bounds.xMin) {this.bounds.xMin = x;}
if(x > this.bounds.xMax) {this.bounds.xMax = x;}
if(y < this.bounds.yMin) {this.bounds.yMin = y;}
if(y > this.bounds.yMax) {this.bounds.yMax = y;}
};
接下来,我们可以编写一个函数来将格子推进到下一个状态,如果遍历了所有状态,则返回第一个。
// Advance a tile states
this.advanceTile = function(x, y) {
// Get the state index, increment it, roll over if we pass
// over the last state and update the tile state.
var stateIndex = this.getTileStateIndex(x, y)+1;
stateIndex %= this.states.length;
this.setTileStateIndex(x, y, stateIndex);
};
next
函数将模拟向前推进一步。我们获取格子状态,根据状态改变方向,移动蚂蚁,然后推进格子。
// Take a step forwards
this.stepForwards = function() {
// Get the state of the tile that the ant is on, this'll let
// us determine the direction to move in
var state = this.getTileState(this.antPosition.x, this.antPosition.y);
// Change direction
if(state.direction === 'L') {
this.antDirection -= 90;
} else if(state.direction === 'R') {
this.antDirection += 90;
}
this.antDirection %= 360;
// Move the ant
if(this.antDirection === 0) {
this.antPosition.y++;
} else if (this.antDirection === 90 || this.antDirection === -270) {
this.antPosition.x++;
} else if (this.antDirection === 180 || this.antDirection === -180) {
this.antPosition.y--;
}
else {
this.antPosition.x--;
}
// Now we can advance the tile
this.advanceTile(this.antPosition.x, this.antPosition.y);
this.ticks++;
};
最后一个函数将模拟渲染到画布上。我不会在这里包含它,因为它相当长,但如果您想查看代码,可以 在此处 查看。这段代码对本文的帮助不大,因为我们已经在前两篇文章中讨论过画布绘图了。
现在我们已经创建了模拟。下一步是创建一个用户界面可以用来控制模拟的控制器。
第三部分 - 创建控制器
控制器是 AngularJS 构建的一个对象,用于在作用域中创建和操作状态。作用域是视图中绑定操作的数据上下文。控制器包含视图绑定的字段,以及视图绑定的函数。这使我们可以编写 HTML 来将 UI 元素绑定到数据,或将 UI 元素绑定到激活功能。
我们将需要两个新文件——app.js 和 controllers.js。App.js 将是主要的 Angular 应用,并将依赖于控制器。controllers.js 文件将定义应用的主控制器。
让我们从 app.js 开始:
// Define the langtons ant module. It depends on app controllers and directives.
var app = angular.module('langtonsant',
['langtonsant.controllers',
'langtonsant.directives']);
Angular 使用模块系统来让我们拆分应用程序。我们将“langtonsant
”定义为主模块,并声明它依赖于“langtonsant.controllers
”模块,以及“langtonsant.directives
”模块(我们稍后会看到)。现在让我们编写一个控制器。
我们将创建主控制器——它将有一个模拟对象和一个启动、停止和重置它的函数。
以下是 controllers.js 的开头:
// All controllers go in the langtonsant.controllers module
angular.module('langtonsant.controllers', [])
.controller('MainController', function($interval, $timeout) {
var self = this;
我们定义了一个新模块“langtonsant.controllers
”。第二个参数是一个依赖于它的模块数组,这里为空。接下来,我们添加了一个名为“MainController
”的控制器。控制器定义函数返回控制器的一个实例。它的参数是它的依赖项。Angular 会自动为我们注入这些依赖项。每个以美元符号开头的依赖项都是内置的 Angular 依赖项。
我们依赖于 $interval
(用于重复计时器)和 $timeout
(用于在给定时间后调用函数)。
接下来,我们可以定义控制器上的数据。如果我们将其分配给 this
,我们就可以将视图绑定到它。普通的 var
定义用于我们内部使用的数据。
// The frequency of simulation ticks
this.tickFrequency = 10;
// The set of default colours for states
this.defaultStateColours = [
'#FFFFFF',
'#49708A',
'#88ABC2',
'#D0E0EB',
'#EBF7F8'
];
// Available tile states
this.states = [
{direction:'L', colour: this.defaultStateColours[0]},
{direction:'R', colour: this.defaultStateColours[1]}
];
// Simulation info
this.info = {
currentTicks: 0
};
// None scope variables. These are used by the controller, but not exposed.
var currentState = "stopped";
var tickIntervalId = null;
var simulation = new LangtonsAnt();
var canvas = null;
// Initialise the simulation with the states
simulation.initialise({states: this.states});
// When the document is ready, we'll grab the antcanvas.
$timeout(function() {
canvas = document.getElementById('antcanvas');
self.render();
});
我们需要频率——即我们每秒“滴答”宇宙的次数。我们定义一组默认用于格子的颜色。我们创建两个初始格子状态,并将它们传递给模拟的 initialise
函数。我们跟踪可能想要查看的信息(滴答次数)。
我们还存储当前状态,即我们正在运行还是已停止。我们保存一个间隔 ID(因为我们将设置一个计时器来滴答,之后我们需要停止它)。我们创建一个模拟实例,然后是某个巧妙的东西……
使用 $timeout(function() {})
是一个小技巧。它注册一个立即调用的函数,但因为我们使用的是 Angular 的 $timeout
而不是窗口上的那个,所以我们得到一个特殊的免费行为——它在 DOM 加载之后以及 Angular 应用加载之后被调用。如果我们不这样做,画布可能还没有在 DOM 中准备好,而我们希望尽快获取画布以便进行绘制。我们还调用了“render
”函数——我们稍后会编写它。
有时使用 self
而不是 this
?嗯,在 JavaScript 中,this
并不总是您期望的那样。例如,在计时器回调函数中,this
将是全局对象。由于我们希望更改类实例,我们会立即将其存储在 self
中,以便在回调函数中显式引用它。一个很棒的技巧!这是您在 JavaScript 中经常会看到的东西。
接下来,让我们创建一个运行模拟的函数:
// Runs the simulation
this.run = function() {
// If we're already running, we can't start the simulation.
if(currentState === 'running') {
return;
}
// Start the timer
tickIntervalId = $interval(function() {
simulation.stepForwards();
self.info.currentTicks = simulation.ticks;
self.render();
}, 1000 / this.tickFrequency);
// Set the status
currentState = 'running';
};
此函数确保我们没有在运行,然后启动一个计时器。每次计时器触发时,我们向前推进模拟,更新已完成的步数并进行绘制。然后我们将状态设置为正在运行。
因为此函数定义在“this
”上,所以我们可以将其绑定到视图中,例如,使点击触发该函数。
我们需要在视图中知道当前状态,以便根据是否已在运行来显示或隐藏运行/暂停按钮。我们还需要一个 render
函数来指示模拟在画布上进行渲染(如果我们已经准备好)。
// Get the state. We don't offer access to the variable directly
// as we don't want anyone to change it!
this.getCurrentState = function() {
return currentState;
};
// Render the simulation. We can only render it if we've got the
// canvas.
this.render = function() {
if(canvas !== null && canvas !== undefined) {
simulation.render(canvas);
}
};
到目前为止,我们已经很熟悉函数了,所以让我们添加一个暂停模拟的函数:
// Pauses the simulation.
this.pause = function() {
// If we're already paused, there's nothing to do.
if(currentState === 'paused') {
return;
}
// Cancel the timer.
$interval.cancel(tickIntervalId);
// Set the status.
currentState = 'paused';
};
为什么我们要使用 $interval
来创建计时器?嗯,它只是标准间隔函数的包装器,但它与 Angular 一起工作,以便 Angular 在计时器触发后更新绑定。如果我们不使用它,我们就必须在每次滴答后显式告诉 Angular 更新其绑定。
现在我们有了构建视图的足够内容了!
第四部分 - 创建视图
这就是 Angular 的强大之处将显现出来的地方。让我们编写 index.html 文件——首先包含我们所有的 JavaScript 和 CSS 文件:
<!DOCTYPE html>
<html ng-app="langtonsant">
<head>
<title>Langton's Ant</title>
<link rel="stylesheet" type="text/css"
href="bower_components/bootstrap/dist/css/bootstrap.min.css">
<script src="bower_components/angular/angular.min.js"></script>
<script src="js/langtonsant.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
<script src="js/directives.js"></script>
</head>
我们在这里只包含我们的 JavaScript 文件和 Bootstrap 的 CSS。注意 ng-app
指令?这告诉 Angular 该 HTML 元素中的所有内容都应被视为我们 langtonsant
应用程序的一部分——从现在开始,我们可以使用 Angular 指令将视图绑定到控制器。
现在我们可以创建供模拟绘制的画布:
<body>
<canvas id="antcanvas"></canvas>
<div id="controls" ng-controller="MainController as main">
需要 CSS 来使画布全屏显示,但我们已经在前两篇文章中见过它了。新的是 ng-controller
指令。我们告诉 Angular 必须创建一个 MainController
的实例并将其命名为 main
。从现在开始,我们可以将此控制器用于 div
中的任何元素。这意味着我们可以做一些极其酷的事情,例如:
<button type="button" ng-click="main.getCurrentState() == 'running' ? main.pause() : main.run()"
class="btn btn-default">
<span ng-show="main.getCurrentState() == 'running'" class="glyphicon glyphicon-pause"></span>
<span ng-show="main.getCurrentState() != 'running'" class="glyphicon glyphicon-play"></span>
</button>
<button type="button" ng-click="main.reset()"
class="btn btn-default"><span class="glyphicon glyphicon-refresh">
</span></button>
<input type="text" class="form-control" ng-model="main.tickFrequency">
这很棒——我们创建了一个按钮,并用 ng-click
将点击事件连接起来。当我们点击时,我们检查模拟的状态。如果正在运行,我们就暂停它;如果已暂停,我们就运行它。在按钮内部,我们包含两个 span
——一个显示“播放”图标,一个显示“暂停”图标。我们仅在状态合适时显示每个图标,使用 ng-show
指令。我们还有一个文本输入框,通过 ng-model
属性绑定到滴答频率。这意味着如果我们更改输入,Angular 会为我们更改 JavaScript 对象。
所有以 ng-
开头的都是 Angular,我们可以看到它的强大之处——我们不需要手动打开或关闭或隐藏事物,我们让 Angular 来完成,这基于使用名为 main
的 MainController
的简单表达式的结果。
如果您是 Angular 的新手,这是核心功能,您可以在 实用 AngularJS 第一部分 - AngularJS 入门 中找到更多信息。
第五部分 - 更高级的功能
如果您打开 Langton's Ant 链接,您会看到可以点击左上角的齿轮图标来查看更多设置。
我们可以做的一件事是添加和删除格子状态——现在让我们来实现它。首先,我们将添加控制器函数来添加和删除状态:
// Removes a given state.
this.removeState = function(state) {
var index = this.states.indexOf(state);
if(index !== -1) {
this.states.splice(index, 1);
}
};
// Adds a new state.
this.addState = function() {
// Create a new state with the next colour in the list.
var colourIndex = this.states.length % this.defaultStateColours.length;
this.states.push({
direction: 'L',
colour: this.defaultStateColours[colourIndex]
});
};
remoteState
接受一个 state
对象并将其从列表中删除。addState
添加一个新的 state
,使用列表中的下一个颜色。就是这样!现在我们可以在视图中创建一个 state
表,并添加或删除它们的功能:
<table>
<thead>
<tr>
<td>Direction</td>
<td>Colour</td>
<td></td>
</tr>
</thead>
<tr ng-repeat="state in main.states">
<td><la-leftright value="state.direction"></la-leftright></td>
<td><la-colourpicker colour="state.colour"></la-colourpicker></td>
<td><a href="" ng-hide="$first" ng-click="main.removeState(state)">
<span class="glyphicon glyphicon-remove"></span></a></td>
</tr>
<tr>
<td></td>
<td></td>
<td><a href="" ng-click="main.addState()">
<span class="glyphicon glyphicon-plus">
</span></a></td>
</tr>
</table>
在这里,我们创建了一个三列的表格——方向、颜色和用于按钮的空间。我为每一列添加了一个标题。然后我们使用 ng-repeat
指令显示每个状态的一行。这个指令允许我们遍历一个数组并为每个元素显示内容。所以我们可以为每个状态构建一个表格行。
对于第一列,我使用的是 <la-leftright>
控件。看起来不熟悉?那是因为我们要创建它!我将左/右控件的值绑定到状态方向。然后我显示一个颜色选择器控件,绑定到状态颜色。最后,我显示一个“删除”按钮,点击时通过 ng-click
调用 removeState
。另外,通过使用 ng-hide="$first"
来隐藏按钮,如果我们在第一行。ng-hide
在表达式求值为 true
时隐藏元素——而 $first
是 Angular 为我们提供的一个特殊变量,对于第一行是 true
。$first
仅在 ng-repeat
区域可用——Angular 还有其他有用的变量,如 $index
、$last
、$even
、$odd
等。
最后,我们在表格中添加一行,其中只包含一个调用 addState
的加号按钮。
就是这样!
我们添加或删除状态,Angular 会为我们更新 DOM——这意味着我们可以轻松地添加像上述这样的复杂功能。
等等,我使用了两个奇怪的元素——la-leftright
和 la-colourpicker
——这些不是标准的 HTML,那么它们是什么?嗯,这是最后一部分——Angular 指令。
第六部分 - 自定义指令
我编写了 la-leftright
和 la-colourpicker
是因为我希望我的 HTML 看起来是这样的——它们是用于左/右控件和颜色选择器控件,这很明显。这就是我想要的 HTML 外观,Angular 可以帮助实现这一点。
自定义指令是添加到 HTML 中的元素或属性,Angular 会为你连接它们。ng-repeat
、ng-click
等都是指令,我们可以创建自己的指令。
让我们开始定义 leftright
指令——我希望它显示文本“left | right”,并下划线选定的方向,如果用户点击它,则更改它。我们创建了一个“controllers.js”文件,现在让我们创建一个“directives.js”文件:
angular.module('langtonsant.directives', [])
.directive('laLeftright', function() {
return {
restrict: 'E',
scope: {
'value': '='
},
templateUrl: "js/laLeftright.html"
};
});
这就是一个指令——我们使用“restrict
”来指定我们想将此指令用作元素(‘E
’)——我们也可以使用属性(‘A
’)或类(‘C
’),或者全部使用——我们甚至可以使用注释来定义指令。
通过设置 scope
,我是在说‘在我的指令中,我想要一个作用域。它有一个名为‘value
’的属性,并且它与‘value
’属性进行双向绑定。我可以使用 'value':'=leftorright'
来使用名为‘leftorright
’的属性作为作用域的输入,因为我只使用了‘=
’本身,它假设属性的名称是 value
。还有其他选项——例如 'value':'@something'
会将属性‘something
’绑定到 value
,但仅为单向。
最后,我指定了一个模板 URL——这是用于指令内容的 HTML 的 URL,而且它是一个非常简单的 HTML:
<a href=""
ng-style="{'text-decoration':value == 'L' ? 'underline' : 'none'}"
ng-click="value = 'L'">left</a> |
<a href=""
ng-style="{'text-decoration':value == 'R' ? 'underline' : 'none'}"
ng-click="value = 'R'">right</a>
我们有两个链接。第一个使用 ng-style
将文本装饰设置为下划线(如果 value 是‘L
’),否则不设置。它使用 ng-click
在点击时将 value 设置为‘L
’。第二个链接对‘R
’执行相同的操作。
指令可以如此简单!它们也可以非常高级,但您可以看到它们可以帮助我们快速创建可重用的客户端逻辑或表示标记。
结论
这里还缺少一些内容——移动模拟、缩放和颜色选择器,但如果您阅读了这篇文章,您应该能够理解所有额外的代码,我们已经展示了关键的新部分,这是最重要的。我们已经看到了 Bower 如何帮助我们处理第三方库,以及 Angular 如何帮助我们处理客户端逻辑和绑定。
代码中还有一些其他内容——有一个 NodeJS 服务器用于在测试时提供页面内容,但这将在以后的文章中进行探讨!
如果您喜欢看到 Angular 的功能,请关注我的实用 AngularJS 系列——我目前正在撰写它,并且涵盖了框架的所有部分。
一如既往,欢迎评论和建议,希望您喜欢这篇文章!下次,我们将深入探讨 NodeJS 的服务器端 JavaScript。
历史
- 2013 年 12 月 30 日:初始版本