AngularJS:在部分视图出现之前触发 $viewContentLoaded

Posted

技术标签:

【中文标题】AngularJS:在部分视图出现之前触发 $viewContentLoaded【英文标题】:AngularJS: $viewContentLoaded fired before partial view appears 【发布时间】:2015-01-23 15:22:16 【问题描述】:

对于部分视图,我想做一些我通常会用$(document).ready(function() ...) 做的 javascript 东西,例如将 venet 侦听器绑定到元素。我知道这不适用于 AngularJS 和加载到“根”视图中的部分视图。

因此,我在控制器中添加了一个监听器来监听$viewContentLoaded 事件。调用了侦听器的函数,因此触发了该事件,但在我看来,它好像是在呈现局部视图之前。当我在侦听器的函数中设置断点并使用 firebug 对其进行调试时,我也看不到元素,函数内的 jquery 选择也没有找到局部视图的元素。

这是控制器的样子:

angular.module('docinvoiceClientAngularjsApp')
  .controller('LoginController', function ($scope, $rootScope) 

$scope.$on('$viewContentLoaded', function(event) 
  console.log("content loaded");
  console.log($("#loginForm"));   // breakpoint here 
);

[...]

我想我做错了,因为如果这是一个常见错误,*** 上必须有更多帖子。

由于我使用的是ui-routerui-view,我给大家摘录一下路由文件:

angular
  .module('docinvoiceClientAngularjsApp', [
    'ui.router',
    'ngAnimate',
    'ngCookies',
    'ngResource',
    'ngMessages',
    'ngRoute',
    'ngSanitize',
    'ngTouch'
  ])
 .config(function ($routeProvider, $stateProvider) 
    $stateProvider
    .state('login', 
        url: '/',
        templateUrl: 'components/login/loginView.html',
        controller: 'LoginController'
    )
    .run(['$state', function ($state) 
        $state.transitionTo('login');
    ])

 [...]

感谢任何帮助。谢谢和亲切的问候

更新 1: 我将错误简化为以下用例: loginView.html 如下所示:

<div id="loginContainer" style="width: 300px">
  <form id="loginForm" ng-submit="login(credentials)" ng-if="session.token == undefined">

[...]

一旦我从 div 标签中删除ng-if,它就会按预期工作。该事件在 DOM 渲染后触发,因此 jQuery 找到了元素。如果 ng-if 附加到 div 标记,则行为如前所述。

更新 2: 正如所承诺的,我添加了一个工作演示,显示了添加 ng-if 指令时的不同行为。谁能指出我正确的方向?不要拘泥于登录表单,因为还有更多用例,我想根据某些表达式删除视图的某些部分,并在部分视图准备好后执行一些 JavaScript 操作。

您可以在此处找到工作演示:Demo

【问题讨论】:

你可以为你的 DOM 东西创建一个指令。 我已经根据您的代码创建了一个working demo,它工作正常。下次请尝试自己提供演示。 非常感谢工作演示。我已经更新了描述,因为我发现 ng-if 会导致不同的行为。你知道为什么它的行为不同吗?表达式被评估为“真”,因为我可以看到登录表单,只是太晚了;) 我稍后会添加一个工作演示并发布链接以供进一步调查。 DOM 是通过 $digest 循环迭代构建的。一旦 ui-view 编译了模板,并且控制器已经执行,$viewContentLoaded 就会被触发。 dom 可能会在下一次 $digest 时更改,以响应控制器中发生的某些事情,或其他任何事情。没有很好的机制来复制 document.ready() 样式的 jquery。 【参考方案1】:

这与角度摘要循环有关,它是关于角度如何在引擎盖下工作、数据绑定等。有很好的教程解释了这一点。

要解决您的问题,请使用 $timeout,它将使代码在下一个周期执行,而 ng-if 已被解析:

app.controller('LoginController', function ($scope, $timeout) 
    $scope.$on('$viewContentLoaded', function(event) 
      $timeout(function() 
        $scope.formData.value = document.getElementById("loginForm").id;
      ,0);
    );
);

此处修复演示:http://codepen.io/anon/pen/JoYPdv

但我强烈建议你使用指令来进行任何 DOM 操作,控制器不适合那样做。 以下是如何执行此操作的示例: Easy dom manipulation in AngularJS - click a button, then set focus to an input element

【讨论】:

我会认为这是最有价值的答案,因为它提供了一个可能的解决方案和提示来查找有关摘要周期的更多信息,尽管我现在正在使用指令。 解释得很好!我能够通过使用上面的代码块来集成 App.js 和 AngularJS,并且还理解了超时复杂的细节。 这种方法的问题在于,在 ui-router 中 $viewContentLoaded 会为每个视图触发。如果您在单个页面中有很多视图,您的代码将执行多次。有没有办法让它在所有视图都加载后只执行一次? 我编写了一组指令,将它们的几何图形注册到服务中,以启用页面周围的几何导航。在我读到这篇文章之前,它的表现非常不可预测。几何计算的 50 毫秒延迟修复了我!我会寻找一种更具确定性的方法。【参考方案2】:

我在指令的帮助下解决了这个问题。向您的元素添加一个指令(如&lt;div my-dir&gt;&lt;/div&gt;)并在相应指令中对元素进行如下操作,

app.directive('myDir', function () 
    return 
        restrict: 'A',
        link: function (scope, element) 
           // Do manipulations here with the help of element parameter
        
    ;
);

我也尝试过$stateChangeSuccess$viewContentLoaded 等状态提供程序事件,但无法解决问题。因为在这些事件被触发后,在 DOM 上呈现需要一些时间。

所以,我们可以按照这种方法在 Angular JS 中实现完美的结果和正确的方法:)

【讨论】:

【参考方案3】:

这是对哈迪斯的回答有点晚,但可能对其他人有所帮助。我设置了一个服务,以后可以从控制器或指令中调用它。

'use strict';

app.factory('viewContentLoaded', function($q, $rootScope, $timeout) 
    var viewContentLoaded = $q.defer(),
        
        foo = function() 
            $timeout(function() 
                viewContentLoaded.resolve();
                // Get all entries
            , 100);//Timeout
        ,

        checkViewContenLoadedListner = $rootScope.$on('$viewContentLoaded', foo);//Rootscope

    return 
        getLoaded: function()       
            return viewContentLoaded.promise;
        ,
        removeViewContenListner: function() 
            //Remove event listern on $viewContentLoaded no $off so call it will unsubscribe it
            //$rootScope.$off('$viewContentLoaded', foo);//Rootscope
            //Don't forget to unsubscribe later
            checkViewContenLoadedListner();
        

    ;
);

在控制器或指令中包含 viewContentLoaded 并使用 promise 调用 get 函数。不要忘记将应用更改为您的应用名称并包含要加载的服务文件。

viewContentLoaded.getLoaded().then(function()
    //Remove Listner when done
    viewContentLoaded.removeViewContenListner();
    //Your code to run       

, function(reason) 
    //$log.error(reason);
);

【讨论】:

以上是关于AngularJS:在部分视图出现之前触发 $viewContentLoaded的主要内容,如果未能解决你的问题,请参考以下文章

使用 AngularJS,如何在视图和控制器同步特定更改后触发代码执行?

为啥 viewDidLoad 在 vi​​ewWillAppear 之前被调用? [复制]

如何在多个视图部分重用 angularjs 数据?

如何在 angularjs 应用程序的部分视图中包含 css 或 javascript 文件...? [关闭]

AngularJS 不适用于 MVC 局部视图

AngularJS:视图控制器没有被调用