如何在准备好文档的AngularJS控制器中运行函数?


254

我在我的角度控制器中有一个函数,我希望可以在准备好文档的文件上运行此函数,但是我注意到在创建dom时,它会运行角度函数。

 function myController($scope)
 {
     $scope.init = function()
     {
        // I'd like to run this on document ready
     }

     $scope.init(); // doesn't work, loads my init before the page has completely loaded
 }

有人知道我该怎么做吗?

Answers:


449

我们可以使用该angular.element(document).ready()方法为文档准备就绪时附加回调。我们可以像这样简单地将回调添加到控制器中:

angular.module('MyApp', [])

.controller('MyCtrl', [function() {
    angular.element(document).ready(function () {
        document.getElementById('msg').innerHTML = 'Hello';
    });
}]);

http://jsfiddle.net/jgentes/stwyvq38/1/


64
或者您可以使用$ document.ready(function(){...}),Angular Docs:docs.angularjs.org/api/ng/service/$document
StuR 2014年

29
您需要注入$document使用它。angular.element开箱即用。
nshew

17
您需要注入$ document才能使用它,但这是引用document元素的推荐方法。您不必这样做,但是它使测试方法更容易。
杰森·考克斯

10
document是一个全局变量,$document可以通过角度进行注入,从而使测试更加容易。通常,很难对访问全局JS变量(如文档或窗口)的应用程序进行单元测试。我也认为这module.run是放置此代码而不是控制器的好地方。
lanoxx 2014年

7
这对我不起作用,getElementById调用返回null。
ThePartyTurtle '16

29

看到这篇文章如何在页面加载时执行角度控制器功能?
快速查找:

// register controller in html
<div data-ng-controller="myCtrl" data-ng-init="init()"></div>

// in controller
$scope.init = function () {
    // check if there is query in url
    // and fire search in case its value is not empty
};

这样,您不必等到文档准备就绪。


1
如果需要在页面加载后调用该怎么办?
Rishi

22

DOMContentLoaded如果那时document.readyState设置为“ complete”,则Angular将在事件发生时或在评估angular.js脚本时自动初始化。此时,Angular寻找ng-app指令,该指令指定您的应用程序根目录。

https://docs.angularjs.org/guide/bootstrap

这意味着控制器代码将在DOM准备就绪后运行。

因此,这仅仅是$scope.init()


9
我试图这样做,但奇怪的是,它没有起作用,页面中的某些内容没有加载
foreyez 2013年

以及如何在编写自动网页测试时知道在执行诸如单击按钮之类的操作之前,角度代码何时准备就绪?
akostadinov

DOMContentLoaded当文档已完全加载和解析时也将触发,而无需等待样式表,图像和子帧完成加载。有关更多信息,请参见DOMContentLoaded事件和load事件之间的区别是什么?
georgeawg

22

Angular有几个时间点开始执行功能。如果您寻找类似jQuery的东西

$(document).ready();

您可能会发现这种角度模拟非常有用:

$scope.$watch('$viewContentLoaded', function(){
    //do something
});

当您要操作DOM元素时,这一功能非常有用。仅在加载所有te元素后才开始执行。

UPD:当您要更改CSS属性时,以上所述适用。但是,有时在您想要测量元素属性(例如宽度,高度等)时不起作用。在这种情况下,您可能需要尝试以下操作:

$scope.$watch('$viewContentLoaded', 
    function() { 
        $timeout(function() {
            //do something
        },0);    
});

这比建议的答案值得更多。我没有使用$ timeout就完成了这项工作。
Richie86 '17

它仅对setTimeout(0)适用于我-没有它,例如ng-repeat下的内容目前尚未加载
Ignacio Vazquez

2

我有类似的情况,在加载视图之后,以及加载,初始化视图中的特定第3方组件并将其自身的引用放在$ scope上之后,我需要执行控制器功能。最终对我有用的是在此scope属性上设置一个watch,并且仅在函数初始化后才触发它。

// $scope.myGrid property will be created by the grid itself
// The grid will have a loadedRows property once initialized

$scope.$watch('myGrid', function(newValue, oldValue) {
    if (newValue && newValue.loadedRows && !oldValue) {
        initializeAllTheGridThings();
    }
});

多次使用未定义的值调用监视程序。然后,当创建网格并具有预期的属性时,可以安全地调用初始化函数。第一次使用未定义的newValue调用监视程序时,oldValue仍将是未定义的。


1

这是我在使用coffeescript的外部控制器内部的尝试。效果不错。请注意,settings.screen.xs | sm | md | lg是我在应用程序随附的非丑化文件中定义的静态值。这些值是针对同名媒体查询大小的每个Bootstrap 3官方断点:

xs = settings.screen.xs // 480
sm = settings.screen.sm // 768
md = settings.screen.md // 992
lg = settings.screen.lg // 1200

doMediaQuery = () ->

    w = angular.element($window).width()

    $scope.xs = w < sm
    $scope.sm = w >= sm and w < md
    $scope.md = w >= md and w < lg
    $scope.lg = w >= lg
    $scope.media =  if $scope.xs 
                        "xs" 
                    else if $scope.sm
                        "sm"
                    else if $scope.md 
                        "md"
                    else 
                        "lg"

$document.ready () -> doMediaQuery()
angular.element($window).bind 'resize', () -> doMediaQuery()

1

答案

$scope.$watch('$viewContentLoaded', 
    function() { 
        $timeout(function() {
            //do something
        },0);    
});

是我测试过的大多数方案中唯一可行的方案。在具有4个组件的示例页面中,所有这些组件均通过模板构建HTML,事件的顺序为

$document ready
$onInit
$postLink
(and these 3 were repeated 3 more times in the same order for the other 3 components)
$viewContentLoaded (repeated 3 more times)
$timeout execution (repeated 3 more times)

因此,在大多数情况下,$ document.ready()毫无用处,因为以角度构造的DOM可能还差得远。

但是更有趣的是,即使在触发$ viewContentLoaded之后,仍然找不到感兴趣的元素。

仅在执行$ timeout之后才找到它。请注意,即使$ timeout的值为0,执行前也经过了近200毫秒,这表明该线程被搁置了一段时间,大概是在DOM的主线程上添加了角度模板的情况下。从第一个$ document.ready()到最后一个$ timeout执行的总时间接近500毫秒。

在一种特殊情况下,先设置组件的值,然后在$ timeout中稍后更改text()值,则必须增加$ timeout值,直到它起作用为止(即使可以在$ timeout期间找到该元素) )。第三方组件中的异步内容导致值优先于文本,直到经过足够的时间为止。另一个可能是$ scope。$ evalAsync,但是没有尝试过。

我仍在寻找一个事件,该事件告诉我DOM已经完全解决,可以对其进行操作,以便所有情况都可以进行。到目前为止,必须有一个任意的超时值,这意味着充其量只能在缓慢的浏览器上无法正常使用。我还没有尝试过像liveQuery和发布/订阅这样的JQuery选项,这些选项可能有用,但是肯定不是纯角度的。


1

如果您收到类似的信息getElementById call returns null,则可能是因为该函数正在运行,但是ID没有时间将其加载到DOM中。

尝试延迟使用威尔的答案(朝上)。例:

angular.module('MyApp', [])

.controller('MyCtrl', [function() {
    $scope.sleep = (time) => {
        return new Promise((resolve) => setTimeout(resolve, time));
    };
    angular.element(document).ready(function () {
        $scope.sleep(500).then(() => {        
            //code to run here after the delay
        });
    });
}]);

0

为什么不尝试使用什么角度文档提到的https://docs.angularjs.org/api/ng/function/angular.element

angular.element(回调)

我在$ onInit(){...}函数中使用了此函数。

 var self = this;

 angular.element(function () {
        var target = document.getElementsByClassName('unitSortingModule');
        target[0].addEventListener("touchstart", self.touchHandler, false);
        ...
    });

这对我有用。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.