AngularJS Service Config value get destroyed on minification - angularjs

Having some trouble with minification and AngularJS ;-(
I found this jsfiddle "loading" extender for HTTP request, through the AngularJS Wiki page.
It worked great until i published it, and the minification destroys it.
I can't find a way to use "inject" on the config, so im kinda lost about what to do.
Original code:
angular.module("app.services", []).config(function($httpProvider) {
var spinnerFunction;
$httpProvider.responseInterceptors.push("myHttpInterceptor");
spinnerFunction = function(data, headersGetter) {
$("#loader").show();
return data;
};
return $httpProvider.defaults.transformRequest.push(spinnerFunction);
}).factory("myHttpInterceptor", function($q, $window) {
return function(promise) {
return promise.then((function(response) {
$("#loader").hide();
return response;
}), function(response) {
$("#loader").hide();
return $q.reject(response);
});
};
});
Minified code:
angular.module("app.services", []).config(function (a) {
var b;
a.responseInterceptors.push("myHttpInterceptor");
b = function (d, c) {
$("#loader").show();
return d
};
return a.defaults.transformRequest.push(b)
}).factory("myHttpInterceptor", function (a, b) {
return function (c) {
return c.then((function (d) {
$("#loader").hide();
return d
}), function (d) {
$("#loader").hide();
return a.reject(d)
})
}
});
Which throws the following error:
Error: Unknown provider: a from app.services

Use inline annotation for defining providers:
angular.module("app.services", [])
.config(
[
'$httpProvider',
'myHttpInterceptor',
function($httpProvider, myHttpInterceptor) {
var spinnerFunction;
$httpProvider.responseInterceptors.push(myHttpInterceptor);
spinnerFunction = function(data, headersGetter) {
$("#loader").show();
return data;
};
return $httpProvider.defaults.transformRequest.push(spinnerFunction);
}
]
);
And, btw, you should reconsider using jQuery calls inside your configs and factories. Direct DOM manipulation should be handled inside the directives.
For your case, instead of $("#loader").show(); and $("#loader").show(); you should broadcast an event (e.g. $rootScope.$broadcast('loader_show')), and then listen for that event in your custom 'spinner' directive:
HTML:
<div spinner class="loader"></div>
JS:
app.directive('spinner',
function() {
return function ($scope, element, attrs) {
$scope.$on('loader_show', function(){
element.show();
});
$scope.$on('loader_hide', function(){
element.hide();
});
};
}
);

Just for others in the same situation... I followed #Stewie 's advice, and made this instead, which only uses AngularJS code, no stupid jQuery dependency ;-)
Service:
app.config([
"$httpProvider", function($httpProvider) {
var spinnerFunction;
$httpProvider.responseInterceptors.push("myHttpInterceptor");
spinnerFunction = function(data, headersGetter) {
return data;
};
return $httpProvider.defaults.transformRequest.push(spinnerFunction);
}
]).factory("myHttpInterceptor", [
"$q", "$window", "$rootScope", function($q, $window, $rootScope) {
return function(promise) {
$rootScope.$broadcast("loader_show");
return promise.then((function(response) {
$rootScope.$broadcast("loader_hide");
return response;
}), function(response) {
$rootScope.$broadcast("loader_hide");
$rootScope.network_error = true;
return $q.reject(response);
});
};
}
]);
Directive
app.directive("spinner", function() {
return function($scope, element, attrs) {
$scope.$on("loader_show", function() {
return element.removeClass("hide");
});
return $scope.$on("loader_hide", function() {
return element.addClass("hide");
});
};
});

As strange as it might seem, you can also use inline annotation where you do the actual .push() to inject your dependencies on $q and $window i.e. instead of pusing a function() into $httpProvider.responseInterceptors you push an array:
app.config(["$httpProvider", function($httpProvider) {
$httpProvider.responseInterceptors.push(['$q', '$window', function($q, $window) {
return function(promise) {
return promise.then(function(response) {
$("#loader").hide();
return response;
},
function(response) {
$("#loader").hide();
return $q.reject(response);
});
};
}]);
}]);

Related

Service making the $scope not accessible on the html

Can't access the scope, for example putting {{ pagec }} on the html isn't working but when I remove blogpostservice from the controller it works fine again.
var app = angular.module('Blog', []);
app.factory('blogpostservice', ['$http', function ($http) {
this.getMoreData = function (pagecount) {
return $http.get('/api/posts/' + pagecount);
}
}]);
app.controller('MainController', ['$scope', 'blogpostservice',
function ($scope, blogpostservice) {
$scope.pagec = 1;
$scope.posts = [];
this.getMoreData = function (posts) {
blogpostservice.getMoreData(pagec).success(function () {
alert('got it successfully!!!');
}).error(function () {
alert('something went wrong!!!');
});
}
}]);
Because you had wrong factory implementation, factory should always return an object. You must have got an error in console(please check).
app.factory('blogpostservice', ['$http',
function ($http) {
function getMoreData (pagecount) {
return $http.get('/api/posts/' + pagecount);
}
return {
getMoreData: getMoreData
}
}
]);
Or you can convert your factory to service, there you need to bind data to this(context) like your were doing before.
app.service('blogpostservice', ['$http', function ($http) {
this.getMoreData = function (pagecount) {
return $http.get('/api/posts/' + pagecount);
}
}]);
Also don't use .success/.error on $http call, they are
deprecated. Instead use .then.

Error: $injector:unpr Unknown Provider, while calling service in controller in angular js

I am trying to use service in my controller and it is giving
Error: $injector:unpr
Unknown Provider
Unknown provider: getAuthorProvider <- getAuthor <- PostController
Here is what I am doing:
app.js
angular.module('app',[
'ui.router',
'app.controllers'
])
.config(['$stateProvider','$urlRouterProvider','$locationProvider',function($stateProvider,$urlRouterProvider,$locationProvider) {
$stateProvider.state('homePage',{
url: '/',
views: {
'mainView': {
templateUrl: 'views/home.html',
controller: 'PostController'
}
},
});
$locationProvider.html5Mode(true);
$urlRouterProvider.otherwise('/');
}]);
controller.js
angular.module('app.controllers',[
'app.directives'
]).controller('PostController', ['$scope', '$http','getAuthor' , function($scope,$http,getAuthor) {
$http({
method: 'GET',
url: '/api/blog'
}).then(function (response){
$scope.currentPage=1;
$scope.pageSize=10;
//console.log(response);
$scope.posts = response.data;
console.log($scope.posts);
$scope.pageinput = $scope.currentPage;
$scope.authorName = function(authorId) {
getAuthor.getAuthorNameById(authorId,function(response){
console.log(response.data);
//return response.data;
}, function(response){
alert('Some errors occurred while communicating with the service. Try again later.');
});
};
$scope.numberOfPages = function() {
return Math.ceil($scope.posts.length / $scope.pageSize);
};
$scope.setCurrentPage = function(newValue) {
$scope.currentPage = newValue;
$scope.pageinput = newValue;
$scope.$apply();
};
$scope.submitFunc = function() {
var temp = $scope.currentPage;
if($scope.pageinput >=0 && $scope.pageinput <= $scope.numberOfPages()) {
$scope.currentPage = $scope.pageinput;
$scope.CurrentPage = $scope.currentPage;
setTimeout(window.scrollTo(0,150),2000);
}else {
}
}
},function(error){
console.log('failed');
});
}]);
service.js
angular.module('app.factories',[
'LocalStorageModule'
]).factory('getAuthor',['Restangular',function(Restangular) {
function getAuthorNameById(authorId,onSuccess,onError) {
Restangular.one('api/author/name',authorId).get().then(function(response){
onSuccess(response);
}, function(response) {
onError(response);
});
}
}]);
I am calling the function authorName() in my HTML file as:
{{authorName(2)}}
as this function should take authorId and return his name. I am not sure where I am getting it wrong and now I am not getting any idea how to set it perfectly.
it seems you did not include app.factories as a dependency as reported by #charlietfl
in addition you're missing the return statement inside the factory, you're only declaring the function, this could be related to the error.
angular.module('app.factories',[
'LocalStorageModule'
]).factory('getAuthor',['Restangular',function(Restangular) {
function getAuthorNameById(authorId,onSuccess,onError) {
Restangular.one('api/author/name',authorId).get().then(function(response){
onSuccess(response);
}, function(response) {
onError(response);
});
}
return {
getAuthorNameById: getAuthorNameById
}
}]);
And move the invocation of authorName() inside your controller.
When you interpolate a function call (as you did in your template, with this syntax {{authorName(2)}}) the function will be called each digest cycle.
To answer your question in the comments i'd follow this approach.
.directive('handleItem', function(getAuthor){
return {
restrict: 'E',
scope: {
item: '='
}
link: function(scope, elem, attrs){
//make the call here
getAuthor
.getAuthorNameById($scope.item.authorId,function(response){
console.log(response.data);
//return response.data;
}, function(response){
alert('Some errors occurred while communicating with the service.
Try again later.');
});
}
}
})
and in the template something like this:
<div ng-repeat="item in items"> <!--loop over your items-->
<handle-item item="item"></handle-item>
</div>
It doesn't appear that module app.factories is injected in any of the other modules.
Try:
angular.module('app',[
'ui.router',
'app.controllers',
'app.factories'
])
Also doesn't appear that Restangular module is injected anywhere either

Unit test controller

I make an ionic app and it finish but when i start to add tests to it I face a problem with $resources ,in this case I have this Controller :
.controller('newAccountCtrl', function($scope, $window, $rootScope, API, $ionicPopup, $state) {
$scope.newData = {};
$scope.$on('$ionicView.enter', function() {
$scope.newData = {};
});
$scope.newInfo = function() {
API.newAccountInfo().update({ restCode: $scope.newData.restore_code }, $scope.newData, function(res, header) {
$rootScope.popup('success', "OKAY");
$window.location.href = '#/login';
}, function(err) {
if (err.data == null)
$rootScope.popup("Error", "no connection");
else
$rootScope.popup('error', err.data.error);
});
}
})
and in the service i make a request using $resources in function :
angular.module('starter.services', [])
.factory('API', function($rootScope, $resource, $ionicPopup, $ionicLoading, $window) { return {
newAccountInfo: function() {
return $resource(base + '/restoreinfo/:restCode', { restCode: '#_restCode' }, {
update: {
method: 'PUT'
}
}, {
stripTrailingSlashes: false
});
}}});
and in the my test the following code:
describe('newAccountCtrl', function() {
var controller,
deferredLogup, scope, $q;
beforeEach(angular.mock.module('starter'));
// TODO: Load the App Module
beforeEach(module('starter.controllers'));
beforeEach(module('starter.services'));
// TODO: Instantiate the Controller and Mocks
beforeEach(inject(function($controller, _$q_, $rootScope, _API_) {
$q = _$q_;
scope = $rootScope.$new();
API = _API_;
spyOn(API, 'newAccountInfo').and.callThrough(function(callback) {
deferredLogup.promise.then(callback);
return { $promise: deferredLogup.promise };
});
controller = $controller('newAccountCtrl', {
'$scope': scope,
API: API
});
}));
it('#newAccountInfo', function() {
scope.newInfo();
expect(API.newAccountInfo.update).toHaveBeenCalled();
}) });
but I get the error :
Expected a spy, but got undefined.
What I misunderstand here, the code work perfect
just macke factory return the resources direct and remove the functions .

How to call a controller function from a service?

i have 2 controllers who are not in the same scope or have a parent child relation.
So i want to call from controlleB a function in ControllerA. In my case its a listContoller with an addItem function and i want to call this function from a addItemController somewhere else on the page after clicking submit. i know this should work with a service, but i dont know how.
app.controller("listCtrl", ["$scope", "listSvc", function ($scope, listSvc){
$scope.list.data = listSvc.load("category");
$scope.addItem = function(newitem) {
$scope.list.data.unshift(newitem);
...
}
}]);
app.controller("addItemCrtl", ["$scope", "listSvc", function ($scope, listSvc){
$scope.addItem = function() {
listSvc.addItem($scope.newItem);
}
}]);
app.service('listSvc', function() {
return{
load: function(section){
...
},
addItem: function(item){
addItem(item); <<-- call function in listController
}
}
});
UPDATE
k is this better? i put the list.data inside my service and i watch from my controller if the list change and put it on the scope from my controller that ng-repeat can do his work... is this appraoch better? or have someone better tips for me how i should do this...
app.service('listSvc', ['$http', function($http) {
var list = {};
return {
list:{
get: function () {
return list.data;
},
set: function (data) {
list.data = data;
}
},
addItem: function(item){
var response = $http.post("/api/album/"+$scope.list.section, item);
response.success(function(){
list.data.unshift(item);
console.log("yeah success added item");
}).error(function(){
console.log("buuuh something went wrong");
});
return response;
},
load: function(section){
var response = $http.get("/api/album/"+section);
response.success(function(data){
list.set(data);
list.section = section;
console.log("yeah success loaded list");
}).error(function(){
console.log("buuuh something went wrong");
});
return response;
}
};
}]);
and in my controllers i do this
app.controller("listCrtl", ["$scope", "listSvc", function ($scope, listSvc){
listSvc.load("category");
...
$scope.$watch('listSvc.list.get()', function(data) {
$scope.list.data = data;
});
...
}]);
app.controller("addItemCrtl", ["$scope", "listSvc", function ($scope, listSvc){
...
$scope.addItem = function() {
listSvc.addItem($scope.newItem);
}
...
}]);
gregor ;)
I just solved this myself! Perhaps this may help:
The function inside of my Controller:
var timeoutMsg = function() {
vm.$parent.notification = false;
};
The function inside my Service (I had to pass in $timeout as well as the name of the function from my Controller, now it works):
// original broken code:
// this.modalSend = function(vm) {
// fixed:
this.modalSend = function(vm, $timeout, timeoutMsg) {
vm.$parent.sendTransaction = function() {
// Show notification
vm.$parent.message = 'Transaction sent!';
vm.$parent.notification = true;
$timeout(timeoutMsg, 4000);
// original broken code:
// $timeout(timeoutMsg(), 4000);
};
}
var vm = $scope

Angular.js share service data between directives

I'm working on my first Angular.js application and I'm a bit confused.
Currently I have two directives that both need the same data to build up the page.
This data is loaded from an external api.
Now currently I have created this factory, which looks like:
(function() {
var app = angular.module('dataService', []);
app.factory('dataService', ['$http', function($http) {
var links = [];
return {
getMenu: function() {
if(links.length > 0) {
return links;
} else {
$http.get('http://localhost/server/api.php?ajax=true&action=getCats').success(function(data) {
return data;
})
}
}
}
}])
})();
But I'm rather confused how to use this service, obviously if there is a $http request, the return will never be called with the correct data.
In my directive I would use it like this:
(function() {
// Menu directive
var app = angular.module('menu', ['dataService']);
app.directive('menu', ['dataService', function(dataService) {
return {
restrict: 'E',
templateUrl: 'scripts/menu/menu.html',
controller: function() {
console.log(dataService.getMenu()); // Return 'undefined'
},
controllerAs: 'menuCtrl'
}
}])
})();
Change your service method so that it handles both synchronous and asynchronous scenarios:
getMenu: function() {
var deferred = $q.defer();
if(links.length > 0) {
deferred.resolve(links);
} else {
$http.get('http://localhost/server/api.php?ajax=true&action=getCats').success(function(data) {
deferred.resolve(data);
})
}
return deferred.promise;
}
Usage:
dataService.getMenu().then(function(data){
console.log(data);
});

Resources