I have built somewhat of a login and I want the user to be kicked to the dashboard page upon successful login. As of now, everything works as expected except for the model doesn't update until I reload the Dashboard page.
I wrote a service to handle the $http requests:
abcApp.service('Login', function($q, $http){
return({
login:login
})
var headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
function login(login){
var formVals = {
username: login.username.$modelValue,
password: login.password.$modelValue,
remember: login.remember.$modelValue,
}
var request = $http.post('assets/php/do_login.php', formVals, null, headers);
return( request.then( handleSuccess, handleError ) );
}
function handleSuccess( response ) {
return( response.data );
}
function handleError( response ) {
if (
! angular.isObject( response.data ) ||
! response.data.message
) {
return( $q.reject( "An unknown error occurred." ) );
}
// Otherwise, use expected error message.
return( $q.reject( response.data.message ) );
}
});
Here is the controller that should handle logging in:
$scope.doLogin = function (){
Login.login($scope.login_form)
.then(function(data){
if(data.status == 'success'){
$scope.currentUser = data.user;
$scope.msg = 'Welcome '+data.user.realname;
$scope.userId = data.user.id;
$scope.loggedIn = true;
}
}).then(function () {
return $timeout(function () {
$state.go(
'dashboard',
{},
{reload: true}
);
}, 250);
});
};
Originally I had this all in a controller, then I split it into a service and controller. I have tried $scope.$apply, but I get an inprog error. How can I get the model to update without reloading?
Your controller and the variables in it will be served from the cache.
There are a few ways you can solve this. First is disable caching in the state definition...
$stateProvider.
state('dashboard', {
url: '/dashboard',
cache: false,
... but I have had situations where that doesn't work. You can also set a trigger on the state change (either $stateChangeStart on the login controller or $stateChangeSuccess on the dashboard controller):
$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
// reload stuff
});
You can also set these triggers on the $rootScope if you want them to fire at state changes through all your controllers.
Related
I am using Laravel angularjs
I am using this package https://github.com/andbet39/tokenAuth
it's working fine but my problem is without login i can go to any page also once i reload the page user name is disabled
I don't know what is the problem here
app.js
var app = angular.module('todoApp', ['ui.router', 'satellizer'])
.config(function($stateProvider, $urlRouterProvider, $authProvider,$provide) {
$authProvider.loginUrl = '/api/authenticate';
$urlRouterProvider.otherwise('/login');
$stateProvider
.state('login', {
url: '/login',
templateUrl: '/js/tpl/login.html',
controller: 'AuthController'
})
.state('register', {
url: '/register',
templateUrl: '/js/tpl/register.html',
controller: 'AuthController'
})
.state('todo', {
url: '/todo',
templateUrl: '/js/tpl/todo.html',
controller: 'TodoController'
});
function redirectWhenLoggedOut($q, $injector) {
return {
responseError: function (rejection) {
var $state = $injector.get('$state');
var rejectionReasons = ['token_not_provided', 'token_expired', 'token_absent', 'token_invalid'];
angular.forEach(rejectionReasons, function (value, key) {
if (rejection.data.error === value) {
localStorage.removeItem('user');
$state.go('login');
}
});
return $q.reject(rejection);
}
}
}
$provide.factory('redirectWhenLoggedOut', redirectWhenLoggedOut);
});
TodoController.js
app.controller('TodoController', function($state,$http,$rootScope, $scope,$auth) {
$scope.todos=[];
$scope.newTodo={};
$scope.init = function (){
$http.get('/api/todo').success(function(data){
$scope.todos=data;
})
};
$scope.save = function(){
$http.post('/api/todo',$scope.newTodo).success(function (data) {
$scope.todos.push(data);
$scope.newTodo={};
});
};
$scope.update = function(index){
$http.put('/api/todo/'+ $scope.todos[index].id,$scope.todos[index]);
};
$scope.delete = function(index){
$http.delete('/api/todo/'+ $scope.todos[index].id).success(function(){
$scope.todos.splice(index,1);
});
};
$scope.logout = function() {
$auth.logout().then(function() {
localStorage.removeItem('user');
$rootScope.authenticated = false;
$rootScope.currentUser = null;
});
}
$scope.init();
});
AuthController.js
app.controller('AuthController', function($auth, $state,$http,$rootScope, $scope) {
$scope.email='';
$scope.password='';
$scope.newUser={};
$scope.loginError=false;
$scope.loginErrorText='';
$scope.login = function() {
var credentials = {
email: $scope.email,
password: $scope.password
}
$auth.login(credentials).then(function() {
return $http.get('api/authenticate/user');
}, function(error) {
$scope.loginError = true;
$scope.loginErrorText = error.data.error;
}).then(function(response) {
// var user = JSON.stringify(response.data.user);
// localStorage.setItem('user', user);
$rootScope.authenticated = true;
$rootScope.currentUser = response.data.user;
$scope.loginError = false;
$scope.loginErrorText = '';
$state.go('todo');
});
}
$scope.register = function () {
$http.post('/api/register',$scope.newUser)
.success(function(data){
$scope.email=$scope.newUser.email;
$scope.password=$scope.newUser.password;
$scope.login();
})
};
});
I want to redirect to login page if authandicate is falied
How to fix this ?
In angularjs 1.4+ there is no
$http.get('/api/todo').success(function(data){
$scope.todos=data;
})
What you should do instead
$http.get('/api/todo').then(function(data){
$scope.todos=data;
})
And same with this $http.post which you have below.
Also after refreshing page rootScope is deleted and that is why nickname is blank after refresh.
You probably want to store nickname in localStorage or async promise based localForage.
If you chose async localForage on login you can emit custom event with rootScope and execute some function on this event which gather nickname from localForage. You might want to execute this function in some external controller which would wrap all app so when you assign $scope.nick you will have access to it across entire app. Same with $scope.auth = true, you will be able to build your app basing on this boolean for logged in using ng-if directive.
Inject $location to your controller as function parameter and try to redirect like so
$location.path('/todo' );
or
$location.url(YOUR_URL);
Also I don't really understand why you are doing two backend call for login, one inside another. You probably should do one $http.post which would return token in response. Then you could fix and simplify your function code to
$scope.login = function() {
var credentials = {
email: $scope.email,
password: $scope.password
}
$auth.login(credentials).then(function(response) {
$rootScope.authenticated = true;
$rootScope.currentUser = response.data.user;
$scope.loginError = false;
$scope.loginErrorText = '';
}, function(error) {
$scope.loginError = true;
$scope.loginErrorText = error.data.error;
$location.path('/todo' );
});
}
However I don't know your code from $auth service.
Remember to inject $location service.
redirectWhenLoggedOut seems to be an http interceptor.
I think the idea is that you redirect when the http call was not successful. So you need to add an http interceptor that catches the http error and redirects to the login page.
$httpProvider.interceptors.push('redirectWhenLoggedOut');
Don't forget to inject the $httpProvider;
I have an angular Service:
presence.service('AuthService', function($http, PresenceURLService){
var apiURL = PresenceURLService.apiURL;
this.isLogged = false,
this.access_token = "",
this.login = function(credentials, callback){
var configura = {
headers : {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'
}
};
$http({
method:'POST',
url: apiURL+'login',
data: credentials,
config: configura
}).then(function(response){
//success
this.isLogged = response.data.response;
this.access_token = response.data.access_token;
callback(response.data);
}, function(response){
//error
callback(response.data);
});
}
});
Whenever an user tries to login, the API returns tru or false and it is stored in this.isLogged. Works fine.
I have this code on run for the app, in order to stop the state load if the user is not logged:
presence.run(function($rootScope, $location, $state, AuthService) {
$rootScope.$on( '$stateChangeStart', function(e, toState , toParams, fromState, fromParams) {
var isLogin = toState.name === "login";
if(isLogin){
return; // no need to redirect
}
console.log("State we are going to: "+toState.name);
// now, redirect only not authenticated
var logged = AuthService.isLogged;
console.log("Before load must check the AuthService isLogged var: "+logged);
if(logged === false) {
e.preventDefault(); // stop current execution
$state.go('login'); // go to login
}
});
});
In this code logged is always false. But, previously, when I call login() function, it is stored true.
Why it losses the data and how to obtain this behaviour?
This is because context in which you set isLogged is not AuthService. Read more here how this works
Try this instead:
presence.service('AuthService', function($http, PresenceURLService){
var that = this; // In order to access correct context within callback
var apiURL = PresenceURLService.apiURL;
this.isLogged = false,
this.access_token = "",
this.login = function(credentials, callback){
var configura = {
headers : {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'
}
};
$http({
method:'POST',
url: apiURL+'login',
data: credentials,
config: configura
}).then(function(response){
//success
// Use that instead of this here. As this doesn't refers to AuthService
that.isLogged = response.data.response;
that.access_token = response.data.access_token;
callback(response.data);
}, function(response){
//error
callback(response.data);
});
}
});
I've made an AuthService that should help me take care of all authentication matters like logging in, registering, getting user data from server etc.
I am looking for a solution that runs only on login, on page refresh and when triggered to refresh get the user data from the service and make it available on the controllers that i include the service to. I would like to have something like vm.user = AuthService.getUserData() and this returns the session variable from the service. Something like this:
function getUserData(){
if (session) {
return session.user;
}
return null;
}
On $rootScope.$on('$stateChangeStart' i have :
AuthService.loadSessionData();
Which translates to:
function loadSessionData() {
$http({
url: API.URL+'auth/session-data',
method: 'GET'
})
.success(function(response)
{
session = response;
})
.error(function(err){
console.log(err);
});
};
One of the issues here are that i have to set a timeout on AuthService.getUserData() because when this executes, the call that retrieves the session data from the server is not finished yet, but this is a bad practice.
Here is the complete service code http://pastebin.com/QpHrKJmb
How about using resolve? If I understood correctly you wish to have this data in your controller anyway.
You can add this to your state definitions:
.state('bla', {
template: '...'
controller: 'BlaCtrl',
resolve: {
user: ['AuthService', function(AuthService) {
return AuthService.loadSessionData();
}
}
}
also, alter your loadSessionData function: (make sure to inject $q to AuthService)
function loadSessionData() {
return $q(function(resolve, reject) {
$http({
url: API.URL + 'auth/session-data',
method: 'GET'
})
.success(function(response)
{
if (response) {
resolve(response);
} else {
reject();
}
})
.error(function(err){
reject(err);
});
})
}
Lastly, add the user object from the resolve function to you controller:
app.contoller('BlaCtrl', ['$scope', 'user', function($scope, user) {
$scope.user = user;
}]);
What does this accomplish?
In case the user does not have a valid session or an error occurs, the state change is rejected and the event $stateChangeError is thrown. You can listen (like you did for $stateChangeStart for that event and respond with a modal, redirect to an error page, or whatever.
You only pull the session for states that needs it - not for every state change.
The state is not resolved until the user data is resolved as well (either by resolve or reject).
You should call loadSessionData() in getUserData()
function getUserData(){
loadSessionData()
.success(function(response)
{
if (response) {
return response.user;
}
})
return null;
}
and
function loadSessionData() {
return $http.get(API.URL+'auth/session-data');
};
I am sure this is a configuration error, but not sure how to get round it correctly with angular.
I have my states setup like so
$stateProvider
.state('app', {
url: '',
abstract: true,
templateProvider: ['$templateCache', function ($templateCache) {
return $templateCache.get('app/main/main.html');
}],
controller: 'MainController',
resolve: {
Pages: ['PageFactory', function (PageFactory) {
return PageFactory.getAll();
}]
}
})
.state('app.home', {
url: '/home',
views: {
'content#app': {
templateProvider: ['$templateCache', function ($templateCache) {
return $templateCache.get('app/page/page.html');
}],
controller: 'PageController'
}
},
resolve: {
Page: ['$stateParams', 'PageFactory', function ($stateParams, PageFactory) {
return PageFactory.get($stateParams.id);
}],
ModuleData: function () {
return {};
},
Form: function () {
return {};
}
}
});
Now my problem is that if PageFactory.get($stateParams.id) fails, then the page is reloaded, and reloaded and reloaded.
Here is a sample from PageFactory, which as you can see returns a promise
angular.module('app').factory('PageFactory', ['$http', '$q', function ($http, $q) {
var urlBase = 'api/page';
var factory = {};
factory.getAll = function () {
var $defer = $q.defer();
$http.get(urlBase)
.success(function (data) {
$defer.resolve(data);
})
.error(function (error) {
$defer.reject(error);
});
return $defer.promise;
};
factory.get = function (id) {
var $defer = $q.defer();
$http.get(urlBase + '/' + id)
.success(function (data) {
$defer.resolve(data);
})
.error(function (error) {
$defer.reject(error);
});
return $defer.promise;
};
}]);
It is possible to restrict the number of times the resolve is attempted, or should i have set this up differently in the first place?
I noticed this when moving the site from 1 place to another and needed to specify a base href in my index.html page. Because the $http was trying to connect to a url that didnt exist, it just kept on trying and trying and trying, which if someone left it would harn our web servers performance.
My state app is my route state (abstract state) and the user defaults to app.home uponing entering the site. I guess this is why it just keeps retrying the resolve?
We also had infinite loops when there were errors in resolves, so we ended up having more logic in the $stateChangeError event handler. Since we have this, we had no more troubles with loops.
See how we check where we were trying to go to and if we failed while going to a home state, we do not try again and redirect to a simple error state instead.
Here our example, this is setup in our main module's run method:
$rootScope.$on("$stateChangeError", function (event, toState, toParams, fromState, fromParams, error) {
console.log('Error on StateChange from: "' + (fromState && fromState.name) + '" to: "'+ toState.name + '", err:' + error.message + ", code: " + error.status);
if(error.status === 401) { // Unauthorized
$state.go('signin.content');
} else if (error.status === 503) {
// the backend is down for maintenance, we stay on the page
// a message is shown to the user automatically by the error interceptor
event.preventDefault();
} else {
$rootScope.$emit('clientmsg:error', error);
console.log('Stack: ' + error.stack);
// check if we tried to go to a home state, then we cannot redirect again to the same
// homestate, because that would lead to a loop
if (toState.name === 'home') {
return $state.go('error');
} else {
return $state.go('home');
}
}
});
The common way to use resolve with promises is to use $q.defer()
Pages: ['PageFactory', '$q', function (PageFactory, $q) {
var deffered = $q.defer();
PageFactory.getAll()
.success(function(data) {
deffered.resolve(data);
})
.error(function(data) {
deffered.reject();
})
return deferred.promise;
}]
This will reject the the state change if it fails, or you can do whatever when it fails. And it passes the data through if it succeeds.
Also see this post about the same thing.
Angular ui-router get asynchronous data with resolve
There is a hidden page in my angular app, so only special id user can access it. To implement it I send HTTP get request to the server (because only the server knows that special id) when state changes to that hidden state.
And if server responses to me with an error message then I prevent state changing process, in other case user goes to that special page.
Here is the code I am using:
angular
.run(["$rootScope", "$location", "$state", "mySvc", function ($rootScope, $location, $state, mySvc) {
var id = mySvc.getId();
$rootScope.$on( '$stateChangeStart', function(event, toState , toParams, fromState, fromParams) {
if(toState.name === "specialstate") {
mySvc.check(id)
.then(function(result) {
if(result.error !== 0) {
event.preventDefault();
}
}, function(error) {
event.preventDefault();
});
}
});
}]);
Function in service:
function check(id) {
var deferred = $q.defer();
$http({
url: "/api/url",
method: "GET",
headers: {'Content-Type': 'application/json'}
}).
then(function (result) {
console.log(result);
if(result.data.error === 0) {
deferred.resolve(result.data);
}
else {
deferred.reject(result.data);
}
}, function (error) {
deferred.reject(error);
});
return deferred.promise;
};
Everything is right, except one thing: the state is being changed without waiting for request result. I think I should use resolve, but I do not know how to use it properly.
Thanks.
ui-router supports a resolve object that defines aset of data that should be resolved (from server, for instance), before the state transition is allowed.
.state('hidden', {
url: '/hidden',
...
resolve: {
data: function(mySvc) {
var id = mySvc.getId();
return mySvc.check(id).then(function(result) {
if (result.error !== 0) {
throw new Error("not allowed");
}
});
}
}
});
The data object defined would be available to your controller in that hidden state, and only in case it returns a 2xx status code and there's an error property equal to 0.
If data is resolved, then a state change occurs and a controller is instantiated. In case of rejection, none of this takes place.
As a personal note, I find that a great device to handle authorization.