AngularJS 模型
AngularJS 模型
在我之前的文章中,我讨论了 AngularJS 控制器的各个方面及其在 MVC(模型视图控制器)架构中的作用。在本文中,我将更多地讨论$scope对象以及在 AngularJS 应用程序中创建和管理模型的不同方法。
基于 MVC 的应用程序中模型通常负责对视图中使用的数据进行建模并处理用户交互,例如单击按钮、滚动或在视图中引起其他更改。
在基本示例中,AngularJS 使用$scope对象作为模型。但是,在上一篇文章中,我展示了如何使用controllerAs方法将控制器本身添加到具有给定名称的范围中,并实质上将其用作模型。在后一种情况下,控制器是具有构成模型的数据和方法的对象。但控制器仍然附加到 $ scope,从而更容易设置与控制器对象的绑定。
显然,当我们使用模型时,$scope很重要。
理解 $scope
很容易将$scope视为模型对象的一个众所周知的名称,但这是错误的。$scope不仅仅是一个注入到系统中来表示我们需要的数据和方法的对象。它是数据绑定和将模型连接到视图的关键,并且它还有其他方法来支持两者之间的通信。
顾名思义,作用域是应用程序某个部分的表示。实际上,作用域代表页面的 DOM(文档对象模型)的某个部分。通常,每个新的 Angular 指令(例如控制器)都会有一个与之关联的作用域。就像文档由 DOM 表示为树一样,给定页面中的作用域也嵌套在树结构中。页面有一个根作用域,其他作用域作为子作用域添加到该根作用域。
每个作用域都可以访问其自己的属性和函数以及其父级的属性和函数。AngularJS 创建作用域树并在$parent字段中创建对父级作用域的引用。虽然可以从当前作用域访问父级,但通常将控制器或其他指令中的代码与容器中的作用域耦合不是一个好主意。但是,由于作用域处于层次结构中,因此可以利用父级关系在视图中进行数据绑定,因为层次结构更为人所知,并且可以避免耦合。让我们看一个利用作用域层次结构的示例。
范围和数据绑定
在 MVC 应用程序中,模型的主要用途之一是提供视图中显示的数据并实现从视图调用的函数。在 AngularJS 应用程序中,数据绑定语句是根据当前范围进行评估的。对于如下所示的控制器,正在创建和管理的$scope对象是控制器的当前范围,可用于在视图中进行绑定。
angular.module("hackApp").controller(
"todoController", function($scope){
$scope.description = "A list of things to do.";
$scope.todos = [];
$scope.newTodo = {"title":"", "completed":false};
$scope.addTodo = function(){
var toAdd = {"title":$scope.newTodo.title, "completed":$scope.newTodo.completed};
$scope.todos.push(toAdd);
};
});
当我们在相应的 AngularJS 视图中使用如下语法时,绑定语句中的表达式将根据控制器中操作的范围对象进行评估。
<div ng-controller="todoController">
<span>{{description}}</span>
<form>
<label for="newTodo">Task:</label>
<input type="text" id="newTodo" placeholder="New Task" ng-model="newTodo.title" />
<button ng-click="addTodo()" value="Add">Add</button>
</form>
<ul>
<li ng-repeat="todo in todos"><input type="checkbox" ng-model="todo.complete" />{{todo.title}}</li>
</ul>
</div>
请注意,在模板中可以使用包含其名称的简单表达式引用在$scope对象上设置的“description”字段。对于创建新待办事项的输入,使用ng-model属性。它表示输入应绑定到应用于 $ scope 的“newTodo”上的“title”字段。同样,ng-click属性用于将按钮单击事件挂接到在范围上定义的addTodo函数。
在此视图中,尽管只有一个控制器,但实际上存在多个作用域。应用程序具有可用于页面的$rootScope 。控制器创建一个子作用域和ng-repeat,它将为待办事项列表中的每个项目创建一个 LI 元素。它还为每个元素创建一个新作用域。列表项的每个子作用域都将控制器作用域设置为父作用域,并使用当前正在迭代的值填充“todo”字段。
在重复中,可能需要从父作用域(即控制器的作用域)访问数据。一种方法是使用当前作用域上的特殊$parent属性来达到上级并从父作用域访问数据。在此示例中,使用 $parent.todos 访问“todos”数组。
<ul>
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.complete" />
{{todo.title}} ({{$index + 1}} of {{$parent.todos.length}})
</li>
</ul>
事实证明,在这种情况下,使用$parent属性是不必要的。AngularJS 数据绑定首先在当前范围内查找表达式中引用的字段。如果在当前范围内找不到该字段,AngularJS 将在父范围内查找该字段。因此,可以将前面的示例修改为以下内容,并且仍然有效。
{{todo.title}} ({{$index + 1}} of {{todos.length}})
在重复场景中,此功能使数据绑定到父级作用域的状态变得更加容易,甚至更加直观。明确使用$parent字段很重要的一种情况是,如果当前作用域和父级都具有同名的字段。如果您希望显示父级的值,则$parent引用就变得必不可少。
事件发射和广播
该模型不仅具有要绑定到输入和显示元素的字段,还具有充当事件处理程序的功能。就像数据绑定可以使用范围层次结构来绑定到直接范围之外的数据一样,在范围中触发的事件也可以广播到子范围或发送到父范围。这在使用上一篇文章中讨论的嵌套控制器时特别有用。
$ emit和$broadcast函数可用于替代传统的传统绑定语句。例如,以下模板标记将导致点击事件发出命名事件和参数。此事件将“冒泡”到任何监听范围。
<li ng-repeat="todo in todos" ng-click="$emit('todoSelect', todo.title)"></li>
在父范围上,需要订阅事件并需要定义处理程序(如此处所示)。
$scope.$on("todoSelect", function(event, args){
$scope.message = "Selected:" + args + " from scope: " + event.targetScope.$id + " handling scope: " + event.currentScope.$id;
});
使用$broadcast 的工作原理类似,但作用域树向下而不是向上遍历以查找处理程序。一个很大的区别是,对于发出的事件,事件传播可以由作用域层次结构中的任何侦听器停止。广播事件无法取消,并会传递给所有子作用域。在这两种情况下,都可以将事件标记为阻止默认处理,以便浏览器不会根据事件执行默认操作。
申请并观看
AngularJS 应用程序中数据绑定的一大特点是,对模型所做的更改会直接反映在视图中,而视图中的更改会应用于$scope。AngularJS 会为您处理此问题,因此无需编写特殊代码来连接更改侦听器。AngularJS 使用$scope对象上的两种方法来实现这一点:$watch和$apply。
顾名思义,每个函数都允许一半的更新行为。$watch函数用于监视模型的某些方面并在其更改时收到通知。$apply函数用于将更改应用于模型。如上所述,在大多数情况下,不需要直接使用这些函数,因为 AngularJS 会根据需要调用它们来管理数据绑定。
如果您不需要了解这些函数,为什么要提及它们?事实证明,它们在某些情况下很有用。应用更改的最常见原因是您在承诺或回调中修改了模型。如果您使用的是$http服务,AngularJS 会为您非常干净地处理这个问题,但如果您使用的是另一个库并发现自己在承诺或回调中修改了模型,则可能需要$watch 。对$scope (模型)进行更改后,调用 apply 方法将触发摘要。然后,这将提醒任何观察者注意更改。
asynchronousCall(function(data)){
$scope.myproperty = data.someValue;
$scope.$apply();
};
同样,$watch方法不是标准数据绑定所必需的,但可能会出现应用程序需要知道对象已更改的情况。在这些情况下,$watch 方法允许监视某些值并执行侦听器函数来比较旧值和新值并根据任何更改采取行动。以下示例显示如何将侦听器连接到范围内字段的更改事件。
$scope.$watch(
function(scp) { return scp.newTodo.title; },
function(newValue, oldValue) {
if(newValue !== oldValue){
console.log("Value of title changed to:" + newValue);
}
}
);
函数中的第一个参数是返回要监视的范围的值的函数。将范围传递到函数中,可以更轻松地返回要监视的值。
第二个参数是侦听器函数,它接收旧值和新值,然后可以采取行动。请记住,AngularJS 执行的每个摘要循环都会调用监视函数,因此使用的侦听器函数应该快速执行。
对于其他场景,还有watchCollection和watchGroup。WatchCollection允许您监视数组并在添加或删除项目时收到通知。watchGroup方法允许为受监视的一组项目分配单个侦听器。
避免直接使用 $scope
在上一篇关于控制器的文章中,我们介绍了“controllerAs”的概念。这是将控制器设置为具有给定名称的作用域上的属性并将控制器用作模型的想法。对我们的标记进行一点小改动,为作用域上的控制器命名,并通过控制器引用模型属性。
<div ng-controller="todoAsController as todoC">
<span>{{todoC.message}}</span>
<span>{{todoC.description}}</span>
<form>
<label for="newTodo2">Task:</label>
<input type="text" id="newTodo2" placeholder="New Task" ng-model="todoC.newTodo.title" />
<button ng-click="todoC.addTodo()" value="Add">Add</button>
</form>
<ul>
<li ng-repeat="todo in todoC.todos"><input type="checkbox" ng-model="todo.complete" /> {{todo.title}} ({{$index + 1}} of {{todoC.todos.length}})</li>
</ul>
</div>
请注意ng-controller指令中的“as todoC” 。这指示 AngularJS 在名为“todoC”的作用域上创建一个名为“todoC”的字段,并将其值设置为控制器。现在,控制器本身不再直接在作用域上设置字段和函数,而是变成了模型,如下所示。
angular.module("hackApp").controller(
"todoAsController", function(){
var vm = this;
vm.description = "A list of things to do.";
vm.todos = [];
vm.newTodo = {"title":"", "completed":false};
vm.addTodo = function(){
var toAdd = {"title":vm.newTodo.title, "completed":vm.newTodo.completed};
vm.todos.push(toAdd);
};
});
第一步是获取对控制器的本地引用,以避免此引用出现问题,然后在变量上设置字段和函数。控制器现在是模型,这简化并澄清了代码。此外,它消除了编写代码以达到父范围或其他操作的诱惑,这些操作会将此控制器与其职责范围之外的对象耦合在一起。
使用此模型时,仍需要使用$scope来处理已发出或广播的事件。在这种情况下,在使用范围时,请尽量限制自己仅使用$emit、$broadcast和<font style="
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~