I am moving all http calls in my controllers to services, using $q to promise... Everything seems to work till I refresh the page then the content disappears.
Setting: In my login service, I have a function which does an $http.post, it takes username and password. Then the response data is put in the promise.
Problem: Though the service works, It is trying resend the http call when the page is refreshed. The call then fails because the are no login details.
Question: How do I keep/store the original data so when the page is refreshed (and user is still signed in) and when I use the service in other controllers it does not do the http call it just returns the data?
Service:
var app = angular.module('myApp', ['ngRoute']);
app.factory('loginService', function($http, $q) {
var deferResults = $q.defer();
return {
appContent: function(login_username, login_password) {
$http.post('/login',{username: login_username, password: login_password}).success(function(data) {
deferResults.resolve(myData);
});
return deferResults.promise;
}
});
Controllers:
function loginPageCtrl($scope, loginService) {
$scope.login = function (login_username, login_password, login_rememberMe) {
loginService.appContent(login_username, login_username).then(function (data) {
$scope.pageOneContent = data;
});
};
};
function pageTwoCtrl($scope, loginService) {
// Use the same data when page is refreshed
// without having to pass-in login details
loginService.appContent().then(function (data) {
$scope.pageTwoContent = data;
});
};
Of course that happens, your app loses all its state when the page is refreshed. You need to persist that username and password somewhere, like a cookie, and then retrieve that then make your second $http call, even better just keep a token in a cookie and use that for authentication. Use angular's $cookieStore to persist the login details:
var userData={username:login_username,password:login_password};
$cookieStore.put('user_data', userData);
and then when the controller loads, check if the cookie exists:
var userData = $cookieStore.get('user_data');
return userData
Check the source in this answer to see this in action.
To recap, every time the controller loads check the cookie, if its undefined then redirect the user to a login page, if it's not then extract the toke/username/password and make your $http call.
Related
I have a working angular form sitting inside an app (rails app). It sends a get request to a rails server.
The problem is that if this app receives a normal get request, it refreshes the page. However, when the request is sent via angular, I can see the request on the server, but the page is never refreshed. (I can use these get requests to get data back as well).
The question is how do I pass a get request in angular form, so that the server processes it normally, and refreshes the page?
html:
<form ng-submit="submitForm()" style="margin-top:30px;">
<h3>get request</h3>
<button type="submit" class="btn btn-primary">Get</button>
</form>
app.js controller:
$scope.submitForm = function(){
posts.factorySubmit();
};
app.js factory get function:
o.factorySubmit = function() {
$http.get('http://localhost:8080/clients');
};
--Side note - if I create a get request, with regular java script in the app, outside of the angular context, I get expected behavior, the page refreshes.
You've wrapped your $http in a factory which is great practice. What you're not doing is capturing the response.
o.factorySubmit = function() {
// Add a return here
return $http.get('http://localhost:8080/clients'); // This call returns a promise
};
Then when you call this function...
$scope.submitForm = function(){
posts.factorySubmit().then(function(response) {
$scope.requests = response.data; // This is where you get your response
},
function(response) {
// This is your error handling
});
};
The cool thing about angular is that it will detect changes and update the page automatically. It has a digest cycle that is called internally in response to changes. Since you're using the built-in $http provider, it'll refresh when you update the $scope variables.
Angular is by design supposed to not refresh the page as it is built for Single Page Applications. If you want to manually refresh the page, use
$scope.submitForm = function(){
posts.factorySubmit().success(function(){
$route.refresh();
});
};
In the success function of your $get request. Remember to inject $route into your controller.
A similar-ish question here (but not applicable).
In my SPA I'm using PassportJS to handle authentication. I have many routes which require the user to be logged in. I route like this:
// ... other routing stuff
when('/insights', {
templateUrl: 'partials/insights',
controller: 'insightsCtrl',
resolve: {
loggedin: checkLoggedin
}
})
var checkLoggedin = function($q, $timeout, $http, $location, $rootScope){
var deferred = $q.defer();
$http.get('/loggedin').success(function(user){
if (user !== '0') {
$rootScope.user = user;
deferred.resolve();
}
else {
deferred.reject();
$location.url('/login');
}
});
return deferred.promise;
};
My route to check if logged in:
app.get('/loggedin', function(req, res) {
res.send(req.isAuthenticated() ? req.user : '0');
});
If they're not logged in, they're redirected to /login. Once that is successfully done though, I'd like to go back to the original page they tried to request.
Is there a built-in way to store the route the user is trying to access so that I can redirect to it once they're logged in? I can do this with a cookie I suppose, but it feels a little clunky to do so...
There is no built in approach besides using the custom callback that PassportJS provides, but if you are writing this for a SPA, you could leverage $cacheFactory and create a service/factory that stores the last visit page of that user.
Limitations for $cacheFactory are the same as local storage which are that they are domain specific, so if you change domains, your local storage will not be the same cross-domain.
You can use a custom callback as explained in passportjs documentation.
With custom callback, you can send the current query string to your server, and get a redirect to same query string after successful login.
Another possibility is to use localStorage to save current state and take it after login.
You have many modules to use localStorage with angular like angular-local-storage.
I'm writing cms front-end in Angular JS and I don't know how to deal with authorize user to see particular content when user first load the app. I have working solution when user is already log in and just navigate from page to page. I do it this way:
angular.module("myApp")
.run ($rootScope, AUTH_EVENTS, AuthServ) ->
$rootScope.$on '$stateChangeStart', (event, next) ->
if next.data
authorizedRoles = next.data.authorizedRoles
unless AuthServ.isAuthorized(authorizedRoles)
event.preventDefault()
if AuthServ.isAuthenticated()
$rootScope.$broadcast(AUTH_EVENTS.notAuthorized)
else
$rootScope.$broadcast(AUTH_EVENTS.notAuthenticated)
When user change the route, the AuthServ.isAuthorized(authorizedRoles) is fired.
When user logs in I get token and user data from server. Token is stored in local storage and user data are stored in memory (in the scope of top level controller). In the user data I have an info about his role, so I can check if he's authorized to see particular content.
Now let's assume that user is logged in and he reload the page. I still have the token as this is in localStorage but I loose user data (so I don't know what is his role). I don't want to show any content to user before I get his data again from server. So my question is how to resolve this ? Where and when should I make the request to server for user data ?
I thought that solution could be to manually bootstrap the app. I tried to do something like this:
app = angular.module('myApp', [])
fetchData = ->
injector = angular.injector(['ng'])
$http = injector.get('$http')
API_URI = injector.get('API_URI')
$localStorage = injector.get('$localStorage')
$http.get("#{API_URI}/users/me?token=#{$localStorage.token}")
.success (data) ->
app.constant('USER_DATA', data)
bootstrapApplication = ->
angular.element(document).ready ->
angular.bootstrap(document, ['myApp'])
fetchData().then(bootstrapApplication)
The problem with this code is that I don't have access to $localStorage service and API_URI constant. I need them to get token and to dynamically change url (development, production).
So what is the best solution ? Maybe storing user role in local storage as well ?
Any help would appreciated, Thanks.
You can use Angular's resolve in your routes configuration. Here is an example where I am resolving 'user' for a particular route. It must resolve before angular will load this route.
app.config( [ '$routeProvider', '$locationProvider',
function( $routeProvider, $locationProvider ) {
$routeProvider
.when('/somePath', {
templateUrl: "someView.html",
controller: "someController",
resolve: {
user: function( authService ){
return authService.getUser();
}
}
}
);
}
]);
Here is the service(factory in this case) to go along with it. I am checking to see if auth.user exists first so there won't be a request on each route change:
app.factory('authService', [ '$http', '$q',
function( $http, $q ) {
var pub = {};
pub.user = null;
pub.getUser = function() {
var deferred = $q.defer();
if( pub.user ) {
deferred.resolve( pub.user );
}
else{
$http.get('/someAuthUrl').then(function( user ) {
pub.user = user;
deferred.resolve( user );
});
}
return deferred.promise;
};
return pub;
}
]);
And then finally the controller which will not load until the auth function has resolved with your needed user data. You can access the user object now through injection.
app.controller( 'someController', [ '$scope', 'user',
function( $scope, user ) {
//Controller runs here only when user is resolved.
// Anything resolved will be passed in as your last dependency.
$scope.user = user;
}
]);
In this case you could have injected the service into the controller instead and use authService.user as we know it's available.
app.controller( 'someController', [ '$scope', 'authService',
function( $scope, authService ) {
$scope.user = authService.user;
}
]);
This is because whenever you reload/navigate new instance of controller is created and your user data in controller is lost. Best way to do is to move the logic of authenticating and storing data to a Service and use that service in the controller, this way you won't lose the data on refresh/navigation
Some of my AngularJS routes are to pages which require the user to be authenticated with my API. In those cases, I'd like the user to be redirected to the login page so they can authenticate. For example, if a guest accesses /account/settings, they should be redirected to the login form.
From brainstorming I came up with listening for the $locationChangeStart event and if it's a location which requires authentication then redirect the user to the login form. I can do that simple enough in my applications run() event:
.run(['$rootScope', function($rootScope) {
$rootScope.$on('$locationChangeStart', function(event) {
// Decide if this location required an authenticated user and redirect appropriately
});
}]);
The next step is keeping a list of all my applications routes that require authentication, so I tried adding them as parameters to my $routeProvider:
$routeProvider.when('/account/settings', {templateUrl: '/partials/account/settings.html', controller: 'AccountSettingCtrl', requiresAuthentication: true});
But I don't see any way to get the requiresAuthentication key from within the $locationChangeStart event.
Am I overthinking this? I tried to find a way for Angular to do this natively but couldn't find anything.
What I did is implement an angularjs interceptor which handles http request errors. Basically, what I do is when I get the 401 (unauthorized) from my backend, I save the current url and redirect the user to a login page. When the user does login successfully, I retrieve the saved url to set the path with $location.
app.config(function ($routeProvider, $locationProvider, $httpProvider) {
/* Global interceptor for 401 - not authorized */
var interceptor = ['$location', '$q', 'authorizationService', function ($location, $q, authorizationService) {
function success(response) {
return response;
}
function error(response) {
if (response.status === 401) {
authorizationService.saveUrl($location.path());
$location.path('/logon');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
return function (promise) {
return promise.then(success, error);
};
} ];
});
In the logon controller (on successful logon) I set the location like this:
$location.path(authorizationService.getUrl());
I use angular-http-auth in my project. If you have a backend is easier to delegate on it to handle the authentication, showing a login form automatically in each 401 response, than duplicate the authentication mapping both on client and server.
I'm working with a restful API that when authenticating a user successfully, the request returns a token. The token is then added as a header on every request like this:
Authorization: Bearer <token>
I struggled to find a good way to do authentication without a lot of code bloat.
I came up with a good solution using HTML5 sessionStorage. Here's a simple example:
// Main module declaration
var myapp = angular.module('myapp', []);
// Set some actions to be performed when running the app
myapp.run(['$location', '$rootScope',
function($location, $rootScope) {
// Register listener to watch route changes.We use this to make
// sure a user is logged in when they try to retrieve data
$rootScope.$on("$routeChangeStart", function(event, next, current) {
// If there is no token, that means the user is not logged in
if (sessionStorage.getItem("token") === null) {
// Redirect to login page
window.location.href = "login.html";
}
});
}]);
// A controller for the login page
myapp.controller('LoginController', ['$scope', '$http',
function($scope, $http) {
// If a user has check the "remember me" box previously and the email/pass
// is in localStorage, set the email/password
// Login method when the form is submitted
$scope.login = function() {
// Authenticate the user - send a restful post request to the server
// and if the user is authenticated successfully, a token is returned
$http.post('http://example.com/login', $scope.user)
.success(function(response) {
// Set a sessionStorage item we're calling "token"
sessionStorage.setItem("token", response.token);
// Redirect to wherever you want to
window.location = 'index.html';
});
};
}]);