I'm new to AngularJS and Breeze. I'm trying to save changes and have a problem with that. Here's my code:
In controller:
function update() {
vm.loading = true;
return datacontext.saveSettings().then(function () {
vm.loading = false; // never gets called
}).fail(function (data) {
vm.loading = false; // never gets called
});
}
In datacontext:
function saveSettings() {
if (manager.hasChanges()) {
manager.saveChanges() // breaks here if there are changes
.then(saveSucceeded)
.fail(saveFailed)
.catch(saveFailed);
} else {
log("Nothing to save");
return false;
};
}
The error is thrown in angular.js and it's very unhelpful TypeError: undefined is not a function I guess there is something simple I'm missing here, but can't figure out what is it.
Want to note that it does send correct data to SaveChanges method on server, but the error is thrown before any response from the server received. After the response is received it throws another error TypeError: Cannot read property 'map' of undefined but it might be related to the fact the response I return is invalid. I haven't got to that part yet.
Can anyone anyone help with it? I'm lost here.
UPDATE
Here is how I construct my dataservice and manager:
var serviceName = "http://admin.localhost:33333/api/breeze/"; //
var ds = new breeze.DataService({
serviceName: serviceName,
hasServerMetadata: false,
useJsonp: true,
jsonResultsAdapter: jsonResultsAdapter
});
var manager = new breeze.EntityManager({ dataService: ds });
model.initialize(manager.metadataStore);
Two problems:
Your datacontext method does not return a promise so the caller cannot find anything to hang the then or fail call on.
You should be callingcatch, not fail
1. Return a promise
Your saveSettings method did not return a result in the success case so it must fail. Your method must also return a promise in the fail case ... or it will also fail.
And while I'm here, since there is no real difference between your success and fail case, you might as well move the vm.loading toggle to the finally clause.
Try this instead:
function update() {
vm.loading = true;
return datacontext.saveSettings()
.then(function () {
// .. success handling if you have any
})
.catch(function (data) {
// .. fail handling if you have any
})
.finally(funtion() {
vm.loading = false; // turn it off regardless
});
}
And now the dataContext ... notice the two return statements return a promise.
function saveSettings() {
if (manager.hasChanges()) {
return manager.saveChanges()
.then(saveSucceeded)
.catch(saveFailed);
} else {
log("Nothing to save");
// WHY ARE YOU FAILING WHEN THERE IS NOTHING TO SAVE?
// Breeze will handle this gracefully without your help
return breeze.Q.reject(new Error('Nothing to save'));
};
}
2. Use catch
I assume you have configured Breeze to use Angular's $q for promises (you should be using the "breeze.angular" service and have injected "breeze" somewhere).
$q does not have a fail method! The equivalent is catch. For some reason you have both attached to your query. You'll get the ReferenceError exception immediately, before the server has a chance to respond ... although it will launch the request and you will get a callback from the server too.
Try just:
return manager.saveChanges()
.then(saveSucceeded)
.catch(saveFailed);
You see many Breeze examples that call fail and fin. These are "Q.js" methods; "Q.js" is an alternative promise library - one used by Breeze/KnockOut apps and it was the basis for Angular's $q.
Both "Q.js" and $q support the now-standard catch and finally promise methods. We're slowly migrating our example code to this standard. There is a lot of old fail/finally code around in different venues. It will take time.
Sorry for the confusion.
Update savesetting function like below to return Promise.
function saveSettings() {
if (manager.hasChanges()) {
return manager.saveChanges(); // return promise
} else {
log("Nothing to save");
return false;
};
}
Then you can call then and fail in update function like following.
function update() {
vm.loading = true;
return datacontext.saveSettings().then(function () {
vm.loading = false; // never gets called
}).fail(function (data) {
vm.loading = false; // never gets called
});
}
Related
The code below is contrived. I'm simplified things as best I could to ask the question.
I have a simple angular service that makes an API call and returns results:
doWork = function(reqId) {
return $http.get('/api/dowork/' + reqId).then(function(response) {
return response.data;
}).catch(function(response) {
return $q.reject(response.data);
});
}
mediumRequest = function() {
var req = 'medium';//normally do something hard to derive this value
return this.doWork(req);
}
In my controller, I can call the doWork function on the service and get back a good response like this:
myService.doWork('simple').then(function(response){
//do something great with response
});
However, if I need to call an intermediate method to preprocess the request, I get "Cannot read property 'then' of undefined":
myService.mediumRequest().then(function(response){
//do something great with response
});
Why doesn't the function mediumRequest return the promise that doWork returned to it?
Try this code , you did wrong in your service
var app = angular.module("myApp", [])
.service('myService',function($http,$q){
this.doWork = function(reqId) {
return $http.get('/api/dowork/'+ reqId).then(function(response) {
return response.data;
}).catch(function(response) {
return $q.reject(response.data);
});
};
this.mediumRequest = function() {
var req = 'medium';//normally do something hard to derive this value
return this.doWork(req);
};
})
app.controller("myCtrl", function($scope,$compile,myService) {
myService.doWork('simple').then(function(response){
console.log('b',response)
});
myService.mediumRequest().then(function(response){
console.log('a',response)
});
})
It will work
As with most things, the problem was a coding error in my actual service code. The code I've presented in this question would work as expected. I appreciate the couple of folks who offered suggestions of ways to identify the issue. Here was my issue:
My actual "intermediate" function had this structure:
mediumRequest = function(options) {
//process first option meeting criteria
options.forEach(function (item) {
if(item.meetsCriteria)
{
var req = item.code;
return this.doWork(req);
}
});
}
As you can see, the return is actually just exiting the forEach and never actually being returned from the mediumRequest function. Hence the error.
I have a separate functions like, submit,update,delete. for all function
$http[method](url)
.then(function(response) {
$scope.successMessage = true;
} , function(response) {
$scope.errorMessageWrong=true;
});
html
<p ng-show="successMessage">Success</p>
<p ng-show="errorMessageWrong"> Something went wrong </p>
For separate functionalities. i need to show the corresponding messages. but i don't want to repeat the code for update, delete and submit and even the same thing for the other pages which do the same operation.
how to create function called errorHandler or something. so that i can reuse it.
can anyone help me
how to create function called errorHandler or something. so that i can reuse it.
Create chainable promises by returning for fulfilled data responses and throwing rejected error responses.
The example function below takes an httpPromise as an argument, puts success or error messages on $scope, and returns a promise suitable for chaining.
function errorHandler(httpPromise) {
var derivedPromise = httpPromise
.then(function onFulfilled(response) {
$scope.successMessage = true;
//return response for chaining
return response;
},
function onRejected(errorResponse) {
$scope.errorMessageWrong = true;
//throw error to chain rejection
throw errorResponse;
});
//return derivedPromise for chaining
return derivedResponse;
};
Then in client code:
var httpPromise = $http[method](url);
errorHandler(httpPromise).then( function (response) {
//use data
});
The client code saves the httpPromise from the $http service call, processes the promise with the errorHandler function, and the uses the derived promise returned by the errorHandler function.
Because calling the then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1
If you want it global to your app, then you can use an httpInterceptor.
You have to create an interceptor service and then add the interceptor to the $httpProvider in your app.config().
Create your interceptor service:
angular.module('app').factory('myInterceptorService', myInterceptorService);
function myInterceptorService($q){
var errorMessage;
var bShowHideWatchFlag;
return{
requestError: requestError,
responseError: responseError,
showFlag: bShowFlag,
errorMessage: errorMessage
};
function requestError(rejection){
errorMesasge = 'Request error';
bShowHideWatchFlag = true;
$q.reject(rejection);
return;
}
function responseError(rejection){
errorMesasge = 'Response error';
bShowHideWatchFlag = true;
$q.reject(rejection);
return;
}
}
To register with app config, add $httpProvider to app.config
app.config([...,'$httpProvider'...){
$httpProvider.interceptor.push('myInterceptorService');
}
In your controller, you have to bind a watch to the service showFlag:
$scope.$watch( function () { return myInterceptorService.showFlag; },
function (oldval,newval) {
if( oldval!=newval){
$scope.errorMessage = myInterceptorService.errroMessage;
$scope.showMessage = newval;
}
}, true);
You can use service for this and share within various controller. $http is also a service. But if you want to add something more to the data you can create a new service and inject $http to it.
React/Redux n00b here :) - working with a crappy API that doesn't correctly return error codes (returns 200 even when end point is down), therefore is messing up my Ajax calls. Owner of the API will not able to correct this soon enough, so I have to work around it for now.
I'm currently checking each success with something like this (using lodash):
success: function(data) {
// check if error is REALLY an error
if (_.isUndefined(data.error) || _.isNull(data.error)) {
if (data.id) data.sessionId = data.id;
if (data.alias) data.alias = data.alias;
resolve(data || {});
} else {
reject(data); // this is an error
}
}
I want to move this into it's own function so that I can use it with any action that performs an Ajax call, but I'm not sure where to include this.
Should this type of function map to state (hence, treat it like an action and build a reducer) or should this be something generic outside of Redux and throw in main.js for example?
You can dispatch different actions depending on if the promise was resolved or not. The simplest way would be something like this:
function onSuccess(data) {
return {
type: "FETCH_THING_SUCCESS",
thing: data
};
}
function onError(error) {
return {
type: "FETCH_THING_ERROR",
error: error
};
}
function fetchThing(dispatch, id) {
// the ajax call that returns the promise
fetcher(id)
.then(function(data){
dispatch(onSuccess(data));
})
.catch(function(error) {
dispatch(onError(error));
});
}
Heres some more documentation how to do this kind of thing...
I'm having trouble understanding a basic concept of error handling with chaining promises.
In order to learn the rules, I have written a simple example, guessing what the result will be. But unfortunatly it doesn't behave as I though it will.
I have read multiple articles about the subject but perhaps can't I get details because of my poor english language.
Anyway, here is my code :
var promiseStart = $q.when("start");
var promise1 = promiseStart.then(function() {
return Serviceforpromise1.get();
});
var promise2 = promise1.then(function(data1)
{
return Serviceforpromise2.get(data1);
},function(error)
{
return $q.reject();
});
var promiseend = promise2.then(function(data2)
{
return data2;
},function(error)
{
return error;
});
return promiseend;
Well I know that it can be way better coded but it's just for the purpose.
Here is the code of Serviceforpromise1 function :
function Serviceforpromise1()
{
...
return $http.get(*whatever*).then(function (data){
return data;
},function(error)
{
return $q.reject();
});
}
Consider only the case of Serviceforpromise1's failure. A $q.reject is sent back to main chain so I'm waiting the error callback of "promise1 .then(" to be called and it worked as expected. I decided for the example to transfert the error to the "promise2 .then" so in this error callback I added the line return $q.reject();
But it never reached the second error callback (the "promise2 .then" one) and I don't understand why (like Serviceforpromise1, I returned a rejected promise !)
I will be happy to deeply understand what is happening here.
Thanks for your help.
Your understanding is correct, and the problem appears to lie somewhere in the way you are trying to observe this behavior (in something you haven't shown us).
If you return a rejected promise from either a success or error handler in then(), then the promise returned by then() will resolve to a rejected promise. Observe:
angular.module('app', [])
.controller('C', [
'$q',
function ($q) {
var promiseStart = $q.when("start");
var promise1 = promiseStart.then(function (value) {
console.log('Got a value:', value);
return $q.reject('Error!');
});
var promise2 = promise1.then(function (data1) {
return "Got some stuff";
}, function (error) {
console.log("Caught an error:", error);
return $q.reject('New error');
});
var promiseend = promise2.then(function (data2) {
return data2;
}, function (error) {
console.log('Caught an error:', error); // <-- this is logged to the console
return error;
});
return promiseend;
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.min.js"></script>
<div ng-app='app' ng-controller='C'></div>
One thing to note here is that in that last handler, you are returning the error variable, and not throwing an exception or returning a rejected promise. So in this case, promiseend will successfully resolve with the value of that error variable.
So I have recently started using AngularFire since it renders the use of a backend completely useless.
I am trying to see in a registerform if the email already exists. At the moment there is one emailaddress 'xxx' in the firebase, however I am not capable of adding a second entry since I keep getting an undefined function. To clarify, here is the code in my controller:
$scope.submitForm = function (isValid) {
if (isValid) {
var resolved = checkEmail($scope.add.email);
console.log('resolved = ' + resolved);
if (resolved) {
console.log('I should be here');
$scope.addEntry();
} else if (!resolved) {
console.log('Am I seriously getting in here ???');
}
} else {
alert('failed!')
}
};
var db = new Firebase("https://radiant-fire-1289.firebaseio.com/");
function checkEmail(inputEmail) {
db.on('child_added', function (snapshot) {
var data = snapshot.val();
if (data.email.toUpperCase() === inputEmail.toUpperCase()) {
return false;
} else {
console.log('I am in checkEmail');
return true;
}
})
}
inputEmail is obviously the email I put in my html form. The function correctly returns true when submitting email address 'bbb' , it is not equal to 'xxx'. the submitForm function gets called from the form when pressing on the submit button. isValid is just some validation of the form, this works correctly. However the boolean 'resolved' is always undefined. I have a feeling this has something to do with asynchronous functions, however since I am a newbie at javascript (and especially firebase) I have trouble understanding what it is exactly what I am doing wrong.
The console does log 'I am in checkEmail' before he logs 'resolved = ' + resolved, but resolved keeps printing as undefined instead of true. Then the console prints 'Am I seriously getting here ???' which leads to no entry being added. (I guess undefined is the same as false for the compiler? )
The on function is asynchronous. This means that the callback is not executed until after a remote call to Firebase completes. The checkEmail function has already run before the result is fetched, so there is no return value. You could test this by adding a console.log right before the end of checkEmail, which you would note runs before your "I'm in checkEmail" log.
Additionally, there is a misunderstanding of scope here. Return false/true inside of the db.on callback is not somehow propagated up to checkEmail, so the result of checkEmail would always be undefined, even if this were a synchronous call.
To do this correctly, you want to invoke another callback when the result is fetched:
$scope.submitForm = function (isValid) {
if (isValid) {
var resolved = checkEmail($scope.add.email, function(resolved) {
console.log('resolved = ' + resolved);
if (resolved) {
console.log('I should be here');
$scope.addEntry();
} else if (!resolved) {
console.log('Am I seriously getting in here ???');
}
});
} else {
alert('failed!')
}
};
var db = new Firebase(URL);
function checkEmail(inputEmail, callback) {
db.on('child_added', function (snapshot) {
var data = snapshot.val();
if (data.email.toUpperCase() === inputEmail.toUpperCase()) {
callback(false);
} else {
console.log('I am in checkEmail');
callback(true);
}
})
}
Last but not least, you'll want to learn about Angular's HTML compiler and $q. The child_added event is not going to be fired inside Angular's compile scope (since it's asynchronous) so you'll need to manually trigger a compile using $timeout if you make any changes to $scope.
See the Firebase + Angular Getting Started Guide, which will introduce you to the correct way to integrate, and provides a lib called angularFire that handles all these complexities on your behalf.