对于任何具有一点经验的Angular用户而言,直接从控制器直接修改DOM是不好的做法。一个原因是它导致DOM操作代码被本地化并且不能被重用。从这个优秀的Toptal Angular tutorial开始,还有两个原因不能从控制器中操作DOM:
第一个原因是显而易见的,因为控制器只是那个,即代理视图和模型之间的信息交换的实体。但是,我不明白第二点是什么意思。
这个问题实际上是由我目前面临的真正的UI问题所驱动的。我的团队中的开发人员(他将保持无名)实际上将代码添加到我们的一个控制器中,这些控制器直接操作DOM。以下是感兴趣区域的小屏幕截图:
点击铅笔会将我的名字变成可编辑的文本框。但是,在某些浏览器中,我们会看到瞬间闪烁,其中所有上述DOM元素似乎都跳到了整个地方。似乎发生了一些很奇怪的事情,我想了解它是什么。
以下是上述表单中HTML的简化版本:
<input class="usrProfileTextBox" id="displayNameTextBox"
ng-show="editingDisplayName" type="text" ng-model="displayName"
ng-keyup="$event.keyCode == 13 ? changeDisplayName() : null"
ng-blur="changeDisplayName()" />
<div class="usrProfileRightCol">
<label class="usrProfileNameLabel" ng-hide="editingDisplayName">{{displayName}}
</label>
<span ng-hide="editingDisplayName" ng-click="showEditDisplayName()"
class=" pointer-click glyphicon glyphicon-pencil"></span>
</div>
并且,为了完整性,这里是控制器功能,当用户单击用户名框的编辑按钮时会触发该功能:
$scope.showEditDisplayName=function(){
$scope.previousDisplayName = $scope.displayName;
$scope.editingDisplayName = true;
$timeout(function() {
document.getElementById("displayNameTextBox").value=$scope.displayName;
document.getElementById("displayNameTextBox").focus();
});
}
当我们的控制器直接操作DOM时,任何人都可以了解实际发生的事情吗?
答案 0 :(得分:1)
第一个问题是关注点的分离。角色根据指令解剖分布。控制器定义范围内的东西(对于controllerAs语法是this
),链接函数操纵DOM并将所有控制器与事物联系在一起。
第二个问题是directive compilation precedence。子元素DOM元素可能在控制器构造函数和绑定中不可用。角色分配表明这些东西在编译,预链接和后链接功能中效果最好。
这是Angular 1.4.x及更低版本的既定社区实践。由于引入了组件以及Angular 1和Angular 2之间的融合过程,Angular 1.5中的情况发生了很大变化。
由于组件中没有链接功能,因此可以在控制器中使用lifecycle hooks实现它们。来自不使用require
d控制器的链接函数的所有代码分别进入钩子 - 从预链接到$onInit
钩子,从后链接到$postLink
钩子。
问题中列出的代码段中没有优先级问题,因为DOM修改是在点击时运行而不是在初始化时运行。问题是使用ng-controller
指令而不是自定义指令/组件的层次结构。在精心设计的应用程序中,可能根本没有ng-controller
指令。
document.getElementById
有代码味道,因为它会影响可测试性。此代码可能根本不需要DOM操作,因为使用数据绑定和ng-focus
指令可能会实现相同的操作。
在控制器/范围方法中不允许任何DOM操作本身并不是目的(如果应该在ng-click
上进行DOM操作,显然应该在某些方法中完成)。当事情以一种惯用于Angular的方式完成时,它们就可以避免。
答案 1 :(得分:1)
视图将在依赖视图中的$scope
参数更改后重新编译。在你的情况下它是editingDisplayName
。我们遇到了同样的问题,我们可以通过以下方式完成这项工作:
$scope
变量中两个项目的显示状态。 问题的根源是使用editingDisplayName
重新编译。它不是同时在两个元素的一个渲染步骤中设置的。由于ng-hide
的JavaScript的异步进行,这会导致小的闪烁。这也可能发生在指令或本机JavaScript中。它不依赖ng-controller
。
检查以下尝试。它处理两个showState
中元素的$scopes
。多数民众赞成我们如何解决这种闪烁问题。 (由于你的模糊功能,你可能需要改进这个例子)
<input class="usrProfileTextBox"
id="displayNameTextBox"
ng-show="showInputBox"
type="text"
ng-model="displayName"
ng-keyup="$event.keyCode == 13 ? changeDisplayName() : null"
ng-blur="changeDisplayName()" />
<div class="usrProfileRightCol"
ng-show="showTextBox">
<label class="usrProfileNameLabel">
{{displayName}}
</label>
<span ng-click="showEditDisplayName()"
class="pointer-click glyphicon glyphicon-pencil">
</span>
</div>
$scope.showEditDisplayName = function(){
$scope.previousDisplayName = $scope.displayName;
$scope.showTextBox = false;
$timeout(function () {
document.getElementById("displayNameTextBox").value=$scope.displayName;
document.getElementById("displayNameTextBox").focus();
$scope.showInputBox = true;
}, 100);
};
您可能会使用AngularJS指令替换本机JavaScript集value
和focus
。但这只是一个重构暗示。
Tim的编辑:
这个答案的主旨基本上解决了这个问题,但实际导致闪烁的函数是changeDisplayName()
,当用户模糊输入框时会调用该函数,该输入框应隐藏该框并显示更新的用户名标签。这是我最终在生产中使用的代码。注意小心使用定时器分隔输入框的隐藏(第一个),然后显示用户名标签(第二个):
$scope.changeDisplayName=function(){
if ($scope.showInputBox == false) {
return; // prevent function from being called twice from both keypress
} // and blur events
// hide the input box, pause, then show the username label
$scope.showInputBox = false;
$timeout(function() {
$scope.showDisplayLabel = true;
}, 100);
}