AngularJS: 在 AngularJS 呈现模板后如何运行其他代码?

我在 DOM 中有一个角度模板。当我的控制器从服务获取新数据时,它会更新 $scope 中的模型,并重新呈现模板。目前一切正常。

问题是,在模板重新呈现之后,我还需要在 DOM 中(在本例中是一个 jQuery 插件)做一些额外的工作。

似乎应该有一个事件听,如后渲染,但我找不到任何这样的东西。也许一个指令是一种方式去,但它似乎开火太早了。

下面是一个 jsFiddle,它概述了我的问题: 小提琴角度问题

= = 更新 = =

基于有用的注释,我相应地切换到一个指令来处理 DOM 操作,并在该指令中实现了一个模型 $watch。但是,我仍然有同样的基本问题; 在模板被编译并插入到 DOM 之前,$watch 事件内部的代码会触发,因此,jquery 插件总是计算一个空表。

有趣的是,如果我删除了异步调用,那么整个事情就可以正常工作了,所以这是朝着正确方向迈出的一步。

下面是我更新的小提琴,以反映这些变化: http://jsfiddle.net/uNREn/12/

239584 次浏览

按照 Misko 的建议,如果需要异步操作,那么可以使用 $timeout ()代替 $timeout () ,后者不起作用

$timeout(function () { $scope.assignmentsLoaded(data); }, 1000);

使用 $evalAsync ()(它可以工作)

$scope.$evalAsync(function() { $scope.assignmentsLoaded(data); } );

小提琴。我还添加了一个“ delete row of data”链接,该链接将修改 $scope.asks,模拟对数据/模型的更改——以显示更改数据是有效的。

概念性概述页面的 运行时间部分说明,当需要在当前堆栈框架之外但在浏览器呈现之前进行某些操作时,应该使用 evalAsync。(在这里猜测... ... “当前堆栈帧”可能包括角度 DOM 更新。)如果需要在浏览器呈现之后发生某些事情,请使用 $timeout。

但是,正如您已经发现的,我认为这里不需要异步操作。

这篇文章很老了,但是我把你的代码改成:

scope.$watch("assignments", function (value) {//I change here
var val = value || null;
if (val)
element.dataTable({"bDestroy": true});
});
}

Jsfiddle

希望能帮到你

您可以使用 角度控制实用程序的“ jQuery Passthrough”模块。我成功地将一个 jQuery touch carousel 插件绑定到一些图像上,这些图像是我从一个 Web 服务中检索出来的,然后用 ng-repeat 将它们呈现出来。

还可以创建在 link 函数中运行代码的指令。

参见 堆栈溢出的回复

最终我找到了解决方案,我使用 REST 服务来更新我的集合。为了转换可数据化的 jquery 代码如下:

$scope.$watchCollection( 'conferences', function( old, nuew ) {
if( old === nuew ) return;
$( '#dataTablex' ).dataTable().fnDestroy();
$timeout(function () {
$( '#dataTablex' ).dataTable();
});
});

首先,正确处理呈现的地方是指令。我的建议是像下面这样通过指令来包装 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);
});
});
},
};
});

希望这能帮助你避免一些挣扎。

我不得不经常这么做。我有一个指令,需要做一些 jquery 的东西后,模型的东西完全加载到 DOM。所以我把我的逻辑放在这个指令的链接: 函数中,然后把代码封装在 setTimeout (function (){ ... } ,1)中; setTimout 会在加载 DOM 之后触发,1毫秒是加载 DOM 之后代码执行之前的最短时间。这似乎对我工作,但我确实希望角度引发了一个事件一旦模板完成加载,使该模板使用的指令可以做 jquery 的东西和访问 DOM 元素。希望这能帮上忙。

在某些场景中,您更新一个服务并重定向到一个新视图(页面) ,然后在更新服务之前加载您的指令,那么您可以使用 $rootScope。如果您的 $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);
}
};
});

我发现最简单(廉价且令人愉快)的解决方案是简单地在最后呈现的元素末尾添加一个空的 span,使用 ng-show = “ somFunctionThatAlwaysReturn nsZeroOrNothing ()”。这个函数将在检查 span 元素是否应该显示时运行。执行此函数中的任何其他代码。

我知道这不是最优雅的做事方式,但是,这对我很有效..。

我也有过类似的情况,虽然稍微有些颠倒,当动画开始时,我需要移除一个加载指示器,在移动设备上,角度的初始化要比要显示的动画快得多,而且使用 n- 斗篷是不够的,因为加载指示器在任何真实数据显示之前就被移除了。在本例中,我只是将 return 0函数添加到第一个呈现的元素中,并在该函数中翻转了隐藏加载指示符的 var。(当然,我在这个函数触发的加载指示器中添加了一个 ng- 隐藏。

我带来了一个非常简单的解决方案。我不确定这是否是正确的做法,但它在实际意义上是有效的。让我们直接观察我们想要呈现的内容。例如,在包含一些 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长度(或任何其他度量)与您的模型进行比较。我很感谢你对此的任何想法/评论。

$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- 重复运行完成后调用它,我在 ng- 重复和 ng-If = “ $last”中添加了一个空的 span:

<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>

在我的解决方案中,我有几个需要首先加载的自定义指令,因为它们包含其兄弟指令调用的函数的定义。例如:

<div id="container">
<custom-directive1></custom-directive1>
<custom-directive2></custom-directive2>
<custom-directive3></custom-directive3>
</div>

不幸的是,这里的所有解决方案对我都不起作用,因为它们只有在呈现指令之后才起作用,而不是在呈现指令代码之后才起作用。

因此,当我实现上述任何一个解决方案时,为了执行某个 load 函数,即使呈现了指令,作用域也不知道这些指令中的函数是什么。

所以我在我的控制器中创建了一个可观测的任何地方:

//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;

在每个指令的底部。因为在控制器中我定义了可观测函数,每次我更新变量 directiveLoaded,它就执行可观测函数。是的,我知道这是一个黑客行为,但是为了保证所有的指令在执行最后的函数之前完成渲染以及它们后面的代码,这是一个很小的代价。

为了完成演示,这里有两个指令,它们定义了以后需要调用的函数。

指令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;
}
}
});
})();