AngularJS project structure and defintions - angularjs

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.

Related

AngularJs : How to declare an app & multiple controllers, serves, etc, using an IFFE?

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);

AngularJS: Declaring vs Not declaring controller

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.

Angularjs - Uknown Provider

Whenever I do this:
app.controller('hangmanController', ['$scope', 'wordnickAPIService', function ($scope, wordnickAPIService) {
I get this:
[$injector:unpr] Unknown provider: wordnickAPIServiceProvider <- wordnickAPIService
I read through This discussion on the topic, but didn't see an answer that applied. I am sure it is something simple or trivial that I am missing, but, jeez, if Angular isn't giving me fits trying to piece it all together.
Relevant HTML:
<body ng-app="angularHangmanApp" ng-controller="hangmanController">
My controller:
'use strict';
var app = angular.module('angularHangmanApp', []);
app.controller('hangmanController', ['$scope', 'wordnickAPIService', function ($scope, wordnickAPIService) {
[...]variable declarations[...]
var wordListURL = 'http://long_url_that_returns_some_json';
$scope.wordList = wordnickAPIService.get(wordListURL);
}]);
My factory:
'use strict';
var app = angular.module('angularHangmanApp', []);
app.factory('wordnickAPIService', ['$http', function($http) {
return {
get: function(url) {
return $http.get(url);
},
post: function(url) {
return $http.post(url);
},
};
}]);
The problem is that you are creating multiple modules with the same name.
To create a module in angular you use:
var app = angular.module('angularHangmanApp', []);
Then to get That module somewhere else you just type:
var app = angular.module('angularHangmanApp');
No extra []...
Also make sure you declare the service before trying to call it.
In your factory and your controller, you are actually redefining the app module.
Instead of saying
var app = angular.module('angularHangmanApp', []);
say
var app = angular.module('angularHangmanApp');
Use the first style of invocation only once in your application (maybe just app.js). All other references should use the second style invocation, otherwise, you're constantly redefining the angular app and losing all of the controllers, factories, directives declared previously.

Passing variables to angular.js scope from web page

What is correct way of passing variables from web page when initializing $scope?
I currently know 2 possibilities:
ng-init, which looks awful and not recommended (?)
using AJAX request for resource, which requires additional request to server which I do not want.
Is there any other way?
If those variables are able to be injected through ng-init, I'm assuming you have them declared in Javascript.
So you should create a service (constant) to share these variables:
var variablesFromWebPage = ...;
app.constant('initValues', variablesFromWebPage);
With this service, you don't need to add them to the scope in the app start, you can use it from any controller you have, just by injecting it (function MyCtrl(initValues) {}).
Althouhg, if you do require it to be in the scope, then this is one of the main reasons what controllers are meant for, as per the docs:
Use controllers to:
Set up the initial state of a scope object.
Add behavior to the scope object.
Just add this cotroller to your root node:
app.controller('InitCtrl', function($rootScope, initValues) {
$rootScope.variable1 = initValue.someVariable;
$rootScope.variable2 = initValue.anotherVariable;
});
#Cunha: Tnx.
Here some more details how I did it:
In the Webpage:
<script type="text/javascript">
var variablesFromWebPage = {};
variablesFromWebPage.phoneNumber = '#Model.PhoneNumber';
</script>
In the angular application file, registering the service:
var module= angular.module('mymodule', ['ngRoute', 'ngAnimate']);
module.factory('variablesFromWebPage', function () {
return variablesFromWebPage
});
And then the controller:
module.controller('IndexController',
function ($scope, $location, $http, $interval, variablesFromWebPage) {
$scope.phoneNumber = variablesFromWebPage.phoneNumber;
}
);

AngularJS - Error: Unknown provider: searchResultsProvider

EDIT: I have managed to get my unit tests running - I moved the code containing the services to a different file and a different module, made this new module a requirement for fooBar module, and then before each "it" block is called, introduced the code beforeEach(module(<new_service_module_name)). However, my application still won't run. No errors in console either. This is the only issue that remains - that when I use global scope for controllers definition, the application works, but when I use angular.module.controller - it does not.
I have a file app.js that contains the following:
'use strict';
var app = angular.module('fooBar', []);
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/', {
templateUrl: 'partials/form-view.html',
controller: FormViewCtrl
}).
when('/resultDisplay', {
templateUrl: 'partials/table-view.html',
controller: TableViewCtrl
}).
otherwise({redirectTo: '/'});
}]);
app.service('searchResults', function() {
var results = {};
return {
getResults: function() {
return results;
},
setResults: function(resultData) {
results = resultData;
}
};
});
I have another file controllers.js that contains the following:
'use strict';
var app = angular.module('fooBar', []);
app.controller('FormViewCtrl', ['$scope', '$location', '$http', 'searchResults',
function ($scope, $location, $http, searchResults) {
//Controller code
}]);
searchResults is a service that I created that simply has getter and setter methods. The controller above uses the setter method, hence the service is injected into it.
As a result, my application just does not run! If I change the controller code to be global like this:
function ($scope, $location, $http, searchResults) {
//Controller code
}
then the application works!
Also, if I use the global scope, then the following unit test case works:
'use strict';
/*jasmine specs for controllers go here*/
describe('Foo Bar', function() {
describe('FormViewCtrl', function() {
var scope, ctrl;
beforeEach(module('fooBar'));
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('FormViewCtrl', {$scope: scope});
}));
}
//"it" blocks
}
If I revert to the module scope, I get the error -
Error: Unknown provider: searchResultsProvider <- searchResults
Thus, by using global scope my application and unit tests run but by using app.controller, they seem to break.
Another point that I have noted is that if I include the controller code in app.js instead of controllers.js, then the application and unit tests start working again. But I cannot include them in the same file - how do I get this to run in the angular scope without breaking the application and unit tests?
You don't need to go that route. You can use the modular approach, but the issue is with your second parameter.
In your app.js you have this:
var app = angular.module('fooBar', []);
Then in your controller, you have this:
var app = angular.module('fooBar', []);
What you're doing there is defining the module twice. If you're simply trying to attach to the app module, you cannot pass in the second parameter (the empty array: []), as this creates a brand new module, overwriting the first.
Here is how I do it (based on this article for architecting large AngularJS apps.
app.js:
angular.module('fooBar',['fooBar.controllers', 'fooBar.services']);
angular.module('fooBar.controllers',[]);
angular.module('fooBar.services', []);
...etc
controllers.js
angular.module('foobar.controllers') // notice the lack of second parameter
.controller('FormViewCtrl', function($scope) {
//controller stuffs
});
Or, for very large projects, the recommendation is NOT to group your top-level modules by type (directives, filters, services, controllers), but instead by features (including all of your partials... the reason for this is total modularity - you can create a new module, with the same name, new partials & code, drop it in to your project as a replacement, and it will simiply work), e.g.
app.js
angular.module('fooBar',['fooBar.formView', 'fooBar.otherView']);
angular.module('fooBar.formView',[]);
angular.module('fooBar.otherView', []);
...etc
and then in a formView folder hanging off web root, you THEN separate out your files based on type, such as:
formView.directives
formView.controllers
formView.services
formView.filters
And then, in each of those files, you open with:
angular.module('formView')
.controller('formViewCtrl', function($scope) {
angular.module('formView')
.factory('Service', function() {
etc etc
HTH
Ok - I finally figured it out. Basically, if you wish to use the module scope and not the global scope, then we need to do the following (if you have a setup like app.js and controllers.js):
In app.js, define the module scope:
var myApp = angular.module(<module_name>, [<dependencies>]);
In controllers.js, do not define myApp again - instead, use it directly like:
myApp.controller(..);
That did the trick - my application and unit tests are now working correctly!
It is best practice to have only one global variable, your app and attach all the needed module functionality to that so your app is initiated with
var app = angular.module('app',[ /* Dependencies */ ]);
in your controller.js you have initiated it again into a new variable, losing all the services and config you had attached to it before, only initiate your app variable once, doing it again is making you lose the service you attached to it
and then to add a service (Factory version)
app.factory('NewLogic',[ /* Dependencies */ , function( /* Dependencies */ ) {
return {
function1: function(){
/* function1 code */
}
}
}]);
for a controller
app.controller('NewController',[ '$scope' /* Dependencies */ , function( $scope /* Dependencies */ ) {
$scope.function1 = function(){
/* function1 code */
};
}
}]);
and for directives and config is similar too where you create your one app module and attach all the needed controllers, directives and services to it but all contained within the parent app module variable.
I have read time and time again that for javascript it is best practice to only ever have one global variable so angularjs architecture really fills that requirement nicely,
Oh and the array wrapper for dependencies is not actually needed but will create a mess of global variables and break app completely if you want to minify your JS so good idea to always stick to the best practice and not do work arounds to get thing to work
In my case, I've defined a new provider, say, xyz
angular.module('test')
.provider('xyz', function () {
....
});
When you were to config the above provider, you've inject it with 'Provider' string appended.
Ex:
angular.module('App', ['test'])
.config(function (xyzProvider) {
// do something with xyzProvider....
});
If you inject the above provider without the 'Provider' string, you'll get the similar error in OP.

Resources