Understanding dependency injection in AngularJS controllers - javascript

Just learning dependency injection, and I think I'm starting to understand it.
Please tell me if I'm on the right track...
E.g.: Are these two equivalent?
/* injection method */
function <controller_name>($scope) {}
<controller_name>.$inject = ['$scope'];
/* other method */
var app = angular.module('myApp');
app.controller(<controller_name>, function($scope) {});

First a little clarification:
For dependency injection, it doesn't matter whether you declare a controller using a global function or as the argument of module.controller(...) method. Dependency injector is only concerned about the function itself. So what you're actually asking about is the equivalence of those two:
// First
function MyController($scope) {}
MyController.$inject = [ '$scope '];
// Second
function($scope) {}
And because whether the controller function is anonymous or not also doesn't matter for the injector, the above two can just as well be:
// First
function MyController($scope) {}
MyController.$inject = [ '$scope '];
// Second
function MyController($scope) {}
Now it's clear that the only difference between your two controllers is the presence of the $inject property in one of them.
And here's the actual answer to your question:
These two controllers are almost the same. Both will receive the $scope as the argument and will function the same. However, if you decide to minify your code later, only the version with $inject array set on it will work properly. This is because if you don't specify the $inject array nor use the inline annotation approach (http://docs.angularjs.org/guide/di#inlineannotation), the only way for the injector to find out which dependencies you were interested in is to check the names of your function arguments (treating them as service IDs). But minification would name those arguments randomly thus removing the chance to recognize dependencies this way.
So if you're going to minify your code, you have to specify the dependencies explicitly using $inject array or inline annotation, otherwise, any version will work just as good.

If you're going to use the module.controller method, the equivalent to your first example would be:
var app = angular.module('myApp');
app.controller(<controller_name>, ['$scope', function($scope) {}]);
Notice that this way we're passing the $inject string along with the function, so that if it later gets minimized it will still work.

Related

Scope variable not visible within ng-click handler

I'm pretty new to Angular and am trying to figure out what's wrong here. There is a controller defined like this:
(function(){
function myController($scope, CommsFactory) {
$scope.doSomething = function() {
var x = $scope; // <- Doesn't work because $scope is not defined
}
}
angular
.module('aModule')
.controller('myController', myController);
})();
The doSomething() method is then called by a button click like:
<input type="button" ng-click="doSomething()" class="btn--link" value="do it"/>
This seems straightforward to me but the problem is that, when I break within the method, $scope is not defined. This is different from most of the examples I've seen, and I can't figure out why. Shouldn't it be visible here? Obviously a lot of code is missing - I've tried to show only the relevant bits - could I be missing something somewhere else?
You're declaring a module then you need to add [].
Something like this:
angular.module('aModule', [])
.controller('myController', myController);
Usage
angular.module(name, [requires], [configFn]);
Arguments
name.- The name of the module to create or retrieve.
requires (optional).- If specified then new module is being created. If unspecified then the module is being retrieved for further
configuration.
configFn (optional).- Optional configuration function for the module. Same as Module#config().
Please, I would to recommend to read this guide about Angular Module:
angular.module
(function() {
function myController($scope) {
$scope.doSomething = function() {
var x = $scope;
console.log(x);
}
}
angular
.module('aModule', [])
.controller('myController', myController);
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div data-ng-app="aModule">
<div data-ng-controller="myController">
<input type="button" ng-click="doSomething()" class="btn--link" value="do it" />
</div>
</div>
Your code is generally working fine as demonstrated in this fiddle.
Your main problem seems to be in the usage of $scope. $scope is an object containing all variables and methods which should be available in the corresponding template. For this reason, you would always reference a member of $scope, instead of the whole object.
Furthermore, John Papas AngularJS style guide recommends the usage of controllerAs in favor of $scope for multiple reasons as stated in Y030
By convention, you should also give your controllers uppercase names and use explicit Dependency Injection
A typical use case would rather look like:
(function(){
angular
.module('aModule', [])
.controller('myController', MyController);
MyController.$inject = ['$scope', 'CommsFactory'];
function MyController($scope, CommsFactory) {
var vm = this;
vm.doSomething = doSomething;
function doSomething() {
var $scope.x = "Did it!";
}
}
})();
SOLVED: It turns out that what I was experiencing had something to do with the way in which the Chrome debugger works. It appears to do some kind of lazy loading of variables defined outside of the function in which you break (or at least this as far as I've characterized it). What this means, at least in my case, is that if I break inside of the method, and $scope isn't actually used within that method (which, unfortunately, I was doing a lot because I was trying to verify that $scope was visible), then the debugger will report that $scope is unavailable.

Is it possible to use a constructor function as a type of an argument of another function in TypeScript?

A few days ago I've found a TypeScript language. According to video reviews it looks pretty promising for me as it brings to JavaScript an appropriate code completion, implicit code documentation and, probably, makes it more type safety.
I'm trying to rewrite a simple AngularJS application in TypeScript. I'd like to specify types of parameters, which are injected to controllers. Let's assume the following JS code:
// service.js
angular.module('app').service('MyService', MyService);
function MyService() {
this.test = function test() { console.log('test'); };
}
// controller.js
angular.module('app').controller('MyController', MyController);
function MyController($scope, MyService) {
$scope.test = function test() { MyService.test(); };
}
I'm trying to achieve something like this:
// controller.ts
angular.module('app').controller('MyController', MyController);
function MyController($scope, MyService:MyService) {
// ...
}
So it'd be easier to write code of the controller with code completion for MyService parameter. However, this code makes a compiler to emit error TS2304: Cannot find name 'MyService'.
I've tried a couple to things and found at least 2 possible solutions. However, non of them seems to be ideal.
1) Creation of an additional interface, which declares all methods of the service. But this approach requires a lot of additional typing (actually, we define the same set of methods with full signature twice and also have to update several things in case of changes). This way is totally okay when the interface is implemented multiple times, but looks redundant for a single AngularJS service.
2) Constructor function can be replaced with a class. This way it is possible to use a name of the class as a type of the parameter. However, this also leads to a couple of problems:
typescript compiler transforms class definitions into closures which are assigned to variables declared with var keyword (at least, dealing with ES5). This requires the AngularJS service declaration to be placed after the class definition at the very bottom of a file. It increases a possibility to forget to do it.
angular.module('app').service('MyService', MyService);
class MyService {
test() { console.log('test'); }
}
// becomes
angular.module('app').service('MyService', MyService);
var MyService = (function () {
function MyService() {
}
MyService.prototype.test = function () { console.log('test'); };
return MyService;
}());
going this way, we have to inject dependencies to a constructor function of the service. As a result, it is necessary to specify an excess this. string each time a dependency is used.
So, are there any other ways to pass a constructor function as a type of a parameter of another function? Thanks.
P.S.: I don't know whether it's necessary or not, but I'm using Sublime Text 3 with an official TypeScipt plugin and gulp-typescript for compilation.
Yes, you can do this. See working CodePen
class MyController
{
static $inject = ['MyService','$scope'];
constructor(private myService:MyService, private $scope:any){
this.$scope.test = this.myService.test;
}
}
class MyService{
test(){
console.log('test');
}
}

AngularJS module.constant() : how to define a constant inside a module only?

On a page, I have several Angular modules.
For each module I define a constant which contains the version of the module.
var module1 = angular.module('module1').constant('version', '1.2.3');
var module2 = angular.module('module2').constant('version', '2.0.0');
...
I though a constant was defined inside a module. But when I use the constant inside module1, the value I get is '2.0.0'...
Is there a way to define a constant (or anything else) which is proper to a module ?
Edit: for alternative solutions, could you please explain how to use it, for example in a controller declaration ?
module2.controller('myCtrl', function( $scope, $http, $q, ..., version ){
// Here I can use the constant 'version'
}
A very good question. I could think of a quick fix for this:
angular.module('module1').constant('module1.version', '1.2.3');
angular.module('module2').constant('module2.version', '2.0.0');
I don't know how much it suits your needs. But I hope it helps.
The main problem is the naming in Angular. You cannot have the same name for services/constants/values etc. They will be overwritten. I solved this problem with "namespaces" like I showed you above.
Example of injecting namespace like names: http://codepaste.net/5kzfx3
Edit:
For your example, you could use it like so:
module2.controller('myCtrl', ['$scope', '$http', '$q', ... , 'module2.version'], function( $scope, $http, $q, ..., version ){
// Here I can use the constant 'version'
}
That happens because module2 is overriding module1 constant.
You can use moduleName.version as a name, but it's not possible to use the same name.

AngularJS function declaration, positioning and nesting

I'm new to AngularJS and I read you can declare function in 2 different ways (perhaps more...):
First:
var myApp = angular.module('myApp', []);
myApp.controller('mainCtrl', function($scope){
$scope.message = 'Yes';
})
myApp.controller('anotherCtrl', function($scope){
$scope.message = 'No';
})
Second:
var myApp = angular.module('myApp', []);
myApp
.controller('mainCtrl', mainCtrl)
.controller('anotherCtrl', anotherCtrl)
function mainCtrl($scope){
$scope.message = 'Yes';
}
function anotherCtrl($scope){
$scope.message = 'No';
}
Using the first method I was able to use different files (i.e.: controllers.js with all the Controllers, directives.js with all directives, etc...).
I tried using the second method and gives error if functions are declared in different files, which make sense because they are called in one file but . On the other hand it's more readable to me as there is less nesting and so forth.
What is the difference?
What is the difference?
Your First Example
In the first example, you're creating the functions via function expressions as part of your calls to myApp.controller.
It also happens that in your example, the functions are anonymous (they don't have names), but you could make them named if you wanted (unless you need to support IE8 or IE legacy modes that equate to IE8 or earlier):
myApp.controller('mainCtrl', function mainCtrl($scope){
// Gives it a name -------------------^
$scope.message = 'Yes';
});
(This article on my anemic little blog explains why there are issues with that on IE8 and earlier.)
Since the functions don't have anything referring to them except whatever .controller hooks up, you can't use them elsewhere unless you can get references to them back from myApp, or if you declared a variable and assigned it within the expression making the call:
var mainCtrl;
// ...
myApp.controller('mainCtrl', mainCtrl = function mainCtrl($scope){
$scope.message = 'Yes';
});
// ...you could use the `mainCtrl` variable here if you needed
// to reuse the function
Your Second Example
In the second example, you're creating the functions via function declarations, and then referring to those functions in your calls to myApp.controller. The functions have names (they're not anonymous). You could use those functions in more than one place, if it made sense to, without doing the variable thing shown above.
In your second example, you could declare the functions in separate files, but in order to use them in your call to myApp.controller, you need to get a reference to them somehow. There are a large number of ways you can do that, from RequireJS to SystemJS to ES2015 modules to Angular modules (I think) or any other AMD mechanism.

Are JavaScript anonymous function parameter names significant?

I'm going through this Angular tutorial and have noticed that variables seem to be freely added to functions as needed. I figured up to this point that there were just conventions but am I mistaken? Consider the last file:
app.controller('PostsCtrl', function ($scope, $location, Post) {
$scope.posts = Post.all;
$scope.post = {url: 'http://'};
$scope.submitPost = function () {
Post.create($scope.post).then(function (ref) {
$location.path('/posts/' + ref.name());
});
};
Here "$location" was added to function() between $scope and Post. Is $location the only option for the 2-nd parameter in an anonymous function here with 3 parameters or is angular somehow looking at the name of the 2nd parameter and deducing that it needs to inject $location there? Where in the documentation can I see all the conventions for 1, 2, 3, etc parameter versions of this function?
This code doesn't appear to work btw. Post is undefined.
With angular, the names are significant; in plain javascript, not really.
With that, however, if you wanted them to be insignificant in the example above, you could do:
app.controller('PostsCtrl', ['$scope','$location', 'Post',
function (foo, bar, foobar) {
....
}
]);
In which case you're mapping the first, second, and third parameters to $scope, $location, and Post respectively. This is actually a better way to do this, as when you minify with angular, it is going to change the names of those parameters, and it is going to break it.
In this case, you could order those parameters however you'd like. Angular uses Dependency Injection to supply dependencies to your controller. In this case, it will infer those dependencies based on the names so you can order them however you'd like.
Unfortunately, using this method with minification is impossible. In that case, you need to specify your dependencies. Your controller function must then take them in the order that they are defined:
app.controller('PostsCtrl',
['$scope','$location','Post', function($scope, $location, Post) {
}]);
Ahh I was confused by this at first as well. Angular has a nice feature called Dependency Injection. The names do matter because angular looks at the names of the parameters and decides what object to pass to the function. In this rare case in JavaScript, names matter but order does not.
Since you already have other answers with Angular specific instructions, I'll try to explain in simple code how Angular achieves dependency injection in JavaScript and how one might go about doing something similar in plain JS.
First it turns the function into a string, then reads the parameters, and extracts the property from the dependency container, and finally calls the function with the parameters that were extracted. Here's a very simple example of how one might do this:
// parses function and extracts params
function di(app, f) {
var args = f.toString()
.match(/function\s+\w+\s*?\((.*?)\)/)[1] // weak regex...
.split(/\s*,\s*/)
.map(function(x){return app[x.replace(/^\$/,'')]})
return function() {
return f.apply(this, args)
}
}
// Example
// A dependency container
var app = {
foo: 'this is foo',
baz: 'this is baz'
}
// this is like adding a function with the Angular ecosystem
// the order of arguments doesn't matter
var test = di(app, function test($foo, $baz) {
console.log($foo) //=> this is foo
console.log($baz) //=> this is baz
})
// No need to pass arguments, they were injected
test()
//$ this is foo
//$ this is baz
But handling DI by stringifying the function has drawbacks; old browsers don't support Function.prototype.toString very well, and minifiers will shorten the variable names making this technique useless. But Angular can get around this problem by injecting strings and parameters that match those strings; minifiers won't touch the strings.
Obviously AngularJS does much more than this, but hopefully it will clear your mind on "how the hell is this possible?!" -- Well, it's kind of a hack.
The answer here is really Angular specific. You want to read about Dependency Injection here:
https://docs.angularjs.org/guide/di
The Implicit Dependencies section should be good reading. In short, the name does matter for Angular but the order does not. This is NOT the case for raw JavaScript however.
From the Angular site:
There are three equivalent ways of annotating your code with service name information:
Implicitly from the function parameter names
Using the $inject property annotation
Using the inline array annotation

Categories

Resources