I developed an angularjs app and I've a REST API for fetching app resources from my AngularJS app. When the user logs himself, I save his access token in a cookie. If he refreshes the page, I want to get back user information from the token like:
mymodule.run(function ($rootScope, $cookies, AuthService, Restangular) {
if ($cookies.usertoken) {
// call GET api/account/
Restangular.one('account', '').get().then( function(user) {
AuthService.setCurrentUser(user);
});
}
});
AuthService:
mymodule.factory('AuthService', function($http, $rootScope) {
var currentUser = null;
return {
setCurrentUser: function(user) {
currentUser = user;
},
getCurrentUser: function() {
return currentUser;
}
};
});
But if the user accesses a controller which needs a user variable:
mymodule.controller('DashboardCtrl', function (AuthService) {
var user = AuthService.getCurrentUser();
});
the controller code was executed before the end of the API call so I've a null var. Is there a good solution to wait on user data loading before starting controllers?
I've found this link but I'm looking for a more global method to initialize app context.
Here's one way I like to handle this, assuming that Restangular returns the new promise from then (I haven't used Restangular). The promise can be stored somewhere like on AuthService and then used later in the controller. First, add a property to AuthService to hold the new promise:
return {
authPromise: {}, // this will hold the promise from Restangular
// setCurrentUser, getCurrentUser
// ...
When calling Restangular, save the promise and be sure to return the user data so that the controller can access it later, like this:
AuthService.authPromise = Restangular.one('account', '').get()
.then( function(user) {
AuthService.setCurrentUser(user);
return user; // <--important
});
Lastly, create a new promise in the controller that will set the user variable.:
mymodule.controller('DashboardCtrl', function (AuthService) {
var user;
AuthService.authPromise.then(function(resultUser){
user = resultUser;
alert(user);
// do something with user
});
});
Demo: Here is a fiddle where I've simulated an AJAX call with $timeout. When the timeout concludes, the promise resolves.
You could try have the authentication method on a parent controller. Then in the resolve method in the routing you could go resolve:DashboardCtrl.$parent.Authenticate().
Related
Hii I m using following code. I am reading a json file name is "users.json". If i read this file in controller through $http everything works fine. but i want to use the data that i read from file, again and again in different controller so i made a factory for this. but in factory when i read data from that json file through $http.get() and in return when i call that service method in my controller and it returns Object { $$state: Object }
app.factory('AboutFactory',['$http',function ($http) {
return {
getter: function () {
return $http({
method : 'GET',
url : '/home/penguin/Modeles/users.json',
cache : true
})
.then(function (response) {
return response.data
})
}
}
}])
Result of getter function is a promise. so you should use it like this:
AboutFactory.getter().then(function(res)
{
console.log(res);
});
That's because the $http service returns a promise as mentioned in the documentation:
The $http API is based on the deferred/promise APIs exposed by the $q
service. While for simple usage patterns this doesn't matter much, for
advanced usage it is important to familiarize yourself with these APIs
and the guarantees they provide.
You can think of a promise as if you give a top secret message to someone to deliver personally to a friend, then when delivered, report back to you with a message back from your friend.
You provide the message (the request object) to the person so that they can attempt to make the delivery of the message (send the request).
The attempted delivery has taken place (the request has been sent), it either:
a) was delivered successfully (successful response) or
b) your friend was not in so the letter could not be delivered (non success response).
You can then act depending on the response you get back
a) Message was delivered (it was a successful request) and you got a letter back (do something with the response) or
b) Message failed to get delivered (request wasn't successful), so you can maybe try again later or do something else as you don't have the information you requested
Here is an example of using the $http service with the $q service:
// app.js
(function() {
'use strict';
angular.module('app', []);
})();
// main.controller.js
(function() {
'use strict';
angular.module('app').controller('MainController', MainController);
MainController.$inject = ['AboutFactory'];
function MainController(AboutFactory) {
var vm = this;
AboutFactory.getter().then(function(data) {
// do something with your data
vm.data = data;
}, function(error) {
// give the user feedback on the error
});
}
})();
// about.service.js
(function() {
'use strict';
angular.module('app').factory('AboutFactory', AboutFactory);
AboutFactory.$inject = ['$http', '$q']
function AboutFactory($http, $q) {
var service = {
getter: getter
};
return service;
function getter() {
// perform some asynchronous operation, resolve or reject the promise when appropriate.
return $q(function(resolve, reject) {
$http({
method: 'GET',
url: 'https://httpbin.org/get',
cache: true
}).then(function(response) {
// successful status code
// resolve the data from the response
return resolve(response.data);
}, function(error) {
// error
// handle the error somehow
// reject with the error
return reject(error);
});
});
}
}
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<div ng-app="app" ng-controller="MainController as MainCtrl">
<pre>{{MainCtrl.data | json}}</pre>
</div>
Try this approach. It will work as per your expectation.
Read JSON file in controller through $http service as it is works fine.
For sharing the response data from one controller to another you can create a service and store the data into that service.
Service :
app.service('setGetData', function() {
var data = '';
getData: function() { return data; },
setData: function(requestData) { data = requestData; }
});
Controllers :
app.controller('myCtrl1', ['setGetData',function(setGetData) {
// To set the data from the one controller
var data = 'Hello World !!';
setGetData.setData(data);
}]);
app.controller('myCtrl2', ['setGetData',function(setGetData) {
// To get the data from the another controller
var res = setGetData.getData();
console.log(res); // Hello World !!
}]);
Here, we can see that myCtrl1 is used for setting the data and myCtrl2 is used for getting the data. So, we can share the data from one controller to another controller like this.
I am using Laravel as my back-end, serving as an API for user auth and any CRUD operations. I can't seem to get this implementation of user auth on the angular side working. What I want is just the user to be loaded from the API by the call to fetchUser(), then the user can be retrieved from getUser(). I am not using Angular for routing so I can't make it render only when resolved. I figured by using promises Angular would automatically update the view. I guess I don't understand the whole digest process because nothing I do makes the view change after the user is loaded. A simple ng-show="user.$resolved" or even ng-show="user" doesn't work. The page stays as it was before the promise was resolved. Is there something obvious I'm doing wrong? Or maybe a better way to implement this? I'm not too worried about security, it is not a very functional website. Just an API object I can fetch to display the page a certain way if they are logged in and what role they are.
controllers.js
var Controllers = new angular.module("Controllers", []);
Controllers.controller("MainController", ["$scope", "$location", "Auth", "$timeout", function($scope, $location, Auth, $timeout) {
$scope.user = Auth.fetchUser().then(function(data) {
$scope.user = data;
console.log(data);
});
}]);
services.js
Services.factory("Auth", function($http, $q) {
var user;
function getUser() {
return user;
}
function fetchUser() {
return $http.get("api/auth").then(function(response) {
if (response.data.user) {
user = response.data.user;
} else {
user = null;
}
return user;
});
}
return {
getUser: getUser,
fetchUser: fetchUser
};
});
master.blade.php
<div class="container">
#include("layouts/nav")
#yield("content")
</div>
nav.blade.php (partial)
<ul class="nav navbar-nav navbar-right">
<li>Inquire</li>
<li ng-show="user.$resolved && user.id">Profile</li>
<li ng-show="user.$resolved && user.role === 'User'">Admin</li>
<li ng-hide="user.$resolved && user.id">Login</li>
<li ng-hide="user.$resolved && user.id">Register</li>
</ul>
When the user is logged in, login and register only show as if the user has not been loaded/logged in yet.
Problem is, you're returning the entire response object in the $http promise resolver. You've also fallen prey to the deferred promise anti-pattern by wrapping a promise based API ($http) in a deferred object.
Try this...
function fetchUser() {
return $http.get('api/auth').then(function(response) {
if (response.data.user) {
return response.data.user;
}
return $q.reject('No user data');
});
}
and
Auth.fetchUser().then(function(data) {
$scope.user = data;
console.log(data);
});
I have a single page angularjs app in which whenever there is a change in route I check the login status of the user by a variable stored in a service(after submitting the login form to server) as per this solution AngularJS- Login and Authentication in each route and controller:
app.run(['$rootScope', '$location', 'Auth', function ($rootScope, $location, Auth) {
$rootScope.$on('$routeChangeStart', function (event) {
if (!Auth.isLoggedIn()) {
console.log('DENY');
event.preventDefault();
$location.path('/login');
}
else {
console.log('ALLOW');
$location.path('/home');
}
});}]);
//service
.factory('Auth', function(){
var user;
return{
setUser : function(aUser){
user = aUser;
},
isLoggedIn : function(){
return(user)? user : false;
}
}
})
The problem is when I reload the whole page(by the refresh button) the variable in the service is lost and user gets redirected to login page even when the user session is still on at the backend.
How can I still manage the variable in the service? I thought of using sessionStorage but does not sound secure enough.
Im my opinion you can choose from 2 ways:
Persist the Data on the server-side via session
Store your data in the localStorage or even better in the window.sessionStorage so a page reload doesn't affect your applicant
Maybe angular-cookies can solve your problem
Try to store your data in $window.localStorage ( angular abstraction over javascript window.localStorage)
for example :
(function () {
angular.module('app').factory('UserIdentity', ['$window', function ($window) {
this.UserName = function () {
return $window.localStorage.getItem("username");
};
this.Token = function () {
return $window.localStorage.getItem("token");
};
this.create = function (token, userName) {
$window.localStorage.setItem("token", token);
$window.localStorage.setItem("username", userName);
};
this.destroy = function () {
$window.localStorage.removeItem("token");
$window.localStorage.removeItem("username");
};
this.isAuthenticated = function () {
var token = $window.localStorage.getItem("token");
return !!token;
}
return this;
}]);
})();
In one of my UI-router states, I have the following link '/users', which points to a page that shows a list of users. Below is the code I wrote to resolve the list of users by using a $resource call, however, the data doesn't seem resolved when the page is loaded:
.state('users', {
url: '/users',
templateUrl: '/assets/angularApp/partials/users.html',
controller: 'UserListCtrl as vm',
resolve: {
users: function ($resource) {
return $resource('http://localhost:8080/api/users').query().$promise.then(function(data) {
return data;
});
}
}
})
In the UserListCtrl, I have the following to assign the resolved users to vm.users so that the users can be displayed on the partial page:
function UserListCtrl($log, users) {
var vm = this;
vm.users = users;
}
The page, however, only displays a few empty rows without any data filled in. So I think the resolve is not working properly on my url /users, could anyone spot where might be problematic? Thanks
Change it to:
users: function ($resource) {
return $resource('http://localhost:8080/api/users').query().$promise;
}
Because that way you're returning the promise, and by using then(...), you're resolving the promise within and just returning data thus it won't pass it to the controller because it's returning them after the controller has loaded.
Using $q service will help you
var userVal = $resource('http://localhost:8080/api/users').query()
retrun $q.resolve(userVal.$promise).then(function(r){ return r;});
Inside you controller, you are good to go
vm.users = users
I built a simple app with user authentication base on this: link
Basically, I have a userAccountService, responsible for communicating with server and login controller handling the login process.
From other controller I want to check if user is already logged in (to hide LogIn button, and show user profile instead).
So I have a navController
function navCtrl ($scope, $modal, userAccountService) {
$scope.IsUserLoggedIn = function () {
return userAccountService.isUserLoggedIn;
}
}
So in HTML I use this ng-hide="isUserLoggedIn()
my userAccountService:
app.factory('userAccountService', ['$http', '$q', userAccountService]);
function userAccountService($http, $q) {
var service = {
registerUser: registerUser,
loginUser: loginUser,
logOut: logOut,
getValues: getValues,
isUserLoggedIn: false,
accessToken: ""
};
// code ommited
function loginUser(userData) {
var tokenUrl = serverBaseUrl + "/Token";
if (!userData.grant_type) {
userData.grant_type = "password";
}
var deferred = $q.defer();
$http({
method: 'POST',
url: tokenUrl,
data: userData,
})
.success(function (data,status,headers,cfg) {
// save the access_token as this is required for each API call.
accessToken = data.access_token;
isUserLoggedIn = true;
// check the log screen to know currently back from the server when a user log in successfully.
console.log(data);
deferred.resolve(data);
})
.error(function (err, status) {
console.log(err);
deferred.reject(status);
});
return deferred.promise;
}
}
What am I doing wrong? Here's another interesting read I took inspiration from: link
You can't return a variable, but you can return a function, so create a function that returns that variable.
Try something like this, it returns your service object (you might want to put a $watch on it):
Service
function userAccountService($http, $q) {
function getData() {
return service;
}
...
}
Controller
$scope.IsUserLoggedIn = userAccountService.getData().isUserLoggedIn;
Also, you're not correctly updating the state variable from your success callback - you're creating global variables instead of using the service object properties. So, for example:
isUserLoggedIn = true;
should be:
service.isUserLoggedIn = true;