ng-annotate with module constructor - angularjs

Is it possible to use ng-annotate with angular module construction?
For example,
angular.module("testApp", ["ui.router"])
.controller("FrontController", function($scope, $window, $log) {
...
});
gets annotated with the various dependencies. However, I've not been able to get the same to work with module instanciation, because removing the ["ui.router"] array changes the module constructor to a module getter.
Furthermore, there is no documentation on this particular topic that I've been able to find.

Related

AngularJS Annotation Questions

Hi I am new to Angular and JS Frameworks in general. I was wondering if someone could help me decipher some of the notation.
From the docs:
https://docs.angularjs.org/guide/di
Inline Array Annotation
someModule.controller('MyController', ['$scope', 'greeter', function($scope, greeter) {
// ...
}]);
$inject Property Annotation
var MyController = function($scope, greeter) {
// ...
}
MyController.$inject = ['$scope', 'greeter'];
someModule.controller('MyController', MyController);
For my education the $inject method is preferred as it is more exploded. I see three steps: 1) define the function, 2) inject dependencies, 3) attach to parent module.
OK so my questions:
Why are "$scope" and "greeter" listed as parameters to the function and then injected with the $inject? Wouldn't it be one or the other?
someModule.controller('MyController', MyController);
Why is MyController listed twice once as a literal? Is this a type and token thing? Could it just as easily have been:
someModule.controller('MyInstanceOfMyController', MyController);
Thanks for any illumination
For my education the $inject method is preferred as it is more exploded. I see three steps: 1) define the function, 2) inject
dependencies, 3) attach to parent module.
I prefer this notation as well, it reads better.
OK so my questions: Why are "$scope" and "greeter" listed as
parameters to the function and then injected with the $inject?
Wouldn't it be one or the other?
This makes sense, you are telling the injector what to inject, and the function declaration takes these in as usable parameters.
You could change "greeter2 to "newGreeter" in the injector, and you wouldn't need to update the function code in order to use the new module.
someModule.controller('MyController', MyController);
Why is MyController listed twice once as a literal? Is this a type and
token thing? Could it just as easily have been:
someModule.controller('MyInstanceOfMyController', MyController);
Kind of, here you are telling the module
1) What the controller is called
2) Which function defines the controller
These can have the same name, but they don't have to.

Different syntax in Angular for controller, services and others

What is the difference between
app.controller("MyCtrl", function($scope, $http){
//...
});
and
app.controller("MyCtrl", ["$scope", "$http", function($scope, $http){
//...
}]);
Even though both gives the same result and no error. In fact the first one makes code clean and less to write. Also it is same in services, directive. Can someone give me a small brief about it.
No functional difference. Using .controller('ctrl',['$scope', function($scope){...} is to allow a minified version to be read correctly.
A Note on Minification - AngularJS
Since Angular infers the controller's dependencies from the names of arguments to the controller's constructor function, if you were to minify the JavaScript code for MyCtrl controller, all of its function arguments would be minified as well, and the dependency injector would not be able to identify services correctly.
We can overcome this problem by annotating the function with the names of the dependencies, provided as strings, which will not get minified. There are two ways to provide these injection annotations:
Create a $inject property on the controller function which holds an array of strings. Each string in the array is the name of the service to inject for the corresponding parameter:
function MyCtrl($scope, $http) {...}
MyCtrl.$inject = ['$scope', '$http'];
app.controller('MyCtrl', MyCtrl);
Use an inline annotation where, instead of just providing the function, you provide an array. This array contains a list of the service names, followed by the function itself:
function MyCtrl($scope, $http) {...}
app.controller('MyCtrl', ['$scope', '$http', MyCtrl]);
Both of these methods work with any function that can be injected by Angular, so it's up to your project's style guide to decide which one you use.
When using the second method, it is common to provide the constructor function inline as an anonymous function when registering the controller:
app.controller('MyCtrl', ['$scope', '$http', function($scope, $http) {...}]);
Adding to wzVang's answer. You can follow your first syntax. i.e
.controller('ctrl', function($scope){
});
without any problem. It is readable. But you should minify your code in production. Then you can use ngAnnotate.

In Angular, is there a reason not to list module dependencies with the module declaration?

In all the tutorials and examples I've read regarding Angularjs, they all define modules with an empty list as the second parameter:
angular.module('myModule', []);
I understand that the presence of the second parameter is required to create a new module, but I don't understand why there are never any elements in the list. The document for angular.module says nothing about what the contents of the list would represent.
However, from defining my app module I understand the list represents modules upon which the new module depends - so why is it always empty for app sub-modules? For example, in my own project I've got a users module upon which my app depends:
/* app.js */
angular.module('myApp', [
'ngCookies',
'ngResource',
'ui.bootstrap',
'ui.router',
'myApp.system',
'myApp.users'
]);
angular.module('myApp.system', []);
angular.module('myApp.users', []);
When I finally got around to learning how to unit test using karma and jasmine, I spent hours trying to figure out this error message:
Error: [$injector:modulerr] Failed to instantiate module myApp.users due to:
Error: [$injector:unpr] Unknown provider: $stateProvider
http://errors.angularjs.org/1.2.13/$injector/unpr?p0=%24stateProvider
at /Users/matt/Development/myApp/public/lib/angular/angular.js:3556
at getService (/Users/matt/Development/myApp/public/lib/angular/angular.js:3683)
at invoke (/Users/matt/Development/myApp/public/lib/angular/angular.js:3710)
at /Users/matt/myApp/public/lib/angular/angular.js:3639
Eventually I found two things that would fix this problem - either I could load module dependencies in the test code, or I could add the dependencies to the empty list in the users module declaration:
/* UserControllerTest.js */
describe('UserCtrl', function () {
var $rootScope,
$scope,
controller;
beforeEach(function () {
module('ui.router');
module('myApp.system');
module('ngResource');
module('myApp.users');
inject(function ($injector) {
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
controller = $injector.get('$controller')('UserCtrl', {$scope: $scope});
});
});
it('should work', function () {
expect(true).toBe(true);
});
});
Or:
/* app.js */
...
angular.module('myApp.users', [
'ngResource',
'ui.router',
'mean.system'
]);
Is there some reason I wouldn't want to do the latter? Why is it that I never see that in docs and tutorials - would it prevent me from being able to mock those dependencies in testing?
Why is it that I don't need the latter sub-module definition for regular operation of my app? I do have a series of 'injection locals' specified for UserCtrl - why isn't that sufficient for the unit test?
The purpose of modules is to encapsulate self-contained parts of code (e.g. a reusable widget, code tgst implements a specific feature etc). In general, it is a good practice to have each module declare the dependencies it relies upon.
If not, then the module relies on the modules thst require it to declare those dependencies, which messes "self-containment", hurts testability and reusability and introduces a bunch if potential future bugs.
That said, there doesn't seem to be a reason not to declare dependencies with each module. (And no, it will not prevent you from mocking the dependencies in unit tests.) Of cource, as one would expect, each module is loaded once even if it is required by several modules.
The API reference is indeed not very detailed about angular.module, but the Developer Guide has a more extensive description.
E.g., quoting the "Dependencies" section:
Modules can list other modules as their dependencies. Depending on a module implies that required module needs to be loaded before the requiring module is loaded. In other words the configuration blocks of the required modules execute before the configuration blocks of the requiring module. The same is true for the run blocks. Each module can only be loaded once, even if multiple other modules require it.

AngularJS Guide - Dependency Injection - Factory methods

At the bottom of Angular's Dependency Injection guide, I came across this snippet about factory methods, and I'm having a hard time understanding where the "depProvider" and "depService" are being defined:
Here's the snippet:
angular.module('myModule', []).
config(['depProvider', function(depProvider){
...
}]).
factory('serviceId', ['depService', function(depService) {
...
}]).
directive('directiveName', ['depService', function(depService) {
...
}]).
filter('filterName', ['depService', function(depService) {
...
}]).
run(['depService', function(depService) {
...
}]);
Am I correct in that 'depProvider' and 'depService' are injected into these definitions, and would have to be defined elsewhere? Or are these built-in dependencies?
I assume that those are fictional modules that should show you how a factory (or anything else) could *dep*end on another service.
They are no built-in modules or anything.
Yes, you're right. That dependencies can be in the same module or in any module declared as a dependency (modules can have dependencies too).
For what it's worth every Angular built-in service starts with a $ so they are easily spotted (e.g. $scope, $http, $timeout etc).

minifying angular

I have this interest in automate/simplify angular project with a compiler tool, which might work on everything else, but angular inject and namespacing is awkward enough to escape compiler knowledge. What is the best/professional method for doing this?
thanks, just one last thing,
app.controller('ctrl',['$rootScope',function($rootScope){
...
}]);
works when minified, but how do I minify
app.config(['$routeProvider', function($routeProvider){
}]);
and does it work when I minify successive actions?
app.controller(...).directive(...).run(...)
In Angular, you need to annotate functions for the injector to know which dependencies you want to inject in your function. There are basically three ways to inject dependencies in your function which are being described on official angular website. The three ways are:
1.Use the inline array annotation
yourModule.controller('yourController', ['$scope', function($scope) {}]);
2.Use the $inject property annotation
var yourController = function($scope) {};
yourController.$inject = ['$scope'];
yourModule.controller('yourController', yourController);
3.Implictly from the function parameter names
yourModule.controller('yourController', function($scope) {});
Now when you minify your project, your dependencies names will get renamed.
In first case your code will be like
yourModule.controller('yourController', ['$scope', function(e) {}]);
In third case your code will be like
yourModule.controller('yourController', function(e) {});
It will break your app because angular has no way to recognize your dependency name. So it is advised never to use implicit dependency injection in your project. From the above two inline array annotation is the most popular way amongst programmers.
I would recommend using https://github.com/olov/ng-annotate. It will allow you to write your code like follows.
angular.module("MyMod").controller("MyCtrl", function($scope, $timeout) {
});
and then ngAnnotate turns it into the following which is safe for minification.
angular.module("MyMod").controller("MyCtrl", ["$scope", "$timeout", function($scope, $timeout) {
}]);
The minifyer leaves strings untouched, that's why we use the array notation.
Chaining methods wont change the way the minifyer keeps strings intact.
var app=module(myapp);
app.config(['$routeProvider', function($routeProvider){
$routeProvider.dosomestuffs()
}]);
will be minified in something like
var a=module(myapp);
a.config(['$routeProvider', function(b){
b.dosomestuffs()
}]);
but angular will still find its way around thanks to the '$routeProvider' string.
If you always use annotations there should not be problems minifying angular scripts.
app.controller(['$scope', function(mrScope) {
mrScope.yourProperty = 'it does not matter the dependency variable names if you use annotations';
}]);
As long as you use the array notation for the dependencies you are injecting, no minification trouble is expected. The minification tool you are using should handle any of your examples without trouble (on my project we're using uglify to accomplish that).
In fact, for oddly named injections (named with dots and chars that result in invalid function names like ABC.CDE), the array notation is the best way to inject them.
I had the same problem when minifying, and like you, it only failed for the $routeProvider config elements.. The answer for me was to use the $inject method like Himanshu says for my configs, even though the syntax you show for your first example works for my controllers. So I'll post my .config() code here since I don't see it specifically listed in the answers above:
var app = angular.module('myApp');
var RouteProviderConfig = function ($routeProvider) {
$routeProvider
.when(...)
.otherwise(...);
};
RouteProviderConfig.$inject = ['$routeProvider'];
app.config(RouteProviderConfig);
This fixed my error for configs, but my controllers work the way your first example is written:
app.controller('ctrl',['$rootScope',function($rootScope){
...
}]);
The Angular Documentation seems to suggest that both ways should work, so I think it's possible there is a bug with configs, or $routeProvider, or something else entirely...

Resources