I've recently started to upgrade from Bootstrap 3 to Bootstrap 4, and that requires I also upgrade from AngularJS 1.5.8 to a minimum of AngularJS 1.6.1. I have a simple AngularJS/MVC application with the following code setup:
/scripts/app.js (contains routing)
/scripts/controllers.js (contains controllers, method calls, etc)
/scripts/services.js (contains the callback methods that hit the C# controllers and bring back data, or "do things" (CRUD methods)
In my controllers.js, I have a method:
function init() {
api.initialIndexLoad(
function(result) {
if (result.error) {
notificationService.danger("<h5>An error occurred.</h5><h6>Details: {0}</h6>".format(result.error));
} else {
vm.dataList = result.theDataFromCSharp;
}
vm.loaded = true;
}
);
}
init();
This makes a call to my services.js, where I have this code:
"use strict";
angular
.module("CustList.services", ["ngResource"])
.service("api",
function api($resource, $http, notificationService) {
function handleError(err, fn) {
if (!fn) {
notificationService.error(err);
} else {
fn(err);
}
}
return {
initialIndexLoad: function(callback, error) {
$http.get("/Customers/InitialIndexLoad")
.success(callback)
.error(function(e) {
handleError(e, error);
});
}
};
}
);
So, of course, after updating my libraries to AngularJS 1.6.1 (Actually, I went straight to AngularJS 1.7.5), I started to get errors, and after a while figured out that the promise syntax had changed. So I tried to change with it, and updated my services.js to be:
"use strict";
angular
.module("CustList.services", ["ngResource"])
.service("api",
function api($resource, $http, notificationService) {
function handleSuccess(response) {
return response.data;
}
function handleError(err, fn) {
if (!fn) {
notificationService.error(err);
} else {
fn(err);
}
}
return {
initialIndexLoad: function() {
$http
.get("/Customers/InitialIndexLoad")
.then(handleSuccess)
.catch(handleError);
}
};
}
);
The errors went away, and I thought I had this upgrade licked, until I realized: I wasn't actually getting the data back! That new handleSuccess method I'd created was getting the data in the response, but the return response.data wasn't returning the data back to my controllers.js method, so I could plug it into vm.dataList.
It doesn't throw an error - it just does nothing. I'd appreciate help figuring this out!
The service method needs to return the $http promise.
app.service("api",
function api($http, notificationService) {
function handleSuccess(response) {
return response.data;
}
function handleError(err) {
notificationService.error(err);
throw err;
}
return {
initialIndexLoad: function() {
̶$̶h̶t̶t̶p̶
return $http
.get("/Customers/InitialIndexLoad")
.then(handleSuccess)
.catch(handleError);
}
};
}
);
Notice that the errorHandler needs to re-throw the error. Otherwise the handler will convert the rejected promise to a fulfilled promise.
The controller needs to use the .then and .catch methods:
function init() {
var promise = api.initialIndexLoad();
promise.then(function(result) {
if (result.error) {
notificationService.danger("<h5>An error occurred.</h5><h6>Details: {0}</h6>".format(result.error));
} else {
vm.dataList = result.theDataFromCSharp;
}
vm.loaded = true;
}).catch(functions(err) {
console.log(err);
throw err;
});
}
The .then method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback (unless that value is a promise, in which case it is resolved with the value which is resolved in that promise using promise chaining.
For more information, see
AngularJS $q Service API Reference - The Promise API
You're Missing the Point of Promises
Return inside of initialIndexLoad
so
return $http
.get("/Customers/InitialIndexLoad")
.then(handleSuccess)
.catch(handleError);
Would guess this is it, maybe lost during refactor/upgrade.
Related
I am trying to delete a post from a list. The delete function is performing by passing serially to a delete function showed below.
$scope.go = function(ref) {
$http.get("api/phone_recev.php?id="+ref)
.success(function (data) { });
}
After performing the function, I need to reload the http.get request which used for listing the list.
$http.get("api/phone_accept.php")
.then(function (response) { });
Once the function performed. The entire list will reload with new updated list. Is there any way to do this thing.
Try this
$scope.go = function(ref) {
$http.get("api/phone_recev.php?id="+ref)
.success(function (data) {
//on success of first function it will call
$http.get("api/phone_accept.php")
.then(function (response) {
});
});
}
function list_data() {
$http.get("api/phone_accept.php")
.then(function (response) {
console.log('listing');
});
}
$scope.go = function(ref) {
$http.get("api/phone_recev.php?id="+ref)
.success(function (data) {
// call function to do listing
list_data();
});
}
Like what #sudheesh Singanamalla says by calling the same http.get request again inside function resolved my problem.
$scope.go = function(ref) {
$http.get("api/phone_recev.php?id="+ref).success(function (data) {
//same function goes here will solve the problem.
});}
});
You can use $q - A service that helps you run functions asynchronously, and use their return values (or exceptions) when they are done processing.
https://docs.angularjs.org/api/ng/service/$q
Inside some service.
app.factory('SomeService', function ($http, $q) {
return {
getData : function() {
// the $http API is based on the deferred/promise APIs exposed by the $q service
// so it returns a promise for us by default
return $http.get("api/phone_recev.php?id="+ref)
.then(function(response) {
if (typeof response.data === 'object') {
return response.data;
} else {
// invalid response
return $q.reject(response.data);
}
}, function(response) {
// something went wrong
return $q.reject(response.data);
});
}
};
});
function somewhere in controller
var makePromiseWithData = function() {
// This service's function returns a promise, but we'll deal with that shortly
SomeService.getData()
// then() called when gets back
.then(function(data) {
// promise fulfilled
// something
}, function(error) {
// promise rejected, could log the error with: console.log('error', error);
//some code
});
};
I am getting the reject is undefined error in my angular controller and I can not figure out why. This is the method. I added all libraries that are required.
Thank you in advance if you need more information let me know
function getUsers() {
return $http.get('../Admin/GetUsers/').then(function (response) {
return response.data;
}, function failureCallback(response) {
reject("Error!");
});
}
There is no reject defined in your code, so naturally trying to use it as a function will fail with a ReferenceError.
The promise returned by $http.get has already been rejected by the time your second handler has been called. Consequently, you probably don't want to handle rejections at all:
function getUsers() {
return $http.get('../Admin/GetUsers/').then(function (response) {
return response.data;
});
}
If you want to handle the rejection but still have the promise from getUsers rejected, you need to either throw in your handler or return a promise that is or will be rejected:
function getUsers() {
return $http.get('../Admin/GetUsers/').then(function (response) {
return response.data;
}, function failureCallback(err) { // Note it's an error, not a response
// Do something here, then
throw err;
});
}
If you are handling the failure and converting it into a success, you'd do that by returning a value to use (or a promise that will ultimately resolve with the value to use) instead of the value from get:
function getUsers() {
return $http.get('../Admin/GetUsers/').then(function (response) {
return response.data;
}, function failureCallback(err) { // Note it's an error, not a response
// Do something here, then
return valueToUseInstead;
});
}
...but I don't think that's what you're trying to do.
You can do it in two ways.Either entire method in controller or use service to return rejected value that you want.
function getUsers() {
var res=$http.get('../Admin/GetUsers/');
res.success(function (response) {
return response.data;
});
res.error(function(){
alert("failure");
});
}
Or
Controller.js:
function getUsers() {
userService.getAll().then(function (response) {
return response.data;
}, function(response) {
alert(response);
});
}
Service.js
this.getAll = function() {
var d = $q.defer();
res.success(function (data) {
d.resolve(data);
});
res.error(function(data){
d.reject(data);
});
}
Insert '$q' in service.
I've seen plunker. I don't understand what you searching for.
This is error free plunker.
Plunker
Thank you.
I'm trying to submit a form that require that the user email is not duplicated, but I want to make an small animation before the POST request. In the $scope.process function I'm getting:
TypeError: Cannot read property 'catch' of undefined.
That's happening because $scope.process is returning before the $http.post is complete, but how can I make process() return the promise instead of undefined?
So, this is what I have so far:
//The submit form function
$scope.submitForm = function () {
$('.btn').attr('disable');
if ($scope.form.$valid) {
$scope.process($scope.account)
.catch(function (err) {
if (err.code === 'duplicate') {
// handle error
}
});
return false;
}
};
//This is the one in charge to send the request
$scope.process = function(body) {
// Timeout before http post to wait for animation
$timeout(function() {
return $http.post(postUrl, body).then(function (response) {
// This return a promise if I remove the $timeout
var nextPage = response.data;
}).catch(function (err) {
throw err;
});
}, 300);
// Return undefined due to $timeout
};
Thanks in advance.
You were getting TypeError: Cannot read property 'catch' of undefined, because you weren't returning promise from process function at all.
Do return $timeout promise from process function & apply .then & .catch over $timeout promise object.
By returning $timeout service the inner $http.post will return a data, so that will make proper chaining mechanism.
Code
$scope.process = function(body) {
// returned promise from here
return $timeout(function() {
//returned $http promise from here.
return $http.post(postUrl, body).then(function (response) {
// This return a promise if I remove the $timeout
nextPage = response.data;
return nextPage; //return data from here will return data from promise.
}).catch(function (err) {
throw err;
});
}, 300);
};
I want two different controllers to run different functions after some promises are resolved in a service (i dont want this service to make an http request each time a controller needs the data, I only want one http request).
I have a service that makes a request and gets a promise. I want controller1 to see this resolution and then run some code. I then want controller2 to also see that this promise resolves and run some code (basically multiple then() methods that run on the same promise but from different files). How can I go about doing this?
All the examples I have seen have one controller running code after a certain promise resolves, but not multiple controllers listening for the same promise to resolve.
here is some code im borrowing from this article (ill add a 'mother controller' to illustrate my example, I dont want the son service to ever make his http call twice): http://andyshora.com/promises-angularjs-explained-as-cartoon.html
son service
app.factory('SonService', function ($http, $q) {
return {
getWeather: function() {
// the $http API is based on the deferred/promise APIs exposed by the $q service
// so it returns a promise for us by default
return $http.get('http://fishing-weather-api.com/sunday/afternoon')
.then(function(response) {
if (typeof response.data === 'object') {
return response.data;
} else {
// invalid response
return $q.reject(response.data);
}
}, function(response) {
// something went wrong
return $q.reject(response.data);
});
}
};
});
father Controller:
// function somewhere in father-controller.js
var makePromiseWithSon = function() {
// This service's function returns a promise, but we'll deal with that shortly
SonService.getWeather()
// then() called when son gets back
.then(function(data) {
// promise fulfilled
if (data.forecast==='good') {
prepareFishingTrip();
} else {
prepareSundayRoastDinner();
}
}, function(error) {
// promise rejected, could log the error with: console.log('error', error);
prepareSundayRoastDinner();
});
};
Mother Controller:
var makePromiseWithSon = function() {
SonService.getWeather()
// then() called when son gets back
.then(function(data) {
// promise fulfilled
if (data.forecast==='good') {
workInTheGarden();
} else {
sweepTheHouse();
}
}, function(error) {
// promise rejected, could log the error with: console.log('error', error);
sweepTheHouse();
});
};
To have your factory service only get the url once, store the httpPromise in your factory service.
app.factory('SonService', function ($http) {
var weatherPromise;
function getWeather() {
return $http.get('http://fishing-weather-api.com/sunday/afternoon')
.then(function(response) {
if (typeof response.data === 'object') {
return response.data;
} else {
// invalid response
throw response;
}
}, function(response) {
// something went wrong
throw response;
});
}
function sonService() {
if (!weatherPromise) {
//save the httpPromise
weatherPromise = getWeather();
}
return weatherPromise;
}
return sonService;
});
The simple answer, in a non-angular-specific (but really easy to apply to Angular) way, is to create a service which caches ON-OUTBOUND-REQUEST (rather than caching return values, like most systems would).
function SearchService (fetch) {
var cache = { };
return {
getSpecificThing: function (uri) {
var cachedSearch = cache[uri];
if (!cachedSearch) {
cachedSearch = fetch(uri).then(prepareData);
cache[uri] = cachedSearch;
}
return cachedSearch;
}
};
}
function A (searchService) {
var a = this;
Object.assign(a, {
load: function ( ) {
searchService.getSpecificThing("/abc").then(a.init.bind(a));
},
init: function (data) { /* ... */ }
});
}
function B (searchService) {
var b = this;
Object.assign(b, {
load: function ( ) {
searchService.getSpecificThing("/abc").then(b.init.bind(b));
},
init: function (data) { /* ... */ }
});
}
var searchService = SearchService(fetch);
var a = new A(searchService);
var b = new B(searchService);
a.load().then(/* is initialized */);
b.load().then(/* is initialized */);
They're sharing the same promise, because the service they were talking to cached and returned the same promise.
If you wanted to be safe, you could cache a promise and then return new instances of promises which resolve (or reject) based on the cached promise.
// instead of
return cachedSearch;
// replace it with
return Promise.resolve(cachedSearch);
Each user is now getting a new instance, every time you make a request, but each instance is also passing or failing based on the original cached call.
And of course you can take it further, and put a time-limit on the cache, or have hooks to invalidate the cache, or whatever...
Converting this to Angular is also a snap
SearchService is a service
A and B are controllers
use $http instead of fetch (though fetch is really pretty)
in fetch( ).then(prepareData) you'd be converting data from JSON on success;
in $http, you'd be returning response.data because your users don't want to have to do that
either way, you're performing that operation exactly once, per outbound call, so cache it, too
use $q (and q methods) instead of native Promise
use angular.extend, instead of Object.assign
You're done; you've now ported that whole concept into Angular AND VanillaJS
I am developing a file reading service that look like this:
angular.factory('fileService', fileService);
function fileService($cordovaFile){
var service = {
readFile: readFile
};
return service;
///////////////
function readFile(path, file){
$cordovaFile.readAsText(path, file)
.then(function (success) {
console.log("read file success");
console.log(success);
return success;
}, function (error) {
alert("Fail to read file:"+error);
console.log("Fail to read file");
console.log(error);
return false;
});
}
}
And then using it like this:
var data = fileService.readFile(cordova.file.dataDirectory,filename);
console.log(data) //return undefined
The problem is it fail to return the data. How can I get the data return back?
Your problem is that you are not actually returning any result from the readFile function. You are returning data from your callback functions but if you come to think of it...that result is returned to the function readFile itself and it stays inside that function. What you would want to do is return the whole result of the function readFile and then resolve the promise in the controller where you use it. Here is the code:
angular.factory('fileService', fileService);
function fileService($cordovaFile){
var service = {
readFile: readFile
};
return service;
function readFile(path, file){
return $cordovaFile.readAsText(path, file);
}
}
And then you use it like this:
var data = fileService.readFile(cordova.file.dataDirectory,filename);
data.then(function (success) {
// Do whatever you need to do with the result
}, function (error) {
/// Handle errors
});
In general, when you use services to implement some kind of functionality that uses promises and returns result, you should always return the promise object which can be than resolved anywhere that it is needed.
I highly recommend that you read this great explanation for promise objects.
Your function readFile returns nothing, so, firstly you should be returning the promise:
function readFile(path, file) {
return
$cordovaFile.readAsText(path, file).then(function (success) {
console.log('Read file success');
console.log(success);
return success;
}, function (error) {
alert('Fail to read file: ' + error);
console.log('Fail to read file');
console.log(error);
return false;
});
}
And then, if you try to use it the way you were, you'll not get undefined anymore, you'll get a promise.
But since it's an async method, you'll get that promise still pending, and you probably don't want that, since you'll need the promise's fulfilled value. So, you should use it like this:
fileService.readFile(cordova.file.dataDirectory, filename).then(function(data) {
// use data here
});