I'm following the AngularJS tutorial on its site, and at the moment my controller is as followed, and the page loads perfectly:
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
});
As I was doing the "A Note on Minification" part of Step 5, I came up with this:
var phonecatApp = angular.module('phonecatApp', []);
function PhoneListCtrl($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}
//phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);
The page loads just fine with the second code, where I commented the creation of controller from the module. My question is, what's the difference between declaring a controller from the module and defining a function
There is no difference in the actual execution/behaviour, the second one is using something called a 'function constructor'. It is a common way of creating a class-like structure in javascript.
The first one under the hood will be doing the same thing it's just that angularJs' dependency injection model works with strings so this is easier to read.
Declaring a global function, AFAIK was possible in order to simplify simple demos and get up to speed quickly. In latest versions of Angular, it's not supported by default anymore (see https://github.com/angular/angular.js/commit/3f2232b5a181512fac23775b1df4a6ebda67d018).
It's bad practice anyway to pollute the global namespace with lots of functions.
Check out Todd Motto's style guide for angular: http://toddmotto.com/opinionated-angular-js-styleguide-for-teams/
Defining your controllers as a function expressions is important to keep your code DRY and allow for named stack traces:
This is okay:
function PhoneListCtrl($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}
The issue is that this puts the function in the global scope; ohhh nooo. The solution is to wrap the declaration in an IIFE. This allows you to keep the best practice of defining your functions and to not pollute the global scope.
Here's an example:
(function () {
angular.module('app', []);
// MainCtrl.js
function MainCtrl () {
}
angular
.module('app')
.controller('MainCtrl', MainCtrl);
// AnotherCtrl.js
function AnotherCtrl () {
}
angular
.module('app')
.controller('AnotherCtrl', AnotherCtrl);
// and so on...
})();
Like this:
function PhoneListCtrl($scope, $http) {
your function would be declared in the global scope.
And we know that is clearly bad to pollute the global scope since it may lead to variable/function conflict.
By wrapping it inside a controller, you isolate the scope.
Related
John Papa's famous Angular 1 Style Guide says to use IIFEs, to avoid the likes of var myApp = angular.module('myApp',[]);
and polluting the global namespace.
The example given is:
logger.js
(function() {
'use strict';
angular
.module('app')
.factory('logger', logger);
function logger() { }
})();
storage.js
(function() {
'use strict';
angular
.module('app')
.factory('storage', storage);
function storage() { }
})();
How does this work? Do I not need to declare the module at least once? E.g with angular.module('app',[]); (wrapped in an IIFE? (instead of var app = angular.module('app',[]); to avoid a global variable))?
However, the two usages of angular.module('app') in the example , do not declare, but will then evaluate angular.module('app') twice, which surely cannot be A Good Thing (in fact, I read a highly upvoted S.O. question earlier which said that this is A Bad Thing, and that there should be a single reference - but that would be a global, which is also A Bad Thing).
Which is it to be? Or can I declare my angular.module('app'), plus a few controllers, serves, factories, directives, in separate files, in separate IIFEs? If so, how?
Do I not need to declare the module at least once?
Yes, the module needs to be created with dependencies first:
app.js
angular.module('app',['ngRoute']);
This must be done before any subsequent scripts add to it. It need not be enclosed in an IIFE because it doesn't declare any global variables.
To avoid pollution of the global namespace, the following needs to be enclosed in an IIFE:
logger.js
(function() {
'use strict';
angular
.module('app')
.factory('logger', logger);
function logger() { }
})();
On the other hand
storage.js
'use strict';
angular.module('app')
.service('storage', function storage() {
var value;
this.get = function() { return value; };
this.set = function(val) {
value = val;
};
})
This does not need an IIFE because it does not declare any global variables or functions.
Both examples avoid polluting the global namespace.
For more information, see
AngularJS angular.module Method API Reference
angular.module('app') without array as 2nd param is a getter, it is ok to use it multiple times.
To improve this you need some build tool, e.g. we use webpack (it will wrap everything into IIFE for u btw) and now it looks like this:
logger.js :
export default ['$log', function($log) {}]
storage.js :
export default ['$http', function($http) {}]
module.js :
import logger from './logger.js';
import storage from './storage.js';
angular
.module('app')
.factory('logger', logger)
.factory('storage', storage);
I currently have 3 angularjs modules, each of which are (roughly) like so:
(function () {
var generalApp = angular.module('general-app', []);
generalApp.controller("NewsletterSignup.Controller", ["$scope", "$http", NewsletterSignupControllerFunction]);
}());
where NewsletterSignupControllerFunction is a global variable that is a reference to a function, eg:
var NewsletterSignupControllerFunction = function ($scope, $http) { ... };
Rather than use a global variable to share logic between the three modules, what is the simplest way to inject NewsletterSignupControllerFunction into each of the modules so I can use it to create the controllers? I have tried various approaches, none of which I can get to work.
One approach is to define a common module with the controller:
common.js
(function () {
angular.module('common', [])
.controller("NewsletterSignup.Controller",
["$scope", "$http", NewsletterSignupControllerFunction]
)
function NewsletterSignupControllerFunction ($scope,$http) {
//code ...
}
}());
Use that common module as a dependency:
angular.module("myApp",['common'])
For more information, see
AngularJS angular.module API Reference
I had observed that inside AngularSeed, some controllers have the following format:
angular.module('myApp.controllers', []).
controller('MyCtrl1', [function() {
}])
.controller('MyCtrl2', [function() {
}]);
whereas, some controllers have the following syntax:
var myApp = angular.module('myApp',[]);
myApp.controller('MyCtrl1', ['$scope', function($scope) {
} }]);
myApp.controller('MyCtrl2', ['$scope', function($scope) {
} }]);
Which is a good practice for a project in production?
Also, is there any performance difference between these two approaches?
There will be no difference in the performance among these two syntaxes.
But it is recommended that we use the second approach.
Remember that angular.module() returns a module object. This would expose the functions of controllers, filters, services, and directive registrations.
Now each of these functions would return the same module, so we are talking about the same reference. This is similar to a builder pattern.
According to Angular Best Practice for App Structure (Public), it is recommended that angular.module() should not be called more than once, and other files and modules should not modify the same.
For this reason, the latter is always recommended.
The logic is simple:
Expose your module as a global variable, and let other files add on to that variable.
if your project is very big i suggest you this syntax...
var controller = function (scope) {
};
controller.$inject = ["$scope"];
app.controller("appCtrl", controller);
When you include only a single component per file, there is rarely a need to introduce a variable for the module. Instead, use the simple getter syntax. When using a module, using chaining with the getter syntax avoids variables collisions or leaks.
From John Papa's Angular style guide:
/* avoid */
var app = angular.module('app');
app.controller('SomeController' , SomeController);
function SomeController() { }
/* recommended */
angular
.module('app')
.controller('SomeController' , SomeController);
function SomeController() { }
In app.js file I defined the following:
var app = angular.module("app", ["ngRoute"]);
In testController.js I defined the following:
app.controller('testController', ['$scope'], function($scope) {
$scope.temp1 = "";
$scope.temp2 = -1;
});
In testService.js I defined the following:
app.factory('testService', function ($http, $scope) {
'use strict';
return {
list: function (callback) {
$http.get('url?param=' + $scope.temp1).success(callback);
}
};
});
In testController.js and testService.js lint tells me that app is undefined. How can I tell both of the files that app is the app from app.js?
How can I tell testService.js that $scope.temp1 is actually taken from testController.js?
Thanks
First, all of your code should be wrapped in an immediately invoked function expression.
https://github.com/johnpapa/angular-styleguide#iife
(function(){
real code goes here
})();
This keeps the global variable space clean
To access app from the other code, you don't.. you retrieve it again. So your controller would look like this:
(function(){
angular.module("app").controller("testController", ....
})();
And finally, the controller part of your code has misplaced brackets. The closing ] should be after the function.
(function(){
angular.module("app").controller("testController", ['$scope',function($scope){
// function code here
}]
})();
And your service is a singleton (there is only one for the whole app. So, you would not pass scope to the constructor as you have, but you would pass it to the function that needed access to it.
But passing scope down to a service like that will probably tightly couple your controller to your service which is something you shouldn't be doing. You should be retrieving from the service and passing specific elements in that it needs rather than the scope that could change.
I have three different application files (in addition to vendor files) for my angular app loaded in this order
app.js
store.js
controller.js
The code from the different files is only visible to the others if I'm using a global variable, however, I thought if I used modules by starting each file like this
angular.module('myApp',
then I could avoid a global and have code defined in each file available to the others.
Example
if I do it this way with the global variable myApp then the storage provider is available in the controller.
var myApp = angular.module('myApp', ['LocalStorageModule'])
.config(['localStorageServiceProvider',
function(localStorageServiceProvider) {
localStorageServiceProvider.setPrefix('my-app');
}]);
myApp.factory('myStorage',
//code ommitted
myApp.controller('MyCtrl', [$scope, 'myStorage',
function MyController($scope, myStorage){
}
]);
However, if in the three files, I instead do it this way (without the global) then I get an unknownProvider error in myCtrl
angular.module('myApp', ['LocalStorageModule'])
.config(['localStorageServiceProvider',
function(localStorageServiceProvider) {
localStorageServiceProvider.setPrefix('my-app');
}]);
angular.module('myApp', [])
.factory('myStorage',
//code omitted
angular.module('myApp', [])
.controller('MyCtrl', [$scope, 'myStorage',
function MyController($scope, myStorage){
}
]);
Question: IN the example above, how can I make the storage from the factory available in the controller without using the global variable pattern?
You should only define module once, and use it in rest of the places. Otherwise it gets overwritten. Please remove the dependency array from module definition for factory & controller. Hope that helps.
angular.module('myApp')
.factory('myStorage',
//code omitted
angular.module('myApp')
.controller('MyCtrl', ['$scope', 'myStorage',
function ($scope, myStorage){
}
]);
Also your controller declaration is needs to be corrected as above.
The Best way to inject any service, factory etc... this way reduce Complicity...
`angular.module('myApp')
.factory('myStorage',
//code omitted
angular.module('myApp')
.controller('MyCtrl', myCtrlFun);
myCtrlFun.$inject = ['$scope', 'myStorage'];
function myCtrlFun($scope, myStorage){
}
`