Why does the order of dependency injection matter?


#1

Hello,
Can someone educate me on why the order of injection matters? I’ve been reading up on how dependency injection works in Angular and the official documents state that DI’s are not position based but name based.

My problem is that in my code, where I put the DI seems to matter.

Consider for example this code in app.js

.constant('zm', {
    httpTimeout:15000,
    largeHttpTimeout:60000,
    logFile:'zmNinjaLog.txt',
  // followed by other constants
})

Now in my controller that needs these constants:

This works - note the ‘zm’ position

angular.module(‘zmApp.controllers’).controller(‘zmApp.MontageCtrl’, [’$scope’, ‘$rootScope’, ‘zm’, ‘ZMDataModel’, ‘message’, ‘$ionicSideMenuDelegate’, ‘$timeout’, ‘$interval’, ‘$ionicModal’, ‘$ionicLoading’, ‘$http’, ‘$state’, ‘$ionicPopup’, ‘$stateParams’, ‘$ionicHistory’, ‘$ionicScrollDelegate’, ‘$ionicPlatform’,function ($scope, $rootScope, zm, ZMDataModel, message, $ionicSideMenuDelegate, $timeout, $interval, $ionicModal, $ionicLoading, $http, $state, $ionicPopup, $stateParams, $ionicHistory, $ionicScrollDelegate) {

But doing this results in all the constants showing up as undefined

angular.module(‘zmApp.controllers’).controller(‘zmApp.MontageCtrl’, [’$scope’, ‘$rootScope’, ‘ZMDataModel’, ‘message’, ‘$ionicSideMenuDelegate’, ‘$timeout’, ‘$interval’, ‘$ionicModal’, ‘$ionicLoading’, ‘$http’, ‘$state’, ‘$ionicPopup’, ‘$stateParams’, ‘$ionicHistory’, ‘$ionicScrollDelegate’, ‘$ionicPlatform’,‘zm’,function ($scope, $rootScope, ZMDataModel, message, $ionicSideMenuDelegate, $timeout, $interval, $ionicModal, $ionicLoading, $http, $state, $ionicPopup, $stateParams, $ionicHistory, $ionicScrollDelegate,zm ) {

Can someone help me understand:
a) Why this happens
b) How do I debug such situations to know if a DI is injected properly or not?

As my code is growing, I am trying to make sure I get all these things sorted out.


#2

To answer your question, you have ‘$ionicPlatform’ before the zm, but you don’t declare it again in the function arguments. So that I think should fix that issue for you.

For debugging, sometimes it could be that if something you’re injecting earlier is faulting that it could stop that process and still attempt to load the controller, not making it to the end of the injections. I haven’t found an easy way to debut that yet though.

You do have it correct though that they need to be in the same relative position. By doing it the way you have shown, you can actually minify your code usually pretty safely giving you the upper hand there.

You can declare your controller like this too (only mentioning this because you may see it done like this in different tutorials or places)

.controller('zmApp.MontageCtrl', function ($scope, $rootScope, ZMDataModel, message, $ionicSideMenuDelegate, $timeout, $interval, $ionicModal, $ionicLoading, $http, $state, $ionicPopup, $stateParams, $ionicHistory, $ionicScrollDelegate,zm ){
	
});

Though it looks like you’re thoroughly using angular and Ionic so you probably know that haha.


#3

Aha, that was indeed the problem @NorthMcCormick. Good eye! Thank you!
Also good tip on a previous DI error causing problems with next ones. Usually, I think that should produce some sort of error in the console. Hopefully I never reach a situation where there is no displayed error, but its faulting.

Yes, I am using minify for my release builds and when I first started with ionic and angular, I read that calling out the names in the array avoids minify problems, so I went for that approach from day 1.

However, everyone (including tutorials) says ‘may cause problems’ if you use minify and don’t declare them in the array. I wonder why the ‘may’ - what is the variability?


#4

No problem!

I assume you would always get a console error, but there are times when no errors are thrown on things and it will make you want to cry hahahaha.

And nice, it’s good to do it from the start, it’s like 2 seconds of more work but makes it easier in the long run. And I don’t know why they would use the “may” either… Unless the minify is specifically for angular and it knows how to parse out the names then it will throw an error and obliterate your code.

You’re on the right track haha. Glad I could help


#5

@NorthMcCormick, I was wondering if you would lend your eye to another problem I fixed that I don’t know why this fix was necessary. This is the last of the ‘I solved it but I don’t know how’ as far as my app goes.

I am drawing graphs using tc-chart-js (wrapper around Chart.js).
The graphs draw perfectly. However, the graphs provide a “handleChartClick(e)” method (part of Chart.js). The problem I faced was each time I tapped on the graph, e would be undefined.

To solve it, I had to wrap the canvas command with an explicit binding to its controller, which is already bound.

The template:

The controller:

The binding that is part of my routes: https://github.com/pliablepixels/zmNinja/blob/master/www/js/app.js#L422

The explicit binding of template to controller (which solved the problem, but don’t know why)

It’s not like the controller is not called without the explicit binding - just that handleClick passes a null variable

Thanks!


#6

FYI, to avoid exactly these kinds of errors when matching injections with the function arguments, we have been using formatting like this when defining services and directives:

.factory('Thread', ['$log', 'Couchbase', 'Authentication', 'User', 'Group',     
            function($log,   Couchbase,   Authentication,   User,   Group) {
...
}

i.e. we line them up, in the same ordering so you can clearly see if you missed something.


#7

Adding a jshint hook script to our build process has also helped catch silly errors before they even get to testing.


#8

@eno, all good tips.

I actually have 2 elements built into my workflow:
a) I use a JSHint plugin with my favorite editor (Brackets) that does JSHinting on the fly as you type and hit enter. So all files either have a green checkmark at the bottom or a yellow warning sign with the JSHint error. Very very useful. (incidentally, jshint does not catch the error I had above)

b) When compiling, I use jshint as well as part of my hooks as a double check

On the alignment part, thats a good tip. When the line folds, its a little less useful, but still more useful than not doing it. I think I’ll follow that tip. Thanks


#9

I’ll have to take a closer look at it, but generally when you have a 3rd party plugin that isn’t native with angularjs it can cause problems.

The reason being is that angularjs has everything in a scope. If something is outside of that scope (like when the plugin generates the graph DOM) angularjs doesn’t know it exists. There are pros and cons. Pros are that everything in your scope is all synced together, cons is this kind of behavior with non-angular scripts.

A couple of things that will help you possibly make that easier are $compile and $apply. Compile allows you to reference a piece of DOM and compile it into Angular’s scope, and then $apply allows you to do the same thing but with logic. It basically takes things that aren’t running in sync with angulars digest loop and put it in a place where it does. It’s the same reason that you should use $timeout and $log instead of timeout and console.log.

I hope that gets you in the right direction with that, charts are hard! If that one really is giving you grief I’d look into either a wrapper or another plugin with a search term like “AngularJS Charting Graphing” and see what you can come up with.


#10

We specifically do not fold the first two lines of a directive, service or controller definition, so no problem.


#11

Check out ng-annotate also guys. Minify your apps without having to provide constructor functions inline as anonymous functions when registering controllers.

app.controller('DemoCtrl', function ($scope, $rootScope, $http, $timeout, $window) { // code });

vs

app.controller('DemoCtrl', ['$scope', '$rootScope', '$http', '$timeout', '$window', function ($scope, $rootScope, $http, $timeout, $window) { // code }]);