我在 DOM 中有一个 Angular 模板。当我的控制器从服务中获取新数据时,它会更新 $scope 中的模型,并重新渲染模板。到目前为止一切都很好。
问题是在模板被重新渲染并且在 DOM 中(在本例中为 jQuery 插件)之后,我还需要做一些额外的工作。
似乎应该有一个事件要听,比如 AfterRender,但我找不到任何这样的东西。也许指令会是一种方法,但它似乎也太早了。
这是一个概述我的问题的 jsFiddle:Fiddle-AngularIssue
== 更新 ==
基于有用的评论,我相应地切换到一个指令来处理 DOM 操作,并在指令中实现了一个模型 $watch。但是,我仍然有同样的基本问题; $watch 事件内部的代码在模板被编译并插入 DOM 之前触发,因此,jquery 插件总是在评估一个空表。
有趣的是,如果我删除异步调用,整个事情就可以正常工作,所以这是朝着正确方向迈出的一步。
这是我更新的 Fiddle 以反映这些更改:http://jsfiddle.net/uNREn/12/
首先,处理渲染的正确位置是指令。我的建议是通过像这样的指令来包装 DOM 操作 jQuery 插件。
我遇到了同样的问题并想出了这个片段。它使用 $watch
和 $evalAsync
来确保您的代码在 ng-repeat
之类的指令得到解析并且 {{ value }}
之类的模板得到呈现后运行。
app.directive('name', function() {
return {
link: function($scope, element, attrs) {
// Trigger when number of children changes,
// including by directives like ng-repeat
var watch = $scope.$watch(function() {
return element.children().length;
}, function() {
// Wait for templates to render
$scope.$evalAsync(function() {
// Finally, directives are evaluated
// and templates are renderer here
var children = element.children();
console.log(children);
});
});
},
};
});
希望这可以帮助您防止一些挣扎。
这篇文章很旧,但我将您的代码更改为:
scope.$watch("assignments", function (value) {//I change here
var val = value || null;
if (val)
element.dataTable({"bDestroy": true});
});
}
见jsfiddle。
希望对你有帮助
按照 Misko 的建议,如果您想要异步操作,那么不要使用 $timeout() (这不起作用)
$timeout(function () { $scope.assignmentsLoaded(data); }, 1000);
使用 $evalAsync() (确实有效)
$scope.$evalAsync(function() { $scope.assignmentsLoaded(data); } );
Fiddle。我还添加了一个“删除数据行”链接,它将修改 $scope.assignments,模拟对数据/模型的更改——以表明更改数据是有效的。
概念概述页面的 Runtime 部分说明,当您需要在当前堆栈框架之外但在浏览器呈现之前发生某些事情时,应该使用 evalAsync。 (在这里猜测......“当前堆栈框架”可能包括 Angular DOM 更新。)如果您需要在浏览器呈现后发生某些事情,请使用 $timeout。
但是,正如您已经发现的那样,我认为这里不需要异步操作。
$scope.$evalAsync($scope.assignmentsLoaded(data));
没有任何意义 IMO。 $evalAsync()
的参数应该是一个函数。在您的示例中,您在应注册函数以供以后执行时调用该函数。
我发现最简单(便宜又愉快)的解决方案就是在最后一个渲染元素的末尾添加一个带有 ng-show = "someFunctionThatAlwaysReturnsZeroOrNothing()" 的空跨度。当检查是否应显示 span 元素时,将运行此函数。执行此函数中的任何其他代码。
我意识到这不是最优雅的做事方式,但是,它对我有用......
我也遇到过类似的情况,虽然在动画开始时我需要删除加载指示器的地方略有相反,但在移动设备上,角度的初始化速度比要显示的动画快得多,并且使用 ng-cloak 是不够的,因为加载指示器是在显示任何真实数据之前就已将其删除。在这种情况下,我只是将 my return 0 函数添加到第一个渲染元素,并在该函数中翻转了隐藏加载指示器的 var。 (当然我在这个函数触发的加载指示器中添加了一个 ng-hide。
$anchorScroll
服务,但似乎没有什么能解决问题。黑客但有效。
我认为您正在寻找 $evalAsync http://docs.angularjs.org/api/ng.$rootScope.Scope#$evalAsync
最后我找到了解决方案,我正在使用 REST 服务来更新我的收藏。为了转换数据表 jquery 是以下代码:
$scope.$watchCollection( 'conferences', function( old, nuew ) {
if( old === nuew ) return;
$( '#dataTablex' ).dataTable().fnDestroy();
$timeout(function () {
$( '#dataTablex' ).dataTable();
});
});
我不得不经常这样做。我有一个指令,需要在模型内容完全加载到 DOM 后执行一些 jquery 内容。所以我把我的逻辑放在链接中:指令的函数并将代码包装在 setTimeout(function() { ..... }, 1); setTimout 将在加载 DOM 后触发,1 毫秒是加载 DOM 后代码执行之前的最短时间。这似乎对我有用,但我确实希望 angular 在模板加载完成后引发一个事件,以便该模板使用的指令可以执行 jquery 内容并访问 DOM 元素。希望这可以帮助。
$scope.$evalAsync() 或 $timeout(fn, 0) 对我来说都不可靠。
我不得不将两者结合起来。我制定了一个指令,并且将优先级设置为高于默认值以获得良好的衡量标准。这是它的指令(注意我使用 ngInject 注入依赖项):
app.directive('postrenderAction', postrenderAction);
/* @ngInject */
function postrenderAction($timeout) {
// ### Directive Interface
// Defines base properties for the directive.
var directive = {
restrict: 'A',
priority: 101,
link: link
};
return directive;
// ### Link Function
// Provides functionality for the directive during the DOM building/data binding stage.
function link(scope, element, attrs) {
$timeout(function() {
scope.$evalAsync(attrs.postrenderAction);
}, 0);
}
}
要调用该指令,您可以这样做:
<div postrender-action="functionToRun()"></div>
如果你想在 ng-repeat 完成运行后调用它,我在我的 ng-repeat 和 ng-if="$last" 中添加了一个空跨度:
<li ng-repeat="item in list">
<!-- Do stuff with list -->
...
<!-- Fire function after the last element is rendered -->
<span ng-if="$last" postrender-action="$ctrl.postRender()"></span>
</li>
在某些情况下,您更新服务并重定向到新视图(页面),然后在更新服务之前加载指令,然后您可以使用 $rootScope.$broadcast 如果您的 $watch 或 $timeout 失败
看法
<service-history log="log" data-ng-repeat="log in requiedData"></service-history>
控制器
app.controller("MyController",['$scope','$rootScope', function($scope, $rootScope) {
$scope.$on('$viewContentLoaded', function () {
SomeSerive.getHistory().then(function(data) {
$scope.requiedData = data;
$rootScope.$broadcast("history-updation");
});
});
}]);
指示
app.directive("serviceHistory", function() {
return {
restrict: 'E',
replace: true,
scope: {
log: '='
},
link: function($scope, element, attrs) {
function updateHistory() {
if(log) {
//do something
}
}
$rootScope.$on("history-updation", updateHistory);
}
};
});
我提出了一个非常简单的解决方案。我不确定这是否是正确的方法,但它在实际意义上有效。让我们直接看我们想要渲染什么。例如,在包含一些 ng-repeat
的指令中,我会注意段落或整个 html 的文本长度(您可能还有其他内容!)。该指令将如下所示:
.directive('myDirective', [function () {
'use strict';
return {
link: function (scope, element, attrs) {
scope.$watch(function(){
var whole_p_length = 0;
var ps = element.find('p');
for (var i=0;i<ps.length;i++){
if (ps[i].innerHTML == undefined){
continue
}
whole_p_length+= ps[i].innerHTML.length;
}
//it could be this too: whole_p_length = element[0].innerHTML.length; but my test showed that the above method is a bit faster
console.log(whole_p_length);
return whole_p_length;
}, function (value) {
//Code you want to be run after rendering changes
});
}
}]);
注意代码实际上是在渲染更改而不是完整渲染之后运行的。但我想在大多数情况下,只要发生渲染更改,您就可以处理这些情况。如果您只想在渲染完成后一次运行代码,您还可以考虑将此p
的长度(或任何其他度量)与您的模型进行比较。 感谢您对此提出任何想法/意见。
您可以使用 angular-ui utils 的“jQuery Passthrough”模块。我成功地将一个 jQuery touch carousel 插件绑定到一些我从 Web 服务异步检索并使用 ng-repeat 呈现它们的图像。
在我的解决方案中,我有一些需要首先加载的自定义指令,因为它们包含其兄弟指令调用的函数的定义。例如:
<div id="container">
<custom-directive1></custom-directive1>
<custom-directive2></custom-directive2>
<custom-directive3></custom-directive3>
</div>
不幸的是,这里没有一个解决方案对我有用,因为它们只在渲染指令后才有效,而不是在指令代码后面。
因此,当我实现上述任何解决方案时,为了执行一些加载函数,即使渲染了指令,作用域也不知道这些指令中的函数是什么。
所以我在控制器的任何地方创建了一个 observable:
//Call every time a directive is loaded
$scope.$watch('directiveLoaded', function (value) {
debugger;
if (value == document.querySelector('#container').children.length) {
//Its ok to use childHead as we have only one child scope
$scope.$$childHead.function1_Of_Directive1();
$scope.$$childHead.function1_Of_Directive2();
}
});
然后我有这两个指令,我放置
scope.$parent.directiveLoaded += 1;
在每个指令的底部。因为在控制器中我定义了 observable,所以每次更新变量 directiveLoaded
时,它都会执行 observable 函数。是的,我知道这是一个 hack,但要保证所有指令在执行最终函数之前完成渲染及其背后的代码,这是一个很小的代价。
为了完成这里的演示,有两个指令定义了以后需要调用的函数。
指令1
(function () {
app.directive('customDirective1', function () {
return {
restrict: 'E',
templateUrl: '/directive1.html',
link: function (scope) {
scope.function1_Of_Directive1 = function() {
scope.function2_Of_Directive2();
console.log("F1_D1")
}
//AT BOTTOM OF EVERY DIRECTIVE
scope.$parent.directiveLoaded += 1;
}
}
});
})();
指令2
(function () {
app.directive('customDirective2', function () {
return {
restrict: 'E',
templateUrl: '/directive1.html',
link: function (scope) {
scope.function1_Of_Directive2 = function() {
console.log("F1_D2")
}
scope.function2_Of_Directive2 = function() {
console.log("F2_D2")
}
//AT BOTTOM OF EVERY DIRECTIVE
scope.$parent.directiveLoaded += 1;
}
}
});
})();
link
函数与templateUrl
结合使用。