AngularJS Inject service in .run function - angularjs

In my AngularJS app, using Yeoman, when minifying my app I have this error :
Uncaught Error: [$injector:unpr] Unknown provider: aProvider <- a <- $http <- AuthenticationService
that I obviously do not have before minifying.
Here is the definition of my service in a separate runner.js file :
angular.module('myApp').run(['$rootScope', 'AuthenticationService', 'FlashService', 'SessionService', function ($rootScope, AuthenticationService, FlashService, SessionService) {
//some code
}]);
I thought of course about the typical Injection error when minifying but I am struggling to see what is wrong in my code...
UPDATE
My AutenticationService :
angular.module('myApp').factory("AuthenticationService", ['$http', '$rootScope', '$sanitize', 'SessionService', 'FlashService', 'SETTINGS', function($http, $rootScope, $sanitize, SessionService, FlashService, SETTINGS) {
var cacheSession = function() {
SessionService.set('authenticated', true);
};
var uncacheSession = function() {
SessionService.unset('authenticated');
SessionService.unset('user');
};
var loginError = function(response) {
FlashService.show('warning', response.flash);
};
var loginSuccess = function(response) {
SessionService.set('user', JSON.stringify(response));
FlashService.clear();
};
var logoutSuccess = function(response) {
FlashService.show('success', response.flash);
};
var sanitizeCredentials = function(credentials) {
return {
email: $sanitize(credentials.email),
password: $sanitize(credentials.password)
};
};
return {
login: function(credentials) {
var login = $http.post(SETTINGS.urlBackend+"/auth/login", sanitizeCredentials(credentials));
login.success(cacheSession);
login.success(loginSuccess);
login.error(loginError);
return login;
},
logout: function() {
var logout = $http.get(SETTINGS.urlBackend+"/auth/logout");
logout.success(uncacheSession);
logout.success(logoutSuccess);
logout.error(loginError);
return logout;
},
isLoggedIn: function() {
var checked = $http.get(SETTINGS.urlBackend+"/auth/check");
return (checked && SessionService.get('authenticated'));
}
};
}]);

Try setting mangle: false in the Uglify configuration in your Gruntfile.js:
grunt.initConfig({
// ...
uglify: {
options: {
mangle: false
}
}
});
I've had this happen when using certain packages from Bower. I believe some of the Angular UI suite of tools weren't compatible, for some reason.

I highly recommend an alternative approach to manually setting up the angular minification work around - the wrapping of the "module" - controller, service, factory - in the [] brackets.
Use the ng-min module! It's written by the guys as Angular - namely Brian Ford. But most important it removes this complication in writing Angular modules, they become clean and readable again and the ng-min does the hard work of fixing minification issues.
I know it's not an answer to you question, but it might be a solution to the problem you are facing in general.
// Allow the use of non-minsafe AngularJS files. Automatically makes it // minsafe compatible so Uglify does not destroy the ng references
ngmin: {
dist: {
files: [
{
expand: true,
cwd: '.tmp/concat/scripts',
src: '*.js',
dest: '.tmp/concat/scripts'
}
]
}
},

Related

Possibly unhandled rejection in a karma unit test

I use jasmine to write a unit test for a controller, where I have to check whether a function worked on $scope.$watch. I use 'controller as' syntax, but I've injected $scope to create a watcher. However, my test throws a vague exception on $scope.$apply(). Here's my controller:
OrderController.$inject = ['Order', 'cart',
'$mdDialog', '$state', '$rootScope', '$scope'];
function OrderController(Order, cart, $mdDialog, $state, $rootScope, $scope) {
var vm = this;
vm.order = new Order({
name: '',
phone: '',
address: ''
});
vm.order.items = [];
vm.promoCode = '';
vm.order.promo_code = false;
$scope.$watch('vm.promoCode', function () {
if (vm.promoCode && vm.promoCode == 'm7e17')) {
vm.order.promo_code = true;
}
}); }
And here's my test:
describe('Order Controller', function () {
var OrderController, CartService, $scope;
var item = {};
beforeEach(angular.mock.module('app'));
beforeEach(angular.mock.module('app.services'));
beforeEach(inject(function ($controller, _$rootScope_, _cart_, $state, $mdDialog) {
$scope = _$rootScope_.$new();
CartService = _cart_;
CartService.add(item);
OrderController = $controller('OrderController',
{
$scope: $scope,
$rootScope: _$rootScope_,
cart: CartService,
$state: $state,
$mdDialog: $mdDialog
}
);
}));
it('Should save a boolean whether a promo code has been entered', function () {
expect(OrderController.order.promo_code).toBe(false);
OrderController.promoCode = 'm7e17';
$scope.$apply();
expect(OrderController.order.promo_code).toBe(true);
}); });
However, when I call $scope.$apply inside my spec, it throws an error: 'Possibly unhandled rejection: ru thrown'. Could not find anything about it on the internet so, what I am I doing wrong here? I have other tests using the scope too, but they don't fail (though not calling $scope.$apply inside them), how can this be fixed?
I had recently the same issue.(Only with ' Possibly unhandled rejection: en thrown')
This issue is caused by the design of angular-translate, i assume you use this in your app.
Have a look at the following link:
How do unit test with angular-translate
There are some approaches to solve this issue.
I used the one which extracts the config of the translationProvider in an own file and in karma i exclude this one.
angular.module('qPro5App').config(loaderConfig);
loaderConfig.$inject = ['$translateProvider'];
function loaderConfig($translateProvider) {
// translation i18n support
$translateProvider.useStaticFilesLoader({
files: [{
prefix: 'assets/i18n/errors-',
suffix: '.json'
},
{
prefix: 'assets/i18n/ui-',
suffix: '.json'
}]
});
$translateProvider
.preferredLanguage('en')
.fallbackLanguage('en')
.useSanitizeValueStrategy('sanitize');
}
and in karma:
exclude: ['app/config/app-i18n-loader.js'],
I had the same problem in a slightly different context (handling a promise) and added
.catch(angular.noop)
which fixed the problem.

Mocking a service in another service from a different module

I have a service that depends on another service from a different module like so:
(function() {
'use strict';
angular
.module('app.core')
.factory('userService', userService);
function authService() {
return: {
userLoggedIn: false
}
}
})();
(function() {
'use strict';
angular
.module('app.services')
.factory('AuthService', authService);
authService.$inject = ['$http', 'userService'];
function authService($http, userService) {
}
I'm trying write tests for my authService but am getting injection errors since it can't find userService
beforeEach(function() {
module('app.services');
});
beforeEach(inject(function(_AuthService_) {
authService = _AuthService_;
}));
How can I overcome this, will using $provide help me here?
UPDATE
I have attempted the following, but still getting the error
beforeEach(function() {
module('app.services');
});
beforeEach(inject(function(_AuthService_, _$provide_) {
authService = _AuthService_;
$provide = _$provide_;
}));
beforeEach(function() {
module(function ($provide) {
$provide.value('userService', function(){
return {
userLoggedIn: false
}
});
});
});
SOLVED
Ok, so I just needed to do the following:
beforeEach(function() {
module('app.dataservices');
module(function ($provide) {
$provide.value('userService', function(){
return {
userLoggedIn: false
}
});
});
});
beforeEach(inject(function(_AuthService_) {
authService = _AuthService_;
}));
Tests are now passing fine for me
Let's say you service uses the $state service and you want to mock id. Specifically the get method. Then you just need to add inside the first describe something like this.
beforeEach(function () {
module(function ($provide) {
$provide.service('$state', function() {
return {
get: function() {}
}
});
});
});
In this gist you can find some interesting examples of mocking services using $provide.
you should be preloading all services in your karma.conf.js (i assume you are using karma).
here is our karma.conf.js file ...
/**
* Karma test runner configuration
*/
'use strict';
module.exports = function (config) {
config.set({
basePath: './',
browsers: ['PhantomJS'],
frameworks: ['jasmine'],
reporters: ['mocha', 'coverage'],
singleRun: true,
preprocessors: {
'src/**/!(*spec)*.js': ['coverage'],
'dest/**/*.html': ['ng-html2js']
},
ngHtml2JsPreprocessor: {
stripPrefix: 'dest/',
moduleName: 'ngHtmlFiles'
},
coverageReporter: {
type: 'html',
dir: 'coverage'
},
files: [
'dest/vendor.min.js',
'bower_components/angular-mocks/angular-mocks.js',
'src/**/*.js',
'dest/**/*.html'
]
});
};

Unit testing angularjs controller using Node.js, Gulp.js and Mocha

I am trying to unit test an angularjs controller using Node.js. I am using gulp.js and mocha to run the tests, via gulp-mocha.
This is what my gulpfile.js looks like right now:
(function () {
var gulp = require('gulp');
var mocha = require('gulp-mocha');
gulp.task('default', function () {
return gulp
.src('Scripts/Tests/*.js', { read: false })
.pipe(mocha({
ui: 'tdd',
reporter: 'dot',
globals: {
angular: require('./Scripts/angular.js')
}
}));
});
})();
This is the code under test:
(function () {
var application = angular.module('Main');
application.controller('MainController', ['$scope', function ($scope) {
$scope.isDisabled = function () {
return true;
};
}]);
})();
And this is the test file itself:
(function () {
var chai = require('chai');
var assert = chai.assert;
angular.module('Main', []); // Create an empty module
require('../MainController.js'); // Fill the module with the controller
suite('Main Controller', function () {
test('isDisabled always true', function () {
var controllerFactory = angular.injector.get('$controller');
var controller = controllerFactory('MainController', {
'$scope': {}
});
var result = controller.isDisabled();
assert.isTrue(result);
});
});
})();
I need to make angular a global in order for my test and the file I am testing to work. However, the call to require in gulpfile.js is giving me a Reference Error: window is not defined error. That makes sense, since I am running in Node.js.
What do I need to do to load up angular in Node.js?
As #unobf said (and the angularjs documentation says), the trick to testing angular from Node.js was to use Karma. That meant installing karma and karma-mocha, along with karma-chrome-launcher, karma-ie-launcher, karma-firefox-launcher (or whatever) via npm install.
Then I basically had to redo my gulp.js file from scratch:
(function () {
var gulp = require('gulp');
var karma = require('karma').server;
gulp.task('runTests', function (done) {
karma.start({
configFile: __dirname + '/karma.config.js',
singleRun: true
}, done);
});
gulp.task('default', ['runTests']);
})();
I also had to create a karma.config.js file to configure karma to use Chrome, Firefox, IE with mocha. In the same file, I configured mocha to use the tdd UI and the dot reporter:
module.exports = function(config) {
config.set({
browsers: ['Chrome', 'Firefox', 'IE'],
frameworks: ['mocha'],
files: [
'./Scripts/angular.js',
'./Scripts/chai.js',
'./Scripts/*.js',
'./Scripts/Tests/*.js'
],
singleRun: true,
client: {
mocha: {
reporter: 'dot',
ui: 'tdd'
}
}
});
};
I learned you have to specify angularjs, chai.js, etc. first so they are picked up first and in global scope for the other files. Along the same lines, the files for the code being tested have to be listed before the test files.
The code being tested didn't change at all. Once my tests were running, I realized my test was broken, and it ended up looking like this to pass:
(function () {
var assert = chai.assert;
suite('Main Controller', function () {
test('isDisabled always true', function () {
var injector = angular.injector(['ng', 'Main']);
var $controller = injector.get('$controller');
var scope = {};
var controller = $controller('MainController', {
'$scope': scope
});
var result = scope.isDisabled();
assert.isTrue(result);
});
});
})();
The big take away was that the controller simply populates the $scope object. I call isDisabled() on the $scope object instead. Of course, getting at the controller is a lot of work, so it makes more sense to use the inject method provided by angular-mocks.js:
(function () {
var assert = chai.assert;
suite('Main Controller', function () {
var $scope = {};
setup(function () {
module('Main');
inject(function ($controller) {
$controller('MainController', {
'$scope': $scope
});
});
});
test('isDisabled always true', inject(function ($controller) {
var result = $scope.isDisabled();
assert.isTrue(result);
}));
});
})();
Hopefully this is enough to get anyone started who is trying to use Node.js and mocha to test angularjs code.
I am doing this in Grunt, but the same principle applies to Gulp. You need to inject the "angular-mocks.js" file to be able to mock the dependency injection. I am using Karma http://karma-runner.github.io/0.12/index.html to do this and set it up in my karma.conf.js file as follows:
module.exports = function(config){
config.set({
basePath : '../../',
files : [
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
...
Then you can inject a $controller into your tests and do things like test your controller initialization. The test below is initializing the scope and then testing that the controller is adding methods to the scope (note, I am using Jasmine, but the same can be done with mocha) but Jasmine comes with some nice builtin spy capabilities.
describe('analysisController', function () {
var scope, state;
beforeEach(function () {
module('analysis');
scope = {
$apply:jasmine.createSpy(),
ws: {
registerCallback:jasmine.createSpy(),
sendMessage:jasmine.createSpy()
}
},
state = {
go : jasmine.createSpy()
};
});
it('should add a bunch of methods to the scope', inject(function ($controller) {
$controller('analysisController', {
$scope : scope,
$state: state
});
expect(typeof scope.colorContrast).toBe('function');
expect(typeof scope.XPathFromIssue).toBe('function');
}));
...

AngularAMD, Dynamic UI-Router, RequireJS - "Error: 'MyDashboardCtrl' is not a function, got undefined"

Intro:
I recognize that this is really deep and I won't probably get any help here but I have exhausted what seems like the entire Internet for a solution.
The short of it is I want to make my ui-router dynamic, and with that the controllers, WHILE USING REQUIREJS.
I know Dan Wahlin has something similar but he hard codes his controllers in his main.js file and I'd much prefer keep everything related to $state.routes come from a DB, ie. template & controller.
This is the error I am getting:
Error: [ng:areq] Argument 'MyDashboardCtrl' is not a function, got
undefined
http://errors.angularjs.org/1.2.9/ng/areq?p0=MyDashboardCtrl&p1=not%20aNaNunction%2C%20got%20undefined
I know the problem is that Dashboard controller is not being cached even though it is in the ui-router as the default route.
While the Dashboard.html is appearing, via the ui-view="container", the controller is just not being cached.
main.js:
require.config({
baseUrl: "/app",
paths: {
'app' : '/app/app',
'jquery': '/vendor/jquery/dist/jquery.min',
'angular': '/vendor/angular/angular',
'angular-bootstrap': '/vendor/angular-bootstrap/ui-bootstrap-tpls.min',
'angular-breeze': '/vendor/breeze.js.labs/breeze.angular',
'angular-cookies': '/vendor/angular-cookies/angular-cookies.min',
'angular-resource': '/vendor/angular-resource/angular-resource.min',
'angular-route': '/vendor/angular-route/angular-route.min',
'angular-sanitize': '/vendor/angular-sanitize/angular-sanitize.min',
'angular-touch': '/vendor/angular-touch/angular-touch.min',
'angular-ui-router':'/vendor/angular-ui-router/release/angular-ui-router.min',
'angular-ui-tree':'/vendor/angular-ui-tree/angular-ui-tree.min',
'angularAMD': '/vendor/angularAMD/angularAMD.min',
'bootstrap':'/vendor/bootstrap/dist/js/bootstrap.min',
'breeze':'/vendor/breeze/breeze.min',
'breeze-directives':'/vendor/breeze.js.labs/breeze.directives',
'highcharts':'/vendor/highcharts/highcharts',
'highchartsng':'/vendor/highcharts/highcharts-ng',
'moment': '/vendor/moment/min/moment.min',
'ngload': '/vendor/ng-load/ng-load',
'q': '/vendor/q/q',
'spin': '/vendor/javascripts/jquery.spin',
'toastr': '/vendor/toastr/toastr.min',
'tweenMax':'/vendor/greensock/src/minified/TweenMax.min',
'underscore':'/vendor/underscore/underscore.min'
},
// Add angular modules that does not support AMD out of the box, put it in a shim
shim: {
angular: {
exports: "angular"
},
'angularAMD': ['angular'],
'angular-ui-router':['angular'],
'highchartsng': {deps:['highcharts']},
'bootstrap': {deps:['jquery']},
'toastr': {deps:['jquery']}
},
// kick start application
deps: ['app'] });
app.js:
define(['angularAMD', 'angular-ui-router', 'config/common',
'layout/services/dBoardSvc', 'localize/LangSettings.Svc'],
function (angularAMD, common, dBoardSvc, LangSettingsSvc) {
var $stateProviderRef = null;
var $urlRouterProviderRef = null;
var app = angular.module("anuApp", ['ui.router'])
/* #ngInject */
.config(function ($locationProvider, $urlRouterProvider, $stateProvider) {
$urlRouterProviderRef = $urlRouterProvider;
$stateProviderRef = $stateProvider;
$locationProvider.html5Mode(false);
$urlRouterProviderRef.otherwise('/')
})
/* #ngInject */
.run(function($q, $rootScope, $state, $window, common, dBoardSvc, LangSettingsSvc){
// set initial current language
LangSettingsSvc.currentLang = LangSettingsSvc.languages[0];
dBoardSvc.all().success(function (data) {
var startUp = undefined;
angular.forEach(data, function (value) {
var tmp = 6;
angular.forEach(value.viewGroups, function (viewGroup, key) {
angular.forEach(viewGroup.viewStates, function (viewState, key) {
var viewStateUrl = undefined;
if (viewState.isStartUp == true && startUp == undefined) {
startUp = viewState.name;
}
if (viewState.url != '0') {
viewStateUrl = viewState.name + "/:moduleId";
}
var state = {
"url": viewStateUrl,
"parent": viewState.parentName,
"abstract": viewState.isAbstract,
"views": {}
};
angular.forEach(viewState.views, function (view) {
state.views[view.name] = {
controller: view.controllerName,
templateUrl: common.appRoot + view.templateUrl + '.html',
controllerUrl: common.appRoot + view.controllerUrl + '.js'
};
});
$stateProviderRef.state(viewState.name, angularAMD.route(state));
--------->>>>>>>>>>>>>>>>> **console.log(state);**
});
});
});
$state.go(startUp);
});
});
// Bootstrap Angular when DOM is ready
angularAMD.bootstrap(app);
return app; });
DashboardCtrl:
define(['app'], function (app) {
'use strict';
app.register.controller('MyDashboardCtrl', MyDashboardCtrl);
/* #ngInject */
function MyDashboardCtrl($scope){
$scope.pageTitle = 'MyDashboardCtrl';
};
});
Conclusion:
Could this be some time of resolve issue? I am literally grasping for straws here and any help would be appreciated.
The problem was with my controller, below is the correct approach:
define(['angularAMD'], function (app) {
'use strict'
/* #ngInject */
app.controller('MyDashboardCtrl', MyDashboardCtrl);
function MyDashboardCtrl($scope) {
$scope.pageTitle = 'This controller now loads dynamically';
};
// MyDashboardCtrl.$inject = ['$scope'];
});
NOTHING is hard coded in my app short of vendor files. My controllers, my views and my services are all dynamic & authorization based!!!
I hope this helps others because the implications of being able to utilize requirejs with dynamic routes means that not only does one :
Minimize the javaScript to only that which the view needs, but by coupling it with dynamic views & a login,
One can effectively hide all client side scripts that a respective user may not have authority to acccess.
I just wish examples were more than 'Hello World' these days ;(

angular mean.io developer prodution uglify error on minification

Can anyone explain me what's wrong with this code:
.controller('ArticleCreateCtrl', ['$scope', '$state', '$filter', 'Articles', function ($scope, $state, $filter, Articles) {
$scope.article = {};
$scope.save = function(){
$scope.article.categories = $filter('strcstoarray')($scope.article.categories);
Articles.store($scope.article).then(
function(data) {
$scope.article = data;
return $state.transitionTo('articles');
},
function(err) {
throw new Error(err);
}
);
};
}])
In the local machine works well when I run it
in heroku (prodution enviroment therefore with all the js minify)
I get :
Error: assignment to undeclared variable data
UPDATE (My service)
angular.module('mean.system')
.factory('Base',['Restangular', function(Restangular) {
return function(route){
var elements = Restangular.all(route);
return {
one : function (id) {
return Restangular.one(route, id).get();
},
all : function () {
return elements.getList();
},
store : function(data) {
return elements.post(data);
},
copy : function(original) {
return Restangular.copy(original);
},
getElements : function() {
return elements;
}
};
};
}]);
//Articles service used for articles REST endpoint
angular.module('mean.articles').factory('Articles', ['Base', function(Base) {
_.mixin({
'findByCategory': function(collection,category) {
return _.filter(collection, function(item) {
return _.contains(item.categories, category);
});
}
});
function Articles() {
this.findByCategory = function(collection,category){
return _.findByCategory(collection,category);
};
}
return angular.extend(Base('articles'), new Articles());
}]);
Make sure you have uglify configured properly in Gruntfile.js, with mangle: false
uglify: {
options: {
mangle: false
},
production: {
files: '<%= assets.js %>'
}
},
I had the same problem, it's because you must use the ngmin task, which prepares some angular libraries to be minified.
At package.json add the following line before uglify:
"grunt-ngmin": "0.0.3"
Then update dependencies:
npm install
Then at the Gruntfile.js added the ngmin task:
ngmin: {
production: {
files: '<%= assets.js %>'
}
},
Remember to add the ngmin task BEFORE uglify:
grunt.registerTask('default', ['clean','cssmin', 'ngmin','uglify', 'concurrent']);
The next time you'll run the server in production mode your code will work.

Resources