Angularjs - Dynamic configuration based on Environment - javascript

I have a sample angular APP - app.js
angular
.module('myUiApp', [
'restangular',
'ngRoute',
'ngCookies',
'ui.bootstrap',
'ui.sortable',
'smart-table',
'config'
])
.config(function($routeProvider, $httpProvider, $sceProvider, $logProvider, RestangularProvider, config) {
RestangularProvider.setBaseUrl(config.apiBaseUrl);
RestangularProvider.setDefaultHeaders({'Content-Type': 'application/json'});
//routing here
.....
});
my Config.js looks like -
angular.module('config', []).service('config', function($location, ENV) {
return ENV.dev;
});
my constants.js looks like -
'use strict';
angular.module('config', []).constant('ENV', (function() {
return {
dev: {
appBaseUrl:'http://localhost:9000/',
apiBaseUrl:'http://localhost:8082/api/'
}
}
})());
I am getting the error saying, Failed to instantiate module myUiApp due to:
[$injector:unpr] Unknown provider: config.
My assumption is injecting config module will invoke the service, which in turn return the json object. any thoughts or suggesstions to do this dynamic config better?

You can only inject providers into an angular .config() block. You're attempting to inject a service, and that is likely the cause of your error.
Also, you have angular.module('config', []) in two different places. This should only be used once to instantiate the module. Use angular.module('config') (without the second argument) subsequently to reference that module.

I would avoid calling the module config, in favor of something that isn't a method used by angular module.config() -- maybe myConfigModule
Secondly, make sure your script includes the constants.js file and the Config.js file before it includes the app.js file
Lastly double check that this situtation is not affecting you:
defining the module twice with angular.module('config', []) ( emphasis on the [ ] ..) When you define the module with the square brackets, you are saying "New Module". In the second file that you include, change it to angular.module('config') -- or, combine the files into this:
angular.module('myConfigModule', [])
.constant('ENV', (function() {
return {
dev: {
appBaseUrl:'http://localhost:9000/',
apiBaseUrl:'http://localhost:8082/api/'
}
}
}).service('config', function($location, ENV) {
return ENV.dev;
});
UPDATE: And typically I see this syntax for controllers, services, anything that is injecting anything else
.service('config', ['$location', 'ENV', function($location, ENV) {
return ENV.dev;
}]); // see beginning and end of square bracket
// also add new injected modules to both the array (surrounded by quotes) and the function

Related

Error: [$injector:modulerr] Failed to instantiate module app due to: State 'frameworks'' is already defined registerState#http://localhost:3030 [duplicate]

I am developing an AngularJS application. To ship the code in production, I'm using this Grunt configuration/task:
grunt.registerTask( 'compile', [
'sass:compile', 'copy:compile_assets', 'ngAnnotate', 'concat:compile_js', 'uglify', 'index:compile'
]);
It's really hard to debug, and it's kind of a question to people who already ran into such problems and can point to some direction.
My main module is including those submodules:
angular
.module('controlcenter', [
'ui.router',
'ui.bootstrap',
'templates-app',
'templates-common',
'authentication',
'api',
'reports',
'interceptors',
'controlcenter.websites',
'controlcenter.users',
'controlcenter.campaigns',
'controlcenter.reports',
'controlcenter.login'
])
.run(run);
The error I get is following:
Uncaught Error: [$injector:modulerr] Failed to instantiate module controlcenter due to:
Error: [$injector:modulerr] Failed to instantiate module controlcenter.websites due to:
Error: State 'websites'' is already defined
If I remove the websites module, I get the same error for
controlcenter.users.
I am using the ui-router to handle routing inside the app.
After my build process (for integration testing), everything works just fine:
grunt.registerTask( 'build', [
'clean', 'html2js', 'jshint', 'sass:build',
'concat:build_css', 'copy:build_app_assets', 'copy:build_vendor_assets',
'copy:build_appjs', 'copy:build_vendorjs', 'copy:build_vendorcss', 'index:build', 'karmaconfig',
'karma:continuous'
]);
So maybe ngAnnotate or or concat/uglify are doing weird things here?
UPDATE 1:
It has something to do with my configuration of the modules. Here is the code:
angular
.module('controlcenter.websites',
[
'ui.router'
]
)
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider.state( 'websites', {
url: '/websites',
views: {
"main": {
controller: 'WebsitesController',
templateUrl: 'websites/websites.tpl.html'
}
}
});
}
When I change the name of the state to websites_2, I get an error
with 'websites_2 is already defined'.
When I remove the module completely, the next one hast the same problem inside the config file. So is the structure wrong?
Update 2:
The problem seems concat related.
It takes every JS file and adds it one after another to one, bigger file. All of my modules are at the end. The last module always has the problem with 'state already defined'. So it's not just the order of the modules appending to each other, it's something elsse...
Update 3:
I placed my code (I've excluded every Controller-Code and functions, just the scaffold) in a gist. This is the outcome after my compile process, without uglifying it.
Issue:
You have multiple files that contains a config function to configure your module, like this:
angular
.module('controlcenter.websites', [])
.config(config);
function config() {
// ...
}
The problem is that after you concatenate all files you end up with a big file with multiple declarations of config. Because of JavaScript's variable hoisting, all declarations are moved to the top and only the very last of them is evaluated, and this one is:
function config($stateProvider) {
$stateProvider.state( 'websites', {
url: '/websites',
views: {
"main": {
controller: 'WebsitesController',
templateUrl: 'websites/overview/websites.tpl.html'
}
},
data : {requiresLogin : true }
});
}
Hence, each time you .config(config) a module, you are telling Angular to configure your module with that particular configuration function, which means that it executes multiple times and tries to define the state websites more than once.
Solution:
Wrap each JavaScript file code with a closure. This way you will avoid declaring a variable/function more than once:
(function (angular) {
'use strict';
angular
.module('controlcenter.website.details', ['ui.router'])
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state( 'websiteDetails', {
url: '/websiteDetails/:id',
views: {
"main": {
controller: 'WebsiteDetailsController',
templateUrl: 'websites/details/website.details.tpl.html'
}
},
data : {requiresLogin : true }
})
.state( 'websiteDetails.categories', {
url: '/categories',
views: {
"detailsContent": {
templateUrl: 'websites/details/website.details.categories.tpl.html'
}
},
data : {requiresLogin : true }
})
;
}
})(window.angular);
Edit:
I strongly recommend you wrap your files into closures. However, if you still don't want to do that, you can name your functions according to their respective modules. This way your configuration function for controlcenter.website.details would become controlcenterWebsiteDetailsConfig. Another option is to wrap your code during build phase with grunt-wrap.
window.angular and closures: This is a technique I like to use on my code when I'm going to uglify it. By wrapping your code into a closure and giving it a parameter called angular with the actual value of window.angular you are actually creating a variable that can be uglified. This code, for instance:
(function (angular) {
// You could also declare a variable, instead of a closure parameter:
// var angular = window.angular;
angular.module('app', ['controllers']);
angular.module('controllers', []);
// ...
})(window.angular);
Could be easily uglified to this (notice that every reference to angular is replaced by a):
!function(a){a.module("app",["controllers"]),a.module("controllers",[])}(window.angular);
On the other side, an unwrapped code snippet like this:
angular.module('app', ['controllers']);
angular.module('controllers', []);
Would become:
angular.module("app",["controllers"]),angular.module("controllers",[]);
For more on closures, check this post and this post.
If you check it in the concatenated file, do you have the states defined twice? Can it be that you are copying the files twice? Check the temporary folders from where you are taking the files (also in grunt config, what you are copying and what you are deleting...).
So I had the same problem but with the following setup:
yeoman angular-fullstack (using typescript)
Webstorm
With the angular-fullstack configuration, the closures were already implemented (as Danilo Valente suggests) so I struggled quite a bit until I found out that in Webstorm, I had the typescript compiler enabled which compiled all of my *.ts files to *.js. But since Webstorm is so 'smart', it does not show these compiled files in the working tree. Grunt however concatenated of course all files regardless if it is typescript of JS. That's why - in the end- all of my states were defined twice.
So the obvious fix: Disabled typescript compiler of webstorm and deleted all the generated *.js files and it works.

Multiple services in Ionic - how to structure multiple services in one file?

I have a question about Ionic services. As you know, Ionic framework comes with a built in www directory which contains the js directory, which contains the services.js file. My first service works. I attempted to write another service in the same, services.js file and as it turns out, I got an error: Uncaught "SyntaxError: Unexpected token . http://localhost:8100/js/services.js Line: 57" in addition to "(index):28 Uncaught Error: [$injector:modulerr] Failed to instantiate module starter due to:
Error: [$injector:modulerr] Failed to instantiate module starter.services due to:
Error: [$injector:nomod] Module 'starter.services' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument." Anyway, here's my code, take a look and let me know what I can do differently. Also, if you need more snippets, let me know!
//services.js
//the first factory, API works
angular.module('starter.services', [])
.factory('API', function($http) {
var apiFooUrl = 'http://localhost:3000/api/do/thing'
return {
all: function(){
return $http.get(apiFooUrl + 's')
},
show: function(thingID){
console.log("stock service get running", thingID);
return $http.get(apiFooUrl + '/' + thingID)
}
};
});
//this is the one that returns the error
.factory('Users', function($http) {
var apiUrl = 'http://localhost:3000/api/users/'
return {
index: function(){
return $http.get(apiUrl)
}
show: function(id){
return $http.get(apiUrl + id)
}
}
})
Edited for more info:
So in response to David L's comment:
I've injected it into my GlobalCtrl like so:
.controller('GlobalCtrl', function(Users, $rootScope, $state, $window,
Things, $scope){
$scope.newUser = {}
$scope.user = {}
//show all Users
Users.index().success(function(results){
$scope.users = results
})
})
It'll also be injected into a DetailCtrl as well.
In app.js, the services are injected like so:
angular.module('starter', ['ionic', 'starter.controllers',
'starter.services'])
There's a lot more that needs to go on eventually so I want to make sure I have it right, now.
Have I included the starter.controllers properly? Do I have to include more than one if I have multiples?
They are indeed included in the index.html file.
you have added service after closing the module,
removing the semicolon will solve this problem
}) //here reomved the semicolon
//this is the one that returns the error
.factory('Users', function($http) {

Why is my directives module not being included in my Angular app module?

Just learning Angular and I'm encountering a few issues with module resolution. In js/directives/directives.js I have the following directive scoped to a directives module:
angular.module("directives").directive("selectList", function() {
return {
restrict: 'E',
link: function() {
// do stuff
}
}
});
On my webpage:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.js"></script>
<script>
angular.module("editUserApp", ["directives"])
.controller("editUserController", ['$http', '$scope', function($http, $scope) {
// do stuff here
}]
);
</script>
The error I'm getting is as follows:
Error: [$injector:modulerr] Failed to instantiate module editUserApp due to:
[$injector:modulerr] Failed to instantiate module directives due to:
[$injector:nomod] Module 'directives' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
Now, obviously, editUserApp cannot know where directives is, all by itself, so how do I tell it to fetch the directives.js file? Do I have to include it in a script tag (which doesn't seem very scalable)?
I need some way to import directives to my angular app. How can I do this?
You need to include your js/directives/directives.js file into your html and remove the directives dependency on your App module.
your code should be :
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.js"></script>
<script>
angular.module("editUserApp", [])
.controller("editUserController", ['$http','directives', '$scope', function($http, $scope,directives) {
// do stuff here
}]
);
</script>
You need
angular.module("directives" ,[])
in your first block
angular.module("directives")
tries to find an existing module called directives
If you are looking for a way to import these files on a as needed basis, you might want to look at http://requirejs.org/ or http://browserify.org/ or similar tools.

Angular JS Error: [$injector:nomod] Module 'portfolioMockupApp.services' is not available

I'm attempting to write some unit tests with Karma and am receiving the following error:
PhantomJS 1.9.8 (Mac OS X) ERROR
Error: [$injector:nomod] Module 'portfolioMockupApp.services' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
http://errors.angularjs.org/1.3.3/$injector/nomod?p0=portfolioMockupApp.services
at /Users/danielbogart/Documents/coding/work/portfolio-mockup/bower_components/angular/angular.js:1749
Also, I have two separate files within the portfolioMockupApp.services module, both saved in the scripts/services directory.
Karma.conf files section:
files: [
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
'bower_components/angular-animate/angular-animate.js',
'bower_components/angular-cookies/angular-cookies.js',
'bower_components/angular-resource/angular-resource.js',
'bower_components/angular-sanitize/angular-sanitize.js',
'bower_components/angular-touch/angular-touch.js',
'test/mock/**/*.js',
'test/spec/**/*.js',
'app/scripts/services/*.js',
'app/scripts/directives/*.js',
'app/scripts/controllers/*.js',
'app/scripts/app.js',
'node_modules/angular/angular.js',
'node_modules/angular-mocks/angular-mocks.js',
'./src/**/*.js',
'./test/**/*.js'
],
Portfolio.js spec (first and only test currently):
'use strict';
describe('Controller: PortfolioCtrl', function () {
// load the controller's module
beforeEach(module('portfolioMockupApp', 'portfolioMockupApp.services'));
var PortfolioCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $scope, $log, $stateParams, $state,
$rootScope,portService, portfolioCreateService) {
scope = $rootScope.$new();
PortfolioCtrl = $controller('PortfolioCtrl', {
$scope: scope
});
}));
it('should have a list of 5 tabs by default', function () {
expect(scope.tabs.length).toBe(5);
});
});
The problem stemmed from having two separate service files using the same service module. In the Karma.conf file I had to explicitly load the service file that initialized the module, and then the other service file and rest of the app afterwards.
'app/scripts/services/port-service.js',
'app/scripts/services/new-port-service.js',
'app/scripts/app.js',
'app/scripts/services/*.js',
'app/scripts/directives/*.js',
'app/scripts/controllers/*.js',
Thanks for checking back in with a solution. I had this same issue when two modules relied on each other and existed in the same folder, lets call them app/scripts/parentModule.js and app/scripts/constants.js. Both should be picked up by the wildcard entry in karma.config.js.
'app/scripts/*.js'
'app/scripts/anotherFolder/*.js'
Since constants.js relies on parentModule.js, the later must be included first and my guess is the wildcard was including the files alphabetically but I've not confirmed this yet.
'app/scripts/parentModule.js'
'app/scripts/*.js'
'app/scripts/anotherFolder/*.js'

Angular.module minification bug

Having the darnedest time trying to figure out why minification is not working.
I have injected via an array object my providers prior the function per numerous suggestions across the web and yet still "Unknown provider: aProvider <- a"
Regular:
var app = angular.module('bpwApp', ['ui.bootstrap', 'ui', 'myTabs'])
.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider){
$routeProvider.
when('/', {templateUrl: 'partials/home.jade', controller: HomeCtrl});
$locationProvider.html5Mode(true);
}])
Minified:
var app = angular.module('bpwApp', ['ui.bootstrap', 'ui', 'myTabs'])
.config(['$routeProvider', '$locationProvider', function(a, b){
a.
when('/', {templateUrl: 'partials/home.jade', controller: HomeCtrl});
b.html5Mode(true);
}])
Any suggestion would be much obliged!
I ran into this problem before with Grunt.js Uglify plugin.
One of the options are mangle
uglify: {
options: {
mangle: false
},
Which I believe runs regex functions on "like strings" and minifys them.
For example:
angular.module("imgur", ["imgur.global","imgur.album"]);
Would become:
angular.module("a", ["a.global","a.album"]);
Disable it --- this feature doesn't play nice with Angular.
Edit:
To be more precise as #JoshDavidMiller explains:
Uglify mangle only mangles like variables, which is what actually causes the AngularJS problem. That is, the problem is in injection and not definition.
function MyCtrl($scope, myService) would get mangled to function MyCtrl(a, b), but the service definition inside of a string should never get altered.
Running ng-min before running uglify solves this problem.
Problem
From AngularJS: The Bad Parts:
Angular has a built in dependency injector that will pass appropriate
objects to your function based on the names of its parameters:
function MyController($scope, $window) {
// ...
}
Here, the names of the parameters $scope and $window will be
matched against a list of known names, and corresponding objects get
instantiated and passed to the function. Angular gets the parameter
names by calling toString() on the function, and then parsing the
function definition.
The problem with this, of course, is that it stops working the
moment you minify your code. Since you care about user experience
you will be minifying your code, thus using this DI mechanism will
break your app. In fact, a common development methodology is to use
unminified code in development to ease debugging, and then to minify
the code when pushing to production or staging. In that case, this
problem won’t rear its ugly head until you’re at the point where it
hurts the most.
(...)
Since this dependency injection mechanism doesn’t actually work in the
general case, Angular also provides a mechanism that does. To be sure,
it provides two. You can either pass along an array like so:
module.controller('MyController', ['$scope', '$window', MyController]);
Or you can set the $inject property on your constructor:
MyController.$inject = ['$scope', '$window'];
Solution
You can use ng-annotate for auto adding annotations required for minifying:
ng-annotate adds and removes AngularJS dependency injection
annotations. It is non-intrusive so your source code stays exactly the
same otherwise. No lost comments or moved lines.
ng-annotate is faster and stabler than ngmin (which is now deprecated) and it has plugins for many tools:
grunt-ng-annotate
gulp-ng-annotate
browserify-annotate
Starting from AngularJS 1.3 there's also a new param in ngApp called ngStrictDi:
if this attribute is present on the app element, the injector will be
created in "strict-di" mode. This means that the application will fail
to invoke functions which do not use explicit function annotation (and
are thus unsuitable for minification), as described in the Dependency
Injection guide, and useful debugging info will assist in tracking
down the root of these bugs.
I got same error. However, for me, the problem is directives' controller declaration. You should do this instead.
myModule.directive('directiveName', function factory(injectables) {
var directiveDefinitionObject = {
templateUrl: 'directive.html',
replace: false,
restrict: 'A',
controller: ["$scope", "$element", "$attrs", "$transclude", "otherInjectables",
function($scope, $element, $attrs, $transclude, otherInjectables) { ... }]
};
return directiveDefinitionObject;
});
https://github.com/angular/angular.js/pull/3125
I had a similar issue using grunt, ngmin and uglify.
I ran the process in this order: concat, ngmin, uglify
I was continuing to get the $injector error from angular until I added in the uglify options mangle: false - then everything was fixed.
I also tried to add the exceptions to uglify like this:
options: {
mangle: {
except: ['jQuery', 'angular']
}
}
But to no avail...
Here is my gruntFile.js for further clarification:
module.exports = function(grunt) {
'use strict';
// Configuration goes here
grunt.initConfig({
pkg: require('./package.json'),
watch: {
files: ['scripts/**/*.js', 'test/**/*spec.js', 'GruntFile.js'],
tasks: ['test', 'ngmin']
},
jasmine : {
// Your project's source files
src : ['bower_components/angular/angular.js', 'bower_components/angular-mocks/angular-mocks.js', 'scripts/app.js', 'scripts/**/*.js' ],
// Your Jasmine spec files
options : {
specs : 'test/**/*spec.js',
helpers: 'test/lib/*.js'
}
},
concat: {
dist : {
src: ['scripts/app.js', 'scripts/**/*.js'],
dest: 'production/js/concat.js'
}
},
ngmin: {
angular: {
src : ['production/js/concat.js'],
dest : 'production/js/ngmin.js'
}
},
uglify : {
options: {
report: 'min',
mangle: false
},
my_target : {
files : {
'production/app/app.min.js' : ['production/js/ngmin.js']
}
}
},
docular : {
groups: [],
showDocularDocs: false,
showAngularDocs: false
}
});
// Load plugins here
grunt.loadNpmTasks('grunt-ngmin');
grunt.loadNpmTasks('grunt-docular');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jasmine');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-connect');
// Define your tasks here
grunt.registerTask('test', ['jasmine']);
grunt.registerTask('build', ['concat', 'ngmin', 'uglify']);
grunt.registerTask('default', ['test', 'build', 'watch']);
};
AndrewM96 suggestion of ng-min is right.
The alignment and white space matters to Uglify as well as Angular.
I had a similar problem. And solved it the following way. We need to run a Gulp module called gulp-ng-annotate before we run uglify.
So we install that module
npm install gulp-ng-annotate --save-dev
Then do the require in Gulpfile.js
ngannotate = require(‘gulp-ng-annotate’)
And in your usemin task do something like this
js: [ngannotate(), uglify(),rev()]
That solved it for me.
[EDIT: Fixed typos]
Uglify has an option to disable mangling on specific files:
options: {
mangle: {
except: ['jQuery', 'angular']
}
}
https://github.com/gruntjs/grunt-contrib-uglify#reserved-identifiers
This is very difficult to debug because a lot of services are named the same (mostly e or a). This will not solve the error, but will provide you with the name of the unresolved service which enables you to track down, in the uglified output, the location in the code and finally enables you to solve the issue:
Go into lib/scope.jsof Uglify2 (node_modules/grunt-contrib-uglify/node_modules/uglify-js/lib/scope.js) and replace the line
this.mangled_name = this.scope.next_mangled(options);
with
this.mangled_name = this.name + "__debugging_" + counter++

Categories

Resources