After trying some solutions like this: Aborting ngResource using a promise object I'm unable to cancel a request made with $resource.
My last try was with this:
Controller:
angular.module('theApp')
.controller('homeController', function ($q, foodTypeFactory) {
var vm = this;
vm.testButton = function () {
vm.aborter = $q.defer();
foodTypeFactory(vm.aborter).getTest({}, function (data) {
console.log(data);
});
};
vm.cancelButton = function () {
vm.aborter.resolve();
}
});
foodTypeFactory:
angular.module('theApp')
.factory('foodTypeFactory', function ($resource, BACKEND_API) {
return function (aborter) {
return $resource(BACKEND_API + '/api/foodtypes/:id', {id: '#id'}, {
getTest: {
timeout: aborter.promise
}
});
}
});
Once the request is made it completes even if I try to cancel it.
I'm using Angular 1.6.2 with angular-resource 1.6.2.
What am I doing wrong?
What i Can suggest to you is to use an http interceptor .. the you can stop a request... somthing like this:
1) create a file like (auth.interceptor.js:
"use strict";
angular
.module("demo")
.factory('authInterceptorService', ['$q', '$location', 'localStorageService',
function ($q, $location, localStorageService) {
// Public Method
return {
request: function (config) {
config.headers = config.headers || {};
if(!MYCONDITION){ //<-- you can here your logic to test if conitnue request flow or not
return; //<-- TERMINATE IT ..
}else{
return config; //<-- CONTINUE WITH NORMAL REQUEST
}
}
};
}]);
2) in your app.config.js file:
$httpProvider.interceptors.push("authInterceptorService");
Then in ALL your request (via $http or via $resource) this logic is apply ... here you can also put the injection of the Bearer Token if you need it
Hope it help you
Finally I found a solution!
From angular 1.5 $resource can be cancelled with $cancelRequest().
In my case:
Controller:
angular.module('theApp')
.controller('homeController', function (foodTypeFactory) {
var vm = this;
vm.testButton = function () {
vm.onGoingRequest = foodTypeFactory.getTest({}, function (data) {
console.log(data);
});
};
vm.cancelButton = function () {
vm.onGoingRequest.$cancelRequest();
}
});
foodTypeFactory:
angular.module('theApp')
.factory('foodTypeFactory', function ($resource, BACKEND_API) {
return $resource(BACKEND_API + '/api/foodtypes/:id', {id: '#id'}, {
getTest: {
cancellable: true
}
});
});
Related
In my provider's constructor I have something like:
constructor(
public http: Http
) {
this.http.get("api.php").toPromise().then(res=>this.res = res.json());
}
However, I only want this provider to be accessible only after this.http.get("api.php").toPromise() is resolved. Everything else should be the same as a normal provider, such as the way it is injected. Is it possible? How?
What I did with AngularJS :
initialize you attribute with var = $q.defer()
when you meet the requested value, use var.resolve(value)
And until you get the value, you can postpone treatments using var.promise.then(function (data) { ... })
Whole code of a service :
angular.module('myApp')
.service('myService', ['$http', '$q', function ($http, $q) {
s.myVar = $q.defer();
s.loadMyVar = function () {
$http({
method: 'GET',
url: "somewhere"
}).then(function successCallback(response) {
s.myVar.resolve(response.data);
});
};
s.getMyVar = function () {
return s.myVar.promise.then(function (data) {
return data;
});
};
return s;
}]);
I have tried to write a unit test case for post method in angular service. I got $http is undefined error. below is my code. any one tell me what i am missing.
i am adding module using separate file.
service code
sample.factory('AddProductTypeService', function () {
return {
exciteText: function (msg) {
return msg + '!!!'
},
saveProductType: function (productType) {
var result = $http({
url: "/Home/AddProductTypes",
method: "POST",
data: { productType: productType }
}).then(function (res) {
return res;
});
return result;
}
};
});
Jasmine
describe("AddProductTypeService UnitTests", function () {
var $rootScope, $scope, $factory, $httpBackend, basicService,createController, authRequestHandler;
beforeEach(function () {
module('sampleApp');
inject(function ($injector) {
basicService = $injector.get('AddProductTypeService');
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
});
});
// check to see if it does what it's supposed to do.
it('should make text exciting', function () {
var result = basicService.exciteText('bar');
expect(result).toEqual('bar!!!');
});
it('should invoke service with right paramaeters', function () {
$httpBackend.expectPOST('Home/AddProductTypes', {
"productType": "testUser"
}).respond({});
basicService.saveProductType('productType');
$httpBackend.flush();
});
});
error :
ReferenceError: $http is not defined
Thanks in advance
You have to inject the $http service into your service
sample.factory('AddProductTypeService', ['$http' ,function ($http) {
/* ... */
}]);
https://docs.angularjs.org/guide/di
So upfront, I am new to angular so I am a little lost with how I want to accomplish a redirect after I post data back to a server:
I have the following in a update:
$http.post("#Url.Action("SaveBranding", "AirlineConfig")", brandModel.model);
Then on the server I have this in my controller:
[HttpPost]
public ActionResult SaveBranding(BrandingViewModel viewModel)
{
if (IsModelStateValid())
{
var airline = GetAirlineFromAirlinePlatformId(viewModel.AirlinePlatformId);
switch (viewModel.PostAction)
{
case "Save":
BrandingViewModel.SaveEntity(viewModel, _db);
var airlineBranding = BrandingViewModel.FromEntity(_db.AirlinePlatforms.Single(x => x.AirlinePlatformId == viewModel.AirlinePlatformId).BrandingViews, viewModel.AirlinePlatformId);
return View("Branding", airlineBranding);
case "Save & Close":
BrandingViewModel.SaveEntity(viewModel, _db);
return RedirectToAction("Edit", "AirlineConfig", new { id = airline.AirlineId });
case "Cancel":
return RedirectToAction("Edit", "AirlineConfig", new { id = airline.AirlineId });
default:
return HttpNotFound();
}
}
return View("Branding"); //Replace this later
}
My routing isnt working and I am lost how to do this so I can navigate to the correct location.
Use window.location to manually redirect in the browser rather than use a server redirect.
The angular way to redirect is using $location service.
angular.module('someModule', [])
.controller('SomeController', ['$scope', '$http', '$location', someController])
function someController($http, $location) {
$scope.brandModel = {};
$scope.submit = function () {
$http.post("#Url.Action("SaveBranding", "AirlineConfig")", brandModel.model).then(function (data) {
$location.path('/url/to/path');
});
}
}
I put this answer here for completeness. I think also $location is more geared up for handling either hash urls or html5mode urls. If you use raw JavaScript, then you either use window.location.hash = "someUrl" or window.location.href = "someUrl". That could be a little caveat for not doing it the "angular" way.
I noticed also that you include that #Url.Action("", ""), when I did my Angular app with MVC in the index page I did this:
angular.module('someModule', [])
.factory('urlService', urlService)
function urlService() {
var service = {
getSaveBrandingUrl: getSaveBrandingUrl
};
return service;
function getSaveBrandingUrl() {
return '#Url.Action("", "")';
}
}
That way I can have all my other scripts separate, and they only rely on a function name so if you change the URL you don't have to go around the app changing all the links. When you inject this into the controller you would do something like:
angular.module('someModule', [])
.controller('SomeController', ['$scope', '$http', '$location', 'urlService', someController])
function someController($scope, $http, $location, urlService) {
$scope.brandModel = {};
$scope.submit = function () {
$http.post(urlService.getSaveBrandingUrl(), brandModel.model).then(function (data) {
$location.path('/url/to/path');
});
}
}
Obviously then you can tie all that up into it's own service to reduce the injection into the controller:
angular.module('someModule', [])
.factory('someControllerService', ['$http', 'urlService', someControllerService])
.controller('SomeController', ['$scope', '$location', 'someControllerService', someController])
function someController($scope, $location, someControllerService) {
$scope.brandModel = {};
$scope.submit = function () {
someControllerService.saveBranding($scope.brandModel.model).then(function (data) {
$location.path('some/url');
});
}
}
function someControllerService($http, urlService) {
var service = {
saveBranding: saveBranding
};
return service;
function saveBranding(branding) {
return $http.post(urlService.getSaveBrandingUrl(), brandModel.model).then(function (data) {
return data.data;
});
}
}
I need to do a request inside the RUN method to retrieve de user data from an api.
The first page (home), depends on the user data.
This is the sequence of dispatchs in my console:
CONFIG
RUN
INIT GET USER DATA
SIDEBAR
HOME
SUCCESS GET USER DATA
My problem is, i need to wait user data before call sidebar and home (controller and view) and i don't know how can i do this.
UPDATE
I have this until now:
MY CONFIG:
extranet.config(['$httpProvider', '$routeProvider', function ($httpProvider, $routeProvider) {
// My ROUTE CONFIG
console.log('CONFIG');
}]);
My RUN:
extranet.run(function($rootScope, $location, $http, Cookie, Auth, Session) {
console.log('RUN');
var token = Cookie.get('token');
// The login is done
var success = function (data) {
Session.create(data);
console.log('USER DATA SUCCESS');
};
var error = function () {
$location.path('/login');
};
// GET USER DATA
Auth.isAuthenticated().success(success).error(error);
});
MY CONTROLLER MAIN:
extranet.controller('MainCtrl', function ($scope, $location) {
console.log('MAIN CONTROLLER');
});
By using resolver
extranet.config(['$httpProvider', '$routeProvider', function ($httpProvider, $routeProvider) {
// My ROUTE CONFIG
$routeProvider.when('/', {
templateUrl: "/app/templates/sidebar.html",
controller: "siderbarController",
title: "EventList",
resolve: {
events: function ($q, Cookie,Session) {
var deffered = $q.defer();
Cookie.get('token').$promise
.then(function (events) {
Session.create(data);
console.log('USER DATA SUCCESS');
deffered.resolve(events);
}, function (status) {
deffered.reject(status);
});
return deffered.promise;
}
}
}]);
I hope you get some idea.
If you are using AngularJS methods for server requests you will get a promise. A promise gets resolved as soon as the response is recieved. All defined callbacks "wait" until the resolve.
Naive solution
So, you will use $http or even $resource if you have a REST-like backend:
var promise = $http.get(userDataUrl, params)
$rootScope.userDataPromise = promise;
After that you can use that promise whereever you need the data:
$rootScope.userDataPromise.then(myCallback)
Better solution
Using $rootScope for that purpose is not an elegant solution though. You should encapsulate the Userdata stuff in a service and inject it whereever you need it.
app.factory('UserData', ['$http',
function($http) {
var fetch = function() {
return $http.get(userDataUrl, params)
};
return {
fetch: fetch
};
}
]);
Now you can use that service in other modules:
app.controller('MainCtrl', ['$scope', 'UserService',
function ($scope, UserService) {
var update = function(response) {
$scope.userData = response.userData;
}
var promise = UserService.fetch();
promise.then(update)
}
);
Please go through the code first
app.js
var app = angular.module('Nimbus', ['ngRoute']);
route.js
app.config(function($routeProvider) {
$routeProvider
.when('/login', {
controller: 'LoginController',
templateUrl: 'templates/pages/login.html',
title: 'Login'
})
.when('/home', {
controller: 'HomeController',
templateUrl: 'templates/pages/home.html',
title: 'Dashboard'
})
.when('/stats', {
controller: 'StatsController',
templateUrl: 'templates/pages/stats.html',
title: 'Stats'
})
}).run( function($q, $rootScope, $location, $route, Auth) {
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
console.log("Started");
/* this line not working */
var canceler = $q.defer();
canceler.resolve();
});
$rootScope.$on("$routeChangeSuccess", function(currentRoute, previousRoute){
$rootScope.title = ($route.current.title) ? $route.current.title : 'Welcome';
});
})
home-controller.js
app.controller('HomeController',
function HomeController($scope, API) {
API.all(function(response){
console.log(response);
})
}
)
stats-controller.js
app.controller('StatsController',
function StatsController($scope, API) {
API.all(function(response){
console.log(response);
})
}
)
api.js
app.factory('API', ['$q','$http', function($q, $http) {
return {
all: function(callback) {
var canceler = $q.defer();
var apiurl = 'some_url'
$http.get(apiurl,{timeout: canceler.promise}).success(callback);
}
}
}]);
When I move from home to stats , again API will send http request, I have many http calls like this, I pasted only few lines of code.
What I need is I need to cancel abort all pending http requests on routechangestart or success
Or any other way to implement the same ?
I put together some conceptual code for this. It might need tweaking to fit your needs. There's a pendingRequests service that has an API for adding, getting and cancelling requests, a httpService that wraps $http and makes sure all requests are tracked.
By leveraging the $http config object (docs) we can get a way to cancel a pending request.
I've made a plnkr, but you're going to need quick fingers to see requests getting cancelled since the test-site I found typically responds within half a second, but you will see in the devtools network tab that requests do get cancelled. In your case, you would obviously trigger the cancelAll() call on the appropriate events from $routeProvider.
The controller is just there to demonstrate the concept.
DEMO
angular.module('app', [])
// This service keeps track of pending requests
.service('pendingRequests', function() {
var pending = [];
this.get = function() {
return pending;
};
this.add = function(request) {
pending.push(request);
};
this.remove = function(request) {
pending = _.filter(pending, function(p) {
return p.url !== request;
});
};
this.cancelAll = function() {
angular.forEach(pending, function(p) {
p.canceller.resolve();
});
pending.length = 0;
};
})
// This service wraps $http to make sure pending requests are tracked
.service('httpService', ['$http', '$q', 'pendingRequests', function($http, $q, pendingRequests) {
this.get = function(url) {
var canceller = $q.defer();
pendingRequests.add({
url: url,
canceller: canceller
});
//Request gets cancelled if the timeout-promise is resolved
var requestPromise = $http.get(url, { timeout: canceller.promise });
//Once a request has failed or succeeded, remove it from the pending list
requestPromise.finally(function() {
pendingRequests.remove(url);
});
return requestPromise;
}
}])
// The controller just helps generate requests and keep a visual track of pending ones
.controller('AppCtrl', ['$scope', 'httpService', 'pendingRequests', function($scope, httpService, pendingRequests) {
$scope.requests = [];
$scope.$watch(function() {
return pendingRequests.get();
}, function(pending) {
$scope.requests = pending;
})
var counter = 1;
$scope.addRequests = function() {
for (var i = 0, l = 9; i < l; i++) {
httpService.get('https://public.opencpu.org/ocpu/library/?foo=' + counter++);
}
};
$scope.cancelAll = function() {
pendingRequests.cancelAll();
}
}]);
You can use $http.pendingRequests to do that.
First, when you make request, do this:
var cancel = $q.defer();
var request = {
method: method,
url: requestUrl,
data: data,
timeout: cancel.promise, // cancel promise, standard thing in $http request
cancel: cancel // this is where we do our magic
};
$http(request).then(.....);
Now, we cancel all our pending requests in $routeChangeStart
$rootScope.$on('$routeChangeStart', function (event, next, current) {
$http.pendingRequests.forEach(function(request) {
if (request.cancel) {
request.cancel.resolve();
}
});
});
This way you can also 'protect' certain request from being cancelled by simply not providing 'cancel' field in request.
I think this is the best solution to abort requests. It's using an interceptor and $routeChangeSuccess event.
http://blog.xebia.com/cancelling-http-requests-for-fun-and-profit/
Please notice that im new with Angular so this may not be optimal.
Another solution could be:
on the $http request adding the "timeout" argument, Docs I did it this way:
In a factory where I call all my Rest services, have this logic.
module.factory('myactory', ['$http', '$q', function ($http, $q) {
var canceler = $q.defer();
var urlBase = '/api/blabla';
var factory = {};
factory.CANCEL_REQUESTS = function () {
canceler.resolve();
this.ENABLE_REQUESTS();
};
factory.ENABLE_REQUESTS = function () {
canceler = $q.defer();
};
factory.myMethod = function () {
return $http.get(urlBase, {timeout: canceler.promise});
};
factory.myOtherMethod= function () {
return $http.post(urlBase, {a:a, b:b}, {timeout: canceler.promise});
};
return factory;
}]);
and on the angular app configuration I have:
return angular.module('app', ['ngRoute', 'ngSanitize', 'app.controllers', 'app.factories',
'app.filters', 'app.directives', 'ui.bootstrap', 'ngGeolocation', 'ui.select' ])
.run(['$location', '$rootScope', 'myFactory', function($location, $rootScope, myFactory) {
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
myFactory.CANCEL_REQUESTS();
$rootScope.title = current.$$route.title;
});
}]);
This way it catches all the "route" changes and stops all the request configured with that "timer" so you can select what is critical for you.
I hope it helps to someone.
Regards