AngularJS: $viewContentLoaded fired before partial view appears

本小妞迷上赌 提交于 2019-11-26 16:06:48

问题


For a partial view I want to do some JavaScript stuff that I usually would do with $(document).ready(function() {...}), e.g. bind venet listeners to elements. I know that this doesn't work for AngularJS and partial views loaded into the "root" view.

Thus I added a listener to the controller that listens to the $viewContentLoaded event. The listener's function is invoked, so the event is fired but it seems to me as if it is before the partial view is rendered. Neither do I see the elements when I set a breakpoint in the listener's function and debug it with firebug, nor does the jquery selection within the function find the partial view's elements.

This is what the controller looks like:

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

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

[...]

I guess that I am doing something wrong as there had to be more posts on stackoverflow if this is a common bug.

As I am using ui-router and ui-view, I will give you an excerpt of the routing file:

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');
    }])

 [...]

Any help is appreciated. Thanks and kind regards

UPDATE 1: I stripped the error down to the following usecase: The loginView.html looks like the following:

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

[...]

As soon as I remove the ng-if from the div tag, it works as expected. The event is triggered after the DOM is rendered, thus jQuery finds the element. If the ng-if is attached to the div tag, the behaviour is as first described.

UPDATE 2: As promised I added a working demo that shows the different behaviour when adding a ng-if directive. Can anyone point me the right direction? Don't stick to the login form as such, as there are many more use cases where I want to remove certain parts of a view based on some expression and do some JavaScript stuff after the partial view is ready.

You can find the working demo here: Demo


回答1:


This is related to angular digest cycle, it's about how angular works underneath the hood, data binding etc. There are great tutorials explaining this.

To solve your problem, use $timeout, it will make the code execute on the next cycle, whem the ng-if was already parsed:

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

Fixed demo here: http://codepen.io/anon/pen/JoYPdv

But I strongly advise you to use directives do any DOM manipulation, the controller isn't for that. Here is a example of how do this: Easy dom manipulation in AngularJS - click a button, then set focus to an input element




回答2:


I have solved this issue with the help of Directives. Add one direcitve to your element (like <div my-dir></div>) and do manipulations to the element in respective directive as follows,

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

I have also tried state provider events like $stateChangeSuccess, $viewContentLoaded but couldn't solve the issue. Because after those events got fired, it's taking time to render on the DOM.

So, we can follow this approach which gives perfect results and proper way to implement in Angular JS :)




回答3:


This is an answer to Hades a bit late but might help someone else. I setup a service which I will later be able to call from a controller or directive.

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

    };
});

In the controller or directive include viewContentLoaded and call the get function with the promise. Don't forget to change the app to whatever you app name is and include the service file to be loaded.

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

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


来源:https://stackoverflow.com/questions/27129829/angularjs-viewcontentloaded-fired-before-partial-view-appears

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!