当字段隐藏在 angular 中时清除模型值的最佳方法
Best way to clear model values when the field is hidden in angular
下面是一些代码,用于清除 angular 模型值,当通过 ng-show 隐藏模型的相应输入时,使用类名和 jquery,但它有难闻的气味,因为它操纵DOM 在控制器中(编辑 - 它不会操纵 DOM 它会更改范围模型值,但我并不热衷于使用 jquery)。有 "angular way" 可以做到这一点吗?
我应该补充一点,下面的代码只是为了概念验证,以表明解决方案是可行的。实际项目有非常复杂的业务规则来显示具有许多逻辑分支的部分、子部分和子部分等......所以很难按照@New Dev建议的那样在手表中编写逻辑......另外,我不希望在两个地方有逻辑:在所有显示和隐藏的 div 中以及在一个函数中......
<!doctype html>
<html xmlns:ng="http://angularjs.org" ng-app="app">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
</head>
<body ng-controller="MainCtrl">
<div style="padding:20px; background-color:silver;color:blue">{{person | json }}</div>
Name: <input ng-model="person.name" name="name" >
<div ng-show="person.name.length">
Age: <input ng-model="person.age" name="age" class="hide-clear">
<div ng-show="person.age.toString().length">
Hobby: <input ng-model="person.hobby" name="hobby" class="hide-clear">
</div>
</div>
<Script>
angular.module('app', [])
.controller('MainCtrl', function($scope,$log,$timeout){
$scope.person = {
name: 'mr smith',
age: 51,
hobby: 'coding'
}
$scope.$watchCollection(
//return the value to be watched
function($scope){
return $scope.person
},
//function to be called when changed
function(newValue,oldValue){
$timeout( function() {
$(".hide-clear").each(function(){
var t = $(this);
if( ! t.is(":visible") ) {
$scope.person[t.attr('name')] = '';
}
})
})
}
)
})
</Script>
</body>
</html>
很高兴您将上述方法视为糟糕的设计(或 "bad smell",如您所说)。实际上,Angular 方式(或更一般地,MVVM 方式)将仅操纵视图模型,并让视图模型驱动视图。
例如,您试图设置 $scope.person.age = ""
和 $scope.person.hobby = ""
时它们的父容器被 ng-show="person.name.length"
隐藏(即当 $scope.person.name
为空时)。不要使用容器的不可见性作为指标,而是首先使用导致容器不可见的原始数据。
$scope.$watch("person.name", function(val){
if (val === "") { // or, if (!val.length), to make it completely equivalent
$scope.person.age = "";
$scope.person.hobby = "";
}
});
上面的代码监视 $scope.person.name
为空(and/or undefined
,无论您的定义是什么)以设置其他属性。 View 对空 person.name
的反应对控制器来说根本不重要——它可以做一些动画或其他 UI 技巧。该逻辑仅处理视图模型状态。
上面的代码可以进一步改进以避免 $watch
并改为对导致 $scope.person.name
变空的事件做出反应。从您的示例来看,这似乎只是由于用户从文本框中删除了名称所致。
<input ng-model="person.name" ng-change="onPersonChanged()">
$scope.onPersonChanged = function(){
if (!$scope.person.name) {
$scope.person.age = "";
$scope.person.hobby = "";
}
};
这优于 $watch
,因为 $watch
在每个摘要周期触发,而 ng-change
仅在输入字段发生更改时触发。
以下是我的最佳尝试。我仍然使用 jquery 来检测元素是否可见,并且该指令不使用隔离范围,但至少所有逻辑都包含在两个指令中,可以在其他项目中重复使用:
指令代码(clearmModelWhenHidden.js)
angular.module('clearModelWhenHidden', [])
.directive('clearModelWhenHiddenContainer', function() {
return {
scope: false,
controller: function($scope, $parse, $timeout) {
$scope.registeredElements = [];
//since we dont' have an isolate scope, namespace our public API to avoid collision
this.clearModelWhenHidden = {};
//to share a method with child directives use the "this" scope and have children require the parent controller...
this.clearModelWhenHidden.register = function(e) {
$scope.registeredElements.push(e);
}
$scope.$watchCollection(
function() {
//convert the registered elements ng-model attribute from a string to an angular
//object that can be watched for changes
var parsedArray = [];
angular.forEach($scope.registeredElements, function(item, i) {
parsedArray.push($parse(item.attributes.ngModel)($scope))
});
return parsedArray;
},
function(newvalue) {
$timeout(function() {
angular.forEach($scope.registeredElements, function(item, i) {
var isVisible = $(item.element).is(':visible');
if (!isVisible) {
var value = $parse(item.attributes.ngModel)($scope);
//create a string that sets the ng-model of each element to an empty string,
//for example, person.name=''
var stringToEval = item.attributes.ngModel + '="" ';
console.log(stringToEval)
$parse(stringToEval)($scope);
}
})
});
}
);
}
}
})
.directive('clearModelWhenHidden', function() {
var link = function(scope, element, attributes, parentController) {
//pass in the element itself so we can used jquery to detect visibility and the attributes so the container can create a watch on the models
parentController.clearModelWhenHidden.register({
'element': element[0],
'attributes': attributes
});
}
return {
link: link,
require: '^clearModelWhenHiddenContainer'
}
})
和一个演示页面
<!doctype html>
<html xmlns:ng="http://angularjs.org" ng-app="app">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.min.js" type="text/javascript"></script>
<script language="javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.js"></script>
<script language="javascript" src="clearModelWhenHidden.js"></script>
</head>
<body ng-controller="MainCtrl as MainCtrl">
<div style="padding:20px; background-color:silver;color:blue">{{MainCtrl.person | json }}</div>
<div clear-model-when-hidden-container>
<section>
Name:
<input ng-model="MainCtrl.person.name" clear-model-when-hidden>
<div ng-show="MainCtrl.person.name.length">
<label>Age</label>:
<input ng-model="MainCtrl.person.age" clear-model-when-hidden>
<section ng-if="MainCtrl.person.age.toString().length">
<label>Hobby</label>:
<input ng-model="MainCtrl.person.hobby" clear-model-when-hidden>
</section>
</div>
</section>
</div>
</body>
</html>
下面是一些代码,用于清除 angular 模型值,当通过 ng-show 隐藏模型的相应输入时,使用类名和 jquery,但它有难闻的气味,因为它操纵DOM 在控制器中(编辑 - 它不会操纵 DOM 它会更改范围模型值,但我并不热衷于使用 jquery)。有 "angular way" 可以做到这一点吗?
我应该补充一点,下面的代码只是为了概念验证,以表明解决方案是可行的。实际项目有非常复杂的业务规则来显示具有许多逻辑分支的部分、子部分和子部分等......所以很难按照@New Dev建议的那样在手表中编写逻辑......另外,我不希望在两个地方有逻辑:在所有显示和隐藏的 div 中以及在一个函数中......
<!doctype html>
<html xmlns:ng="http://angularjs.org" ng-app="app">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
</head>
<body ng-controller="MainCtrl">
<div style="padding:20px; background-color:silver;color:blue">{{person | json }}</div>
Name: <input ng-model="person.name" name="name" >
<div ng-show="person.name.length">
Age: <input ng-model="person.age" name="age" class="hide-clear">
<div ng-show="person.age.toString().length">
Hobby: <input ng-model="person.hobby" name="hobby" class="hide-clear">
</div>
</div>
<Script>
angular.module('app', [])
.controller('MainCtrl', function($scope,$log,$timeout){
$scope.person = {
name: 'mr smith',
age: 51,
hobby: 'coding'
}
$scope.$watchCollection(
//return the value to be watched
function($scope){
return $scope.person
},
//function to be called when changed
function(newValue,oldValue){
$timeout( function() {
$(".hide-clear").each(function(){
var t = $(this);
if( ! t.is(":visible") ) {
$scope.person[t.attr('name')] = '';
}
})
})
}
)
})
</Script>
</body>
</html>
很高兴您将上述方法视为糟糕的设计(或 "bad smell",如您所说)。实际上,Angular 方式(或更一般地,MVVM 方式)将仅操纵视图模型,并让视图模型驱动视图。
例如,您试图设置 $scope.person.age = ""
和 $scope.person.hobby = ""
时它们的父容器被 ng-show="person.name.length"
隐藏(即当 $scope.person.name
为空时)。不要使用容器的不可见性作为指标,而是首先使用导致容器不可见的原始数据。
$scope.$watch("person.name", function(val){
if (val === "") { // or, if (!val.length), to make it completely equivalent
$scope.person.age = "";
$scope.person.hobby = "";
}
});
上面的代码监视 $scope.person.name
为空(and/or undefined
,无论您的定义是什么)以设置其他属性。 View 对空 person.name
的反应对控制器来说根本不重要——它可以做一些动画或其他 UI 技巧。该逻辑仅处理视图模型状态。
上面的代码可以进一步改进以避免 $watch
并改为对导致 $scope.person.name
变空的事件做出反应。从您的示例来看,这似乎只是由于用户从文本框中删除了名称所致。
<input ng-model="person.name" ng-change="onPersonChanged()">
$scope.onPersonChanged = function(){
if (!$scope.person.name) {
$scope.person.age = "";
$scope.person.hobby = "";
}
};
这优于 $watch
,因为 $watch
在每个摘要周期触发,而 ng-change
仅在输入字段发生更改时触发。
以下是我的最佳尝试。我仍然使用 jquery 来检测元素是否可见,并且该指令不使用隔离范围,但至少所有逻辑都包含在两个指令中,可以在其他项目中重复使用:
指令代码(clearmModelWhenHidden.js)
angular.module('clearModelWhenHidden', [])
.directive('clearModelWhenHiddenContainer', function() {
return {
scope: false,
controller: function($scope, $parse, $timeout) {
$scope.registeredElements = [];
//since we dont' have an isolate scope, namespace our public API to avoid collision
this.clearModelWhenHidden = {};
//to share a method with child directives use the "this" scope and have children require the parent controller...
this.clearModelWhenHidden.register = function(e) {
$scope.registeredElements.push(e);
}
$scope.$watchCollection(
function() {
//convert the registered elements ng-model attribute from a string to an angular
//object that can be watched for changes
var parsedArray = [];
angular.forEach($scope.registeredElements, function(item, i) {
parsedArray.push($parse(item.attributes.ngModel)($scope))
});
return parsedArray;
},
function(newvalue) {
$timeout(function() {
angular.forEach($scope.registeredElements, function(item, i) {
var isVisible = $(item.element).is(':visible');
if (!isVisible) {
var value = $parse(item.attributes.ngModel)($scope);
//create a string that sets the ng-model of each element to an empty string,
//for example, person.name=''
var stringToEval = item.attributes.ngModel + '="" ';
console.log(stringToEval)
$parse(stringToEval)($scope);
}
})
});
}
);
}
}
})
.directive('clearModelWhenHidden', function() {
var link = function(scope, element, attributes, parentController) {
//pass in the element itself so we can used jquery to detect visibility and the attributes so the container can create a watch on the models
parentController.clearModelWhenHidden.register({
'element': element[0],
'attributes': attributes
});
}
return {
link: link,
require: '^clearModelWhenHiddenContainer'
}
})
和一个演示页面
<!doctype html>
<html xmlns:ng="http://angularjs.org" ng-app="app">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.min.js" type="text/javascript"></script>
<script language="javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.js"></script>
<script language="javascript" src="clearModelWhenHidden.js"></script>
</head>
<body ng-controller="MainCtrl as MainCtrl">
<div style="padding:20px; background-color:silver;color:blue">{{MainCtrl.person | json }}</div>
<div clear-model-when-hidden-container>
<section>
Name:
<input ng-model="MainCtrl.person.name" clear-model-when-hidden>
<div ng-show="MainCtrl.person.name.length">
<label>Age</label>:
<input ng-model="MainCtrl.person.age" clear-model-when-hidden>
<section ng-if="MainCtrl.person.age.toString().length">
<label>Hobby</label>:
<input ng-model="MainCtrl.person.hobby" clear-model-when-hidden>
</section>
</div>
</section>
</div>
</body>
</html>