AngularJS自学之路(三)——控制器和表达式

控制器

控制器在AngularJS中的作用是增强视图。
AngularJS中的控制器是一个函数,用来向视图的作用域中添加额外的功能。我们用它来给作用域对象设置初始状态,并添加自定义行为。
当我们在页面上创建一个新的控制器时,AngularJS会生成并传递一个新的$scope给这个控制器。可以在这个控制器里初始化scope。由于AngularJS会负责处理控制器的实例化过程,我们只需编写构造函数即可。下面的例子展示了控制器初始化:

function FirstController($scope) {
$scope.message = "hello";
}

细心的读者会发现,我们是在全局作用域中创建的这个函数。这样做并不合适,因为会污染全局命名空间。更合理的方式是创建一个模块,然后在模块中创建控制器,如下所示:

var app = angular.module(‘app‘, []);
app.controller(‘FirstController‘, function($scope) {
$scope.message = "hello";
});

只需创建控制器作用域中的函数,就能创建可以在视图中使用的自定义操作。很幸运,AngularJS允许我们在视图中像调用普通数据一样调用$scope上的函数。

用内置指令ng-click可以将按钮、链接等其他任何DOM元素同点击事件进行绑定。ng-click指令将浏览器中的mouseup事件,同设置在DOM元素上的事件处理程序绑定在一起(例如,当浏览器在某个DOM元素上触发了点击事件,函数就会被调用)。和前面的例子类似,绑定看起来是这样的:

<div ng-controller="FirstController">
<h4>The simplest adding machine ever</h4>
<button ng-click="add(1)" class="button">Add</button>
<a ng-click="subtract(1)" class="button alert">Subtract</a>
<h4>Current count: {{ counter }}</h4>
</div>

按钮和链接都被绑定在了内部$scope的一个操作上,当点击任何一个元素时AngularJS都会调用相应的方法。注意,当设置调用哪个函数时,会同时用括号传递一个参数(add(1))。
下面给FirstController添加一个操作:

app.controller(‘FirstController‘, function($scope) {
$scope.counter = 0;
$scope.add = function(amount) { $scope.counter += amount; };
$scope.subtract = function(amount) { $scope.counter -= amount; };
});

控制器可以将与一个独立视图相关的业务逻辑封装在一个独立的容器中。尽可能地精简控制器是很好的做法。作为AngularJS开发者,使用依赖注入来访问服务可以实现这个目的。

AngularJS同其他JavaScript框架最主要的一个区别就是,控制器并不适合用来执行DOM操作、格式化或数据操作,以及除存储数据模型之外的状态维护操作。它只是视图和$scope之间的桥梁。

控制器嵌套(作用域包含作用域)

默认情况下,AngularJS在当前作用域中无法找到某个属性时,便会在父级作用域中进行查找。如果AngularJS找不到对应的属性,会顺着父级作用域一直向上寻找,直到抵达$rootScope为止。如果在rootScope中也找不到,程序会继续运行,但视图无法更新。

通过例子来看一下这个行为。创建一个ParentController,其中包含一个user对象,再创建一个ChildController来引用这个对象:

app.controller(‘ParentController‘, function($scope) {
$scope.person = {greeted: false};
});
app.controller(‘ChildController‘, function($scope) {
$scope.sayHello = function() {
$scope.person.name = ‘Ari Lerner‘;
};
});

如果我们将ChildController置于ParentController内部,那ChildController的$scope对象的父级作用域就是ParentController的scope对象。根据原型继承的机制,我们可以在子作用域中访问ParentController的scope对象。

例如,我们可以在ChildController的DOM元素中访问定义在ParentController中的person对象

<div ng-controller="ParentController">
<div ng-controller="ChildController">
<a ng-click="sayHello()">Say hello</a>
</div>
{{ person }}
</div>

我们看到,点击按钮时,可以在ChildController中访问ParentController中$scope.person的值,就好像person对象定义在ChildController的scope中一样。
技术分享

控制器应该尽可能保持短小精悍,而在控制器中进行DOM操作和数据操作则是一个不好的实践。

设计良好的应用会将复杂的逻辑放到指令和服务中。通过使用指令和服务,我们可以将控制器重构成一个轻量且更易维护的形式
简洁的控制器:

angular.module(‘myApp‘, [])
.controller(‘MyController‘, function($scope,UserSrv) {
// 内容可以被指令控制
$scope.onLogin = function(user) {
UserSrv.runLogin(user);
};
});

表达式

前面已经见过使用表达式的示例。用{{ }}符号将一个变量绑定到$scope上的写法本质上就是一个表达式:{{ expression }}。当用watch进行监听时,AngularJS会对表达式或函数进行运算。
表达式和eval(javascript)非常相似,但是由于表达式由AngularJS来处理,它们有以下显著不同的特性:

  1. 所有的表达式都在其所属的作用域内部执行,并有访问本地$scope的权限;
  2. 如果表达式发生了TypeError和ReferenceError并不会抛出异常;
  3. 不允许使用任何流程控制功能(条件控制,例如if/else);
  4. 可以接受过滤器和过滤器链。

对表达式进行的任何操作,都会在其所属的作用域内部执行,因此可以在表达式内部调用那些限制在此作用域内的变量,并进行循环、函数调用、将变量应用到数学表达式中等操作。

解析AngularJS 表达式

尽管AngularJS会在运行$digest循环的过程中自动解析表达式,但有时手动解析表达式也是非常有用的。

AngularJS通过$parse这个内部服务来进行表达式的运算,这个服务能够访问当前所处的作用域。

将$parse服务注入到控制器中,然后调用它就可以实现手动解析表达式。举例来说,如果页面上有一个输入框绑定到了expr变量上,如下所示:

<div ng-controller="MyController">
<input ng-model="expr" type="text" 
placeholder="Enter an expression" />
<h2>{{ parseValue }}</h2>
</div>

我们可以在MyController中给expr这个表达式设置一个$watch并解析它:

angular.module("myApp", [])
.controller(‘MyController‘,
function($scope,$parse) {
$scope.$watch(‘expr‘, function(newVal, oldVal, scope) {
if (newVal !== oldVal) {
// 用该表达式设置parseFun
var parseFun = $parse(newVal);
// 获取经过解析后表达式的值
$scope.parsedValue = parseFun(scope);
}
});
});

插值字符串

在AngularJS中,我们的确有手动运行模板编译的能力。例如,插值允许基于作用域上的某个条件实时更新文本字符串。

要在字符串模板中做插值操作,需要在你的对象中注入$interpolate服务。在下面的例子中,我们将会将它注入到一个控制器中:

angular.module(‘myApp‘, []).controller(‘MyController‘,
function($scope, $interpolate) {
// 我们同时拥有访问$scope$interpolate服务的权限
});

$interpolate服务是一个可以接受三个参数的函数,其中第一个参数是必需的。

  1. text(字符串):一个包含字符插值标记的字符串。
  2. mustHaveExpression(布尔型):如果将这个参数设为true,当传入的字符串中不含有表达式时会返回null。
  3. trustedContext(字符串):AngularJS会对已经进行过字符插值操作的字符串通过$sec.getTrusted()方法进行严格的上下文转义。

$interpolate服务返回一个函数,用来在特定的上下文中运算表达式。
设置好这些参数后,就可以在控制器中进行字符插值的操作了。例如,假设我们希望可以在电子邮件的正文中进行实时编辑,当文本发生变化时进行字符插值操作并将结果展示出来。

<div ng-controller="MyController">
<input ng-model="to"
type="email"
placeholder="Recipient" />
<textarea ng-model="emailBody"></textarea>
<pre>{{ previewText }}</pre>
</div>

由于控制器内部设置了一个需要每次变化都重新进行字符插值的自定义输入字段,因此需要设置一个$watch来监听数据的变化。

简而言之,$watch函数会监视scope上的某个属性。只要属性发生变化就会调用对应的函数。可以使用watch函数在scope上某个属性发生变化时直接运行一个自定义函数。

在控制器中,我们设置了$watch来监视邮件正文的变化,并将emailBody属性的值进行字符插值后的结果赋值给previewText属性。

angular.module(‘myApp‘, [])
.controller(‘MyController‘, function($scope, $interpolate) {
// 设置监听
$scope.$watch(‘emailBody‘, function(body) {
if (body) {
var template = $interpolate(body);
$scope.previewText =
template({to: $scope.to});
}
};
});
在

现在,在{{ previewText }}内部的文本中可以将{{ to }}当做一个变量来使用,并对文本的变化进行实时更新。

用startSymbol()方法可以修改标识开始的符号。这个方法接受一个参数。

用endSymbol()方法可以修改标识结束的符号。这个方法也接受一个参数。

如果要修改这两个符号的设置,需要在创建新模块时将$interpolateProvider注入进去。

angular.module(‘emailParser‘, [])
.config([‘$interpolateProvider‘, function($interpolateProvider) {
$interpolateProvider.startSymbol(‘__‘);
$interpolateProvider.endSymbol(‘__‘);
}])
.factory(‘EmailParser‘, [‘$interpolate‘, function($interpolate) {
// 处理解析的服务
return {
parse: function(text, context) {
var template = $interpolate(text);
return template(context);
}
};
}]);

现在,我们已经创建了一个模块,可以将它注入到应用中,并在邮件正文的文本中运行这个邮件解析器:

angular.module(‘myApp‘, [‘emailParser‘])
.controller(‘MyController‘, [‘$scope‘, ‘EmailParser‘,
function($scope, EmailParser) {
// 设置监听
$scope.$watch(‘emailBody‘, function(body) {
if (body) {
$scope.previewText = EmailParser.parse(body, {
to: $scope.to
});
}
});
}]);

现在用自定义的 __ 符号取代默认语法中的 {{ }} 符号来请求插值文本。
由于我们将表达式开始和结束的符号都设置成了__,因此需要将HTML修改成用这个符号取代{{ }}的版本,

<div id="emailEditor">
<input ng-model="to"
type="email"
placeholder="Recipient" />
<textarea ng-model="emailBody"></textarea>
</div>
<div id="emailPreview">
<pre>__ previewText __</pre>
</div>

技术分享

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。