I'm just getting started with Angular and Express and facing tough times with it. I come from a java background and want to learn Angular and Express and therefore trying out to build one small application.
What I'm trying to do: I have given a password reset link to user so as to change his password. The link is something like:
localhost:9000/reset/:token
Now, I have created a simple view which shows an input box to change his password if token is valid otherwise prints an error if token is invalid based on ng-show property of angular.
Problem: Before I can render my above created view, I want ui-router to check if the :token is valid or not. I will be using the information of validity of token in my controller to control ng-show property mentioned above.
After reading this I tried to leverage the $stateProvider.state functionality with a resolve so as to get the response of validation of token as pre-requisite. This will help me when rendering the actual view where I'm using ng-show technique to show error message or input box to change the password based on the ui-router resolve promiseObject.
What is the issue now ?
Well, after breaking my head for too long, I decided to post my question over here. Can anyone please help me here ?
My questions:
1. I'm able to get the data/err from the api call but somehow ui-router is not injecting it in my controller. Can anyone tell me am I doing anything wrong here ?
2. Right now if the token is not valid, I'm returning a 404 in response from my backend api. But the factory method in frontend takes it as err (Is this expected in Node.js ?) and the err is thrown which results in deferred.reject(). Now, if I go with ui-router definition, if the promise is not resolved then the view won't be rendered, right ? Is there any way by which I can pass this err also to my controller ? Reason why I'm asking to pass err is, my view's ng-show logic is based on the response code (4xx/2xx) depending on which I'll show error message or input box.
Code snippets:
Factory Method which calls the rest api:
isPasswordResetTokenValid: function(token, callback) {
var cb = callback || angular.noop;
var deferred = $q.defer();
return User.getUserByPasswordResetToken(token,
function(data) {
deferred.resolve(data);
return cb(data);
},
function(err) {
deferred.reject(err);
return cb(err);
}.bind(this)).$promise;
}
'use strict';
angular.module('scrubApp')
.config(function ($stateProvider) {
$stateProvider
.state('passwordreset', {
url: '/reset/:token',
templateUrl: 'app/account/passwordreset/passwordreset.html',
resolve: {
promiseObj2: function($stateParams, Auth){
var token = $stateParams.token;
console.log(token);
var response = Auth.isPasswordResetTokenValid({token: token})
.then( function(response) {
console.log(response); // shows response
if(response.status == 404) {
//$scope.token-expiry = true;
return response;
}
if(response.status == 200) {
// $scope.user = response.data;
}
})
.catch( function(err) {
console.log(err); // shows error
return err;
});
}
},
controller: 'ResetPasswordCtrl'
});
});
ResetPasswordCtrl controller:
'use strict';
angular.module('myApp')
.controller('ResetPasswordCtrl', function ($scope, User, Auth, Mailer, $stateParams, promiseObj2) {
$scope.errors = {};
$scope.user = {};
console.log(promiseObj2); // This is coming undefined
$scope.isTokenExpired = promiseObj2; // Not able to inject promiseObj2
$scope.isFormSubmitted = false;
});
Thanks in advance
Your resolve promiseObj2 should return a promise from promise service, so that your controller will wait till promise gets resolved.
return Auth.isPasswordResetTokenValid({token: token})
Update
If you want to handle some logic on failure of your token request then you could handle it in your promise itself, that can do couple of thing like
You could redirected to other page using $state.go('login') or $state.go('error') page.
Code
promiseObj2: function($stateParams, Auth, $state){
var token = $stateParams.token;
console.log(token);
return Auth.isPasswordResetTokenValid({token: token}) //added return here
.then( function(response) {
console.log(response); // shows response
if(response.status == 404) {
$state.go('error')
}
if(response.status == 200) {
return response;
}
})
.catch( function(err) {
console.log(err); // shows error
return err;
});
}
If you want to show html page anyhow if error occurs then You could also return data from the .then of promiseObj2 object that will have information about error message. So that error information is return to the controller
Code
promiseObj2: function($stateParams, Auth, $state){
var token = $stateParams.token;
console.log(token);
return Auth.isPasswordResetTokenValid({token: token}) //added return here
.then( function(response) {
console.log(response); // shows response
if(response.status == 404) {
return {status: 404, data: "doen't found resource"}
}
if(response.status == 200) {
return response;
}
})
.catch( function(err) {
console.log(err); // shows error
return err;
});
}
Then inside controller we will get resolve the promise of promiseObj2 object and then you will get the value of error in the .then function of it.
angular.module('myApp')
.controller('ResetPasswordCtrl', function ($scope, User, Auth, Mailer, $stateParams, promiseObj2) {
$scope.errors = {};
$scope.user = {};
promiseObj2.then(function(resp){
console.log(resp)
$scope.isTokenExpired = resp.isTokenExpired;
}, function(err){
console.log(err)
})
});
Update
If we want to handle a condition where server return 4XX status that means our ajax will call catch function that won't return promise though. We could solve this case by creating custom promise using $q and we will resolve it from the promiseObj2
Code
promiseObj2: function($stateParams, Auth, $state, $q){
var token = $stateParams.token,
deffered = $q.defer();
console.log(token);
Auth.isPasswordResetTokenValid({token: token}) //added return here
.then( function(response) {
console.log(response); // shows response
if(response.status == 404) {
//return {status: 404, data: "doen't found resource"}
deffered.resolve({status: 404, data: "doen't found resource"});
}
if(response.status == 200) {
//return response;
deffered.resolve(response);
}
})
.catch( function(err) {
console.log(err); // shows error
deffered.resolve(err);
});
return deffered.promise;
}
Related
My angularjs $http.post .then method never gets executed neither the .catch method is executed although the server is working fine and returning what I want it to return.
This is my node js controller, doing validation for email exists
module.exports.add = function(req, res, next) {
var user = new User(req.body);
User.find({email:req.body.email},function(err,doc){
if(err) {
{
err.status = 406;
return next(err);
}
}
if(doc.length){
return res.status(409).json({message: 'Email already exist'});
}
else {
user.save(function(err, doc) {
if (err) {
err.status = 406;
return next(err);
}
return res.status(201).json({message: 'Your account has been created successfully. Please login to continue.'});
}) //end of user.save
} //end of else
});
};
This is my angular controller
quizApp.controller("createAcc",['$scope','$http','$location','$timeout','signup',function ($scope,$http,$location,$timeout,signup) {
$scope.createAcc = function () {
signup.createAcc($scope.fName, $scope.email, $scope.password)
.then(function () {
console.log("it happened");
})
.catch(function (err) {
if(response.status === 409){
$scope.emailExist = true;
}
console.log("12345");
});
}
}
]);
This is my angular service
quizApp.factory('signup', ['$http',function ($http) {
return {
createAcc : function (fName,email,password) {
var data = {fName:fName,email:email,password:password};
return $http.post('/api/v1/user/add',data)
}
}
}]);
So now the problem is, my server is doing the right validation and sending me the status of 409 i.e, email exists but in my angularjs controller the .then function or the .catch function never runs. In the console it just throws an error of 409 but the .then function or the .catch function never gets executed. I have also tried with .success( function successCallback()}, function errorCallback(){} ) , that doesn't work either. I don't understand where I am doing wrong, everything works fine with Postman (the application) though but I am unable to debug the issue here. I think I am doing something wrong in my angular code , can anyone please tell me why my .then and .catch function never gets executed?
inject the $q to the factory also
change
['$http','$q',function ($http) {
to
['$http','$q',function ($http,$q) {
So I have pulled the interceptor straight from the angular HTTP documentation and yet this still doesn't work. The "request" and "response" functions get called ,but never the "requestError" or the "responseError".
myApp.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push(function ($q) {
return {
'request': function (config) {
return config; //gets called
},
'requestError': function (rejection) {
return $q.reject(rejection); //Never gets called
},
'response': function (response) {
return response; //gets called
},
'responseError': function (rejection) {
return $q.reject(rejection); //Never gets called
}
};
});
}]);
On the server I am returning a 400, but really any error would do. And here is the service
User.publicProfileGetProfile = function (value, type) {
return $http({
url: '/public/profile/' + type + '/' + value,
method: 'GET'
}).then(function (response) {
return response;
}, function(error){
return error;
});
};
No error functions are being called and every response goes through the response function. The standard angular error is displayed with the Bad Request (400) as usual. When the 400 error is returned, it is simply 'undefined' through the 'response' function in the interceptor.
Let me know if I've forgotten to include any important information.
By using return, the error handler is converting the rejection to a success. Instead use throw to chain the rejection.
User.publicProfileGetProfile = function (value, type) {
return $http({
url: '/public/profile/' + type + '/' + value,
method: 'GET'
}).then(function onSuccess(response) {
return response;
}, function onReject(error){
//return converts rejection to success
//return error;
//use throw to chain rejection
throw error;
});
};
When I saw that the JSFiddle (from #georgeawg) was working properly, I made sure mine looked exactly the same. When it didn't work, I looked around to see if I had any other interceptors that might cause problems. I had another interceptor that was being hit first and returning any errors as responses, then they would go through this one and it would process it as a successful response. I removed it and everything seems to be working correct now!
This may just come from a misunderstanding of how to best do authentication in a MEAN stack app, or my lack of knowledge in how promises and $http's .then() method works, but whenever I try to authenticate to my backend Node server with incorrect credentials, it is calling the success callback of $http's .then() method instead of the error callback. Here's my setup:
I'm using the jsonwebtoken and express-jwt packages, AngularJS interceptors to add the token the to request and check for status 401 responseErrors, a TokenService that sets/removes, etc JWTs, and a UserService to handle login, logout, etc.
From debugging, here's what's happening:
Request to login is sent out
Server catches request, looks for specified user and can't find them in database. Returns 401 error with JSON object including error message, etc.
HttpInterceptor engages the responseError method, correctly sees it's a status 401, removes any possible existing token, redirects to /login screen, and returns $q.reject(response).
UserService.login() correctly uses the error callback and does a return response.
Problem - the success callback in my login.js .login() method runs, instead of the second, error callback one. I have a feeling this has to do with what is discussed in this article about promise chaining, but my expertise has its limit right here and I can't quite understand what I should be doing next to tell the next callback in the chain that the previous one had an error...
Here's my setup:
Express:
authRoutes.js
authRoutes.post("/login", function (req, res) {
User.findOne({username: req.body.username}, function (err, user) {
if (err) res.status(500).send(err);
if (!user) {
res.status(401).send({success: false, message: "User with the provided username was not found"})
} else if (user) {
bcrypt.compare(req.body.password, user.password, function (err, match) {
if (err) throw (err);
if (!match) res.status(401).json({success: false, message: "Incorrect password"});
else {
var token = jwt.sign(user, config.secret, {expiresIn: "24h"});
res.json({token: token, success: true, message: "Here's your token!"})
}
});
}
});
});
From debugging, when I log in with incorrect credentials, it's correctly hitting the res.status(401).send(...) line, so this part seems to be okay.
Angular:
app.js (includes HttpInterceptor)
var app = angular.module("TodoApp", ["ngRoute"]);
app.factory("AuthInterceptor", ["$q", "$location", "TokenService", function ($q, $location, TokenService) {
return {
request: function (config) {
var token = TokenService.getToken();
if (token) {
config.headers = config.headers || {};
config.headers.Authorization = "Bearer " + token
}
return config;
},
responseError: function (response) {
if (response.status === 401) {
TokenService.removeToken();
$location.path("/login");
}
return $q.reject(response);
}
}
}]);
app.config(function ($routeProvider, $httpProvider) {
$httpProvider.interceptors.push('AuthInterceptor');
$routeProvider
.when("/", {
templateUrl: "landing/landing-page.html"
});
});
userService.js
var app = angular.module("TodoApp");
app.service("UserService", ["$http", "TokenService", function ($http, TokenService) {
this.signup = function (user) {
return $http.post("http://localhost:8080/auth/signup", user).then(function (response) {
return response;
}, function (response) {
return response;
});
};
this.login = function (user) {
return $http.post("http://localhost:8080/auth/login", user).then(function (response) {
if (response.data.success) TokenService.setToken(response.data.token);
return response;
}, function (response) {
return response;
})
};
this.isAdmin = function (user) {
return user.admin;
};
}]);
login.js (Where the problem seems to manifest itself)
var app = angular.module("TodoApp");
app.config(function ($routeProvider) {
$routeProvider
.when("/login", {
templateUrl: "auth/login.html",
controller: "LoginController"
})
});
app.controller("LoginController", ["$scope", "$http", "$location", "UserService", "TokenService", function ($scope, $http, $location, UserService, TokenService) {
$scope.login = function (user) {
UserService.login(user).then(function (response) {
$location.path("/todo");
}, function (response) {
console.log("There was a problem: " + response);
});
}
}]);
That last part, UserService.login(user).then(function (response) { $location.path("/todo"); is the line that is running and trying to redirect the user to the list of Todo items, when I want it to run that console.log("There was a problem: " + response); line instead...
Like I said above, I have a feeling it has to do with chaining promises and how errors are handled partway through the chain instead of bubbling down through the chain. Not sure if I need to add a .catch() block like the site I mentioned above says to do. And even if that is the answer, I'm not entirely sure how to write that.
If there is a better way I should be organizing this, I'm definitely open to suggestions as well. I have to teach this to a class of students and want to make sure I'm teaching good practices.
Thanks in advance for the help!
Take a closer look at this part of your code:
this.login = function (user) {
return $http.post("http://localhost:8080/auth/login", user).then(function (response) {
if (response.data.success) TokenService.setToken(response.data.token);
return response;
}, function (response) {
return response;
})
}
Here you've provided error callback with a return value which is passed to the next callback in promise chain. The source of your confusion is that you still need to return rejected promise of throw from the callback if you want the error to propagate further. Otherwise, it effectively means that you've recovered from error situation and the next step in the flow will be success. This is what you have now.
In your case you either remove error callback altogether
return $http.post("http://localhost:8080/auth/login", user).then(function (response) {
if (response.data.success) TokenService.setToken(response.data.token);
return response;
});
... or make sure you return failed promise
return $http.post("http://localhost:8080/auth/login", user).then(function (response) {
if (response.data.success) TokenService.setToken(response.data.token);
return response;
}, function (response) {
return $q.reject(response);
});
... or throw:
return $http.post("http://localhost:8080/auth/login", user).then(function (response) {
if (response.data.success) TokenService.setToken(response.data.token);
return response;
}, function (response) {
throw new Error(response);
});
Have you tried to use $q.reject in error cases for then() calls?
E.g.
// remember to add $q to deps
this.login = function (user) {
return $http.post("http://localhost:8080/auth/login", user).then(function (response) {
if (response.data.success) TokenService.setToken(response.data.token);
return response;
}, function (response) {
$q.reject(response);
})
};
Related docs: https://docs.angularjs.org/api/ng/service/$q
I'm trying to separate the $http.post() call into a ".factory()", But would like to fetch the response which is coming async on the controller. Is there a way of doing that?
Controller:
Login.post($scope.user);
Factory:
.factory( 'Login' , function($http,SERVERURL){
var serverUrl = SERVERURL;
return {
'post' : function(user){
$http.post(serverUrl+'/login', user).
then(function(response) {
console.log(response);
}, function(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
}
};
})
There is a .then() but I want that on the controller, so I can behave accordingly. Thank you!
Basically you need to return the $http.post promise, and from success function you could return a data that will return to the consumer of this method. So that you could easily call the factory method from controller & inside .then function of that call you could have success and error function.
Code
.factory('Login', function($http, SERVERURL) {
var serverUrl = SERVERURL;
return {
'post': function(user) {
return $http.post(serverUrl + '/login', user).
then(function(response) {
console.log(response);
return response.data; //return data from here
}, function(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
}
};
})
Controller
Login.post().then(function(data){ //success function
console.log(data)
}, function(error){ //error function
console.log(error);
})
You could add a callback param.
.factory( 'Login' , function($http,SERVERURL){
var serverUrl = SERVERURL;
return {
'post' : function(user, callback){
$http.post(serverUrl+'/login', user).
then(function(response) {
console.log(response);
callback(null, response);
}, function(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
callback(response);
});
}
};
})
And your controller will become:
Login.post($scope.user, function(err, response) {
if(err) {} //do something if there is an error
// or deal with the response
});
To return any response to controller just do:
return {
'post' : function(user){
return $http.post(serverUrl+'/login', user);
}
};
In your controller you will already call.then()
Angular's $http methods return a Promise.
The $http API is based on the deferred/promise APIs exposed by the $q service.
Factory
Your method post is not yet returning anything but can quite simply return the Promise which is created by calling $http.post:
.factory('Login' , function($http, SERVERURL){
var serverUrl = SERVERURL;
return {
'post' : function (user) {
return $http.post(serverUrl + '/login', user)
// ^^^^^^
.then(function (response) {
console.log(response);
return response.data;
}, function (response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
}
};
});
Controller
Then consume the result of the returned Promise by calling then on it:
Login.post($scope.user).then(function (res) {
// do something with `res`...
});
I'm reading a book called MEAN Machine, and as I'm getting to the late chapters, I've got stuck at one of the sample applications, that doesn't seem to work.
The problem seems to occur because my mainController calls authService's Auth.getUser() method, which may return either a $http.get() or a $q.reject(). As I'm not logged in, it returns $q.reject(), and fails to chain the .success() promise.
It throws following exception:
TypeError: undefined is not a function at mainCtrl.js:13
My code is as follows.
CONTROLLERmainController
angular.module('mainCtrl', [])
.controller('mainController', function($rootScope, $location, Auth) {
var vm = this;
// check to see if a user is logged in on every request
$rootScope.$on('$routeChangeStart', function () {
vm.loggedIn = Auth.isLoggedIn();
// get user information on route change
Auth.getUser()
/* ========= PROBLEM HERE ========= */
.success(function (data) {
vm.user = data;
});
});
// ... other stuff
});
SERVICEauthService
angular.module('authService', [])
// ===================================================
// auth factory to login and get information
// inject $http for communicating with the API
// inject $q to return promise objects
// inject AuthToken to manage tokens
// ===================================================
.factory('Auth', function ($http, $q, AuthToken) {
var authFactory = {};
// get the user info
/* ========= PROBLEM LEADS HERE ========= */
authFactory.getUser = function () {
if (AuthToken.getToken())
return $http.get('/api/me', { cache: true });
else {
return $q.reject({ message: 'User has no token.' });
}
}
What am I missing?
Replace your call to the service with:
Solution A: .then(successCallback, errorCallback):
Auth.getUser().then(
function (response) { ... }, // success handler
function (response) { // error handler
// case where user is not logged in
// or http request fails
});
or
Solution B: .then(successCallback).catch(errorCallback):
Auth.getUser()
.then(function (response) { ... }) // success handler
.catch(function (response) { // error handler
// case where user is not logged in
// or http request fails
});
Explanation :
Your getUser method is defined as follows :
authFactory.getUser = function () {
if (AuthToken.getToken())
return $http.get('/api/me', { cache: true });
else {
return $q.reject({ message: 'User has no token.' });
}
}
But the success and the error shorthand methods are specific to $http. They doesn't exist in angular's promise $q API. Therefore, when the user is not logged in, because you are returning a $q promise, you got an undefined is not a function.
The methods you can call on a $q promise object are (link to documentation) :
then(successCallback, errorCallback, notifyCallback)
catch(errorCallback) which is a shorthand for promise.then(null, errorCallback)
finally(callback, notifyCallback)