3

I'm using a template called Gentelella and I'm trying to implement AngularJS into it. However, I'm having an issue with a certain Javascript file. In the end of this file, a $(document).ready function is called which initialises Javascript code that makes some changes in the HTML code. The issue is that the $(document).ready function is called too early, before the HTML is fully loaded.

This issue occurs probably because I'm using ngRoute and this injects the template html file into the ng-view of the index.html. When this happens, the DOM probably already announces a document ready before AngularJS has injected the template (=HTML).

So basically, I just need to find a way to call some code in a Javascript file once AngularJS has injected the template.

I attached some code to gain some insight into the issue:

Snippet of the custom.min.js

$(document).ready(function () {
  init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
});

Snippet of the main.js:

.config(function($routeProvider, $httpProvider) {

  $routeProvider.when('/', {
    templateUrl : 'dash.html',
    controller : 'dash',
    controllerAs: 'controller'
  }).when('/login', {
    templateUrl : 'login.html',
    controller : 'navigation',
    controllerAs: 'controller'
  }).when('/plain_page', {
    templateUrl : 'plain_page.html',
    controller : 'dash',
    controllerAs: 'controller'
  }).otherwise('/');

  $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';

})

Thanks in advance!

Jérémy
  • 405
  • 1
  • 4
  • 22
  • Can you check this http://stackoverflow.com/questions/18646756/how-to-run-function-in-angular-controller-on-document-ready – Agam Banga May 04 '17 at 12:03
  • 1
    Don't try to hook into document.ready at all; call it from within the `link` function of the directive that needs to use the code. (In general, modifying the DOM directly causes problems when using Angular; it's best to work within Angular's own lifecycle.) – Daniel Beck May 04 '17 at 12:03
  • @DanielBeck Thank you for your answer. I know that it's not the best way to change DOM and use jQuery functions in an AngularJS project, but that's just how the situation is right now. Would you be able to give a concrete example of the link function? Do I just insert the init code into the link? Thanks! – Jérémy May 04 '17 at 12:19
  • 1
    This describes it better than I can: https://docs.angularjs.org/guide/directive (or as @mistalis shows below you can include it in a controller or ng-init.) (I should point out that I'm still using v1.x terminology, I got off the Angular bus when they rewrote everything so am less familiar with current versions, but I think the v4 equivalent is directive `ngAfterViewInit`) – Daniel Beck May 04 '17 at 12:37
  • @DanielBeck Sorry, but I still didn't figure out how `link` would be able to solve this issue. Should I create a new directive and put the code inside that one? – Jérémy May 04 '17 at 13:26
  • A small detail that I forgot to mention (didn't know if it was important) is that I'm running this whole project in a Spring Boot project. But I don't think it affects anything. – Jérémy May 04 '17 at 14:13

5 Answers5

5

Many jQuery plugins depend on a workflow of 1. draw the DOM. 2. run an init() function to set up code against those DOM elements.

That workflow fares poorly in Angular, because the DOM isn't static: Angular sets up and destroys DOM nodes on its own lifecycle, which can overwrite event bindings or DOM changes made outside Angular. Document ready isn't particularly useful when you're using Angular, because all it indicates is that Angular itself is ready to start running.

To use Angular effectively, you have to get into the habit of initing code only when it's actually needed. So instead of a big bucket of init_foo(); init_bar(); on document.ready, you should have a Foo directive with its own init code, and a Bar directive with its own specific init code, and so on. Each of those directives should only modify the DOM created by that specific directive. This is the only safe way to ensure that the DOM elements you need to modify actually exist, and that you're not creating conflicts or unexpected interdependencies between directives.

To take one example: I'm guessing your init_flot_chart() crawls down through the DOM looking for a particular element inside of which it'll draw a flot chart. Instead of that top-down approach, create a directive:

angular.module('yourApp')
  .directive('flotWrapper', function () {
    return {
      template: "<div></div>",
      scope: {
        data: '@'
      },
      link: function(scope, elem, attrs) {
        var options = {}; // or could pass this in as an attribute if desired
        $.plot(elem, scope.data, options); // <-- this calls flot on the directive's element; no DOM crawling necessary
      }
    };
});

which you use like this:

<flot-wrapper data="{{theChartData}}"></flot-wrapper>

...where theChartData is an object containing whatever data is to be drawn in the chart. (You can add other attributes to pass in whatever other parameters you like, such as the flot options, a title, etc.)

When Angular draws that flotWrapper directive, it first creates the DOM element(s) in the directive template, and then runs whatever is in its link function against the template's root element. (The flot library itself can be included via a plain old <script> tag so its plot function is available when the directive needs it.)

(Note that this wouldn't update automatically if the contents of theChartData change; a more elaborate example which also watches for changes and responds appropriately can be seen here.)

Community
  • 1
  • 1
Daniel Beck
  • 20,653
  • 5
  • 38
  • 53
1

As you are using ngRoute, your controller will run when the page is loaded. You can call an init() method when the controller starts to do whatever you want.

function MyCtrl($scope) {
    function init() {
        console.log('controller started!');
    }
    init();
}

As a side note, it is a best practice recommanded in John Papa Angular's guide.


Another possibily is to use the ng-init directive, for example:

<div ng-controller="MyCtrl" ng-init="myFunction()">...</div>
Mistalis
  • 17,793
  • 13
  • 73
  • 97
  • thank you for your answer. This solution doesn't seem to work. I called an alert inside the init() method and when the alert was called, the HTML wasn't fully loaded yet. – Jérémy May 04 '17 at 12:26
0

There is a lifecycle hooks named $postLink() in recent version of angular.js, Called after this controller's element and its children have been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation.Check the guide

B.Ma
  • 839
  • 7
  • 9
-1
$document.ready(function() {
  $scope.$on('$viewContentLoaded', function() {
    $timeout(function() {
      init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
    })
  })
})

update Notice: sry guys, this is not a rigorous solution, plz see another answer I wrote

B.Ma
  • 839
  • 7
  • 9
-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:

app.controller('Controller', function ($scope, $timeout) {
    $scope.$on('$viewContentLoaded', function(event) {
      $timeout(function() {
        init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
      },0);
    });
});
Rana Ghosh
  • 4,514
  • 5
  • 23
  • 40
  • Thank you for your answer. This one doesn't work. The function is already called before the actual HTML is injected. – Jérémy May 04 '17 at 13:19