jqueryangularjsdomangular-ui-routerready

AngularJS: $viewContentLoaded fired before partial view appears


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


Solution

  • 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