I have login and logout buttons that change dynamically after login.
<ul class="nav navbar-nav navbar-right" ng-controller="loginController">
<li class="dropdown">
<a href data-ng-show="token" ng-click="logout()"><b>Logout</b></a>
<a href data-ng-hide="token" data-toggle="dropdown"><b>Login</b></a>
</li>
</ul>
So after I login, the button changes to logout using ng-hide but the same button doesn't change back to login after I log out?
Do I have to do a scope watch on the variables? I have the code like this using ui-router and states:
myApp.config(function ($stateProvider, $urlRouterProvider, $httpProvider) {
// For any unmatched url, redirect to /login
$urlRouterProvider.otherwise("/Login");
var header = {
templateUrl: 'views/Header.html',
controller: function ($scope) {
}
};
var footer = {
templateUrl: 'views/Footer.html',
controller: function ($scope) {
}
};
// Now set up the states
$stateProvider
.state('Login', {
url: "/Login",
views: {
header: header,
content: {
templateUrl: 'views/Login.html',
controller: function ($scope) {
}
},
footer: footer
}
})
.state('LoggedIn', {
url: "/LoggedIn",
views: {
'header': header,
'content': {
templateUrl: 'views/LoggedIn.html',
controller: function ($scope) {
}
},
'footer': footer
},
data: {
requiresLogin: true
}
});
Login controller where I am storing token in localstorage(as store in my case) to update the login/logout buttons using ng-show
myApp.controller('loginController', ['$rootScope', '$scope', 'Auth', '$state', 'jwtHelper', 'store',
function ($rootScope, $scope, Auth, $state, jwtHelper, store) {
function successAuth(res) {
var decodedToken = jwtHelper.decodeToken(res.token);
store.set('jwt', res.token);
store.set('email', decodedToken.email);
if (store.get('jwt')) {
$state.go("LoggedIn");
} else {
alert("Invalid username or password");
}
}
$scope.signup = function () {
var formData = {
email: $scope.email,
password: $scope.password
};
Auth.signup(formData, successAuth, function () {
$rootScope.error = 'Failed to signup';
});
};
$scope.logout = function ()
{
Auth.logout(function () {
$state.go("Login");
console.log("token after logout is :" + store.get('jwt'));
});
};
$scope.token = store.get('jwt');
}]);
Update:
Lets say after logging-in, Its routing from "Home" to "AboutUs" page with button shown as "Logout" and then if I logout it routes back to "Home" where the button is shown as "Login"
but the problem is after logging-in if I manually route to "Home" and then I click on "Logout" button, the button stays as "Logout" even when token is set to null;
You can try by making following changes in html:
<ul class="nav navbar-nav navbar-right" ng-controller="loginController">
<li class="dropdown">
<a href ng-if="isLogin" ng-click="logout()"><b>Logout</b></a>
<a href ng-if="!isLogin" data-toggle="dropdown"><b>Login</b></a>
</li>
</ul>
And modification in javascript will be :
function successAuth(res) {
var decodedToken = jwtHelper.decodeToken(res.token);
store.set('jwt', res.token);
store.set('email', decodedToken.email);
if (store.get('jwt')) {
$state.go("LoggedIn");
$scope.isLogin = true; // Added a new scope
} else {
alert("Invalid username or password");
}
}
$scope.logout = function ()
{
Auth.logout(function () {
$state.go("Login");
$scope.isLogin = false; // Added a new scope
console.log("token after logout is :" + store.get('jwt'));
});
};
$scope.isLogin = !angular.isUndefined(store.get('jwt')); // Add this line also.
I hope it works for you.!!
Use ng-show in both the places or ng-hide in both places and on success of logout make the flag token opposite of what it currently is.That must work
Related
I gather several possible solutions none of them work for me. Can someone tell which part has error.
This is the logout function I am working on, the icon should disappear if user click logout and back to login page, but now it only lead to login page. Icon is still there because the function didn't trigger.
Everything works only AFTER I click the button in login page,
navigation.service.js
(function() {
angular
.module('TheApp')
.service('authentication', ['$window', function($window) {
var saveToken = function(token) {
$window.localStorage['auth-token'] = token;
}
var getToken = function() {
return $window.localStorage['auth-token'];
}
var logout = function() {
$window.localStorage.removeItem('auth-token');
//delete $window.localStorage['auth-token'];
//$window.localStorage.clear();
}
var isLoggedIn = function() {
var token = getToken();
//Get token from storage If token exists get payload, decode it, and parse it to JSON
if (token) {
let payload = JSON.parse($window.atob(token.split('.')[1]));
return payload.exp > Date.now() / 1000;
} else {
return false;
}
}
return {
saveToken: saveToken,
getToken: getToken,
isLoggedIn: isLoggedIn,
logout: logout
};
}])})();
navigation.directive.js
(function() {
angular
.module('TheApp')
.directive('navigation', navigation);
function navigation() {
return {
restrict: 'EA',
templateUrl: './common/directives/navigation/navigation.template.html',
controller: 'navigationCtrl as navvm'
};
}})();
navigation.controller.js
(function() {
angular
.module('TheApp')
.controller('navigationCtrl', ['$location', 'authentication', '$window', function($location, authentication, $window) {
console.log("enters navigation controller");
var vm = this;
vm.isLoggedIn = authentication.isLoggedIn();
vm.logout = function() {
console.log('test if the link work');
authentication.logout();
$location.path('/login');
};
}]);})();
navigation.template.html
<ul class="nav navbar-nav navbar-right">
<li ng-show="navvm.isLoggedIn">
<a href="" ng-click="navvm.logout()">
<span aria-hidden="true" class="glyphicon glyphicon-log-out"></span></a></li>
</ul>
Thanks.
Solution(UPDATE):
A careless situation, I omit to put the controller link inside index.html (main template page). So if anyone working on the similar project, the code above should works without errors.
I am developing a web application by using AngularJS framework for my frontend. For my login page, I have to prevent user browse to other page except login page and registration. But the code that what I did now, prevent user navigate to registration page also. The following is my code. How can I solve this problem in order that enable user to browse to login page and registration page only if the user without login.
.run(function ($rootScope, $state, AuthService, AUTH_EVENTS) {
$rootScope.$on('$stateChangeStart', function (event,next, nextParams, fromState) {
if ('data' in next && 'authorizedRoles' in next.data) {
var authorizedRoles = next.data.authorizedRoles;
if (!AuthService.isAuthorized(authorizedRoles)) {
event.preventDefault();
$state.go($state.current, {}, {reload: true});
$rootScope.$broadcast(AUTH_EVENTS.notAuthorized);
}
}
if (!AuthService.isAuthenticated()) {
if (next.name !== 'login') {
event.preventDefault();
$state.go('login');
}
}
});
you can achive this by adding one boolean parameter in data property of .state, let say requiresAuth and check that also in .run block;
below are pseudo code for that
in .config block
$stateProvider
.state("register", {
url: '/register',
templateUrl: 'register.html',
controller:'UserController',
controllerAs: 'vm',
data: {
requiresAuth: false,
pageTitle: 'Register'
}
})
.state("dashboard", {
url: '/dashboard',
templateUrl: 'dashboard.html',
controller:'OtherController',
controllerAs: 'vm',
data: {
requiresAuth: true,
pageTitle: 'Dashboard',
authorizedRoles: ['WHATEVER_ROLE']
}
});
and in .run block
var stateChangeStart = $rootScope.$on('$stateChangeStart', function(event, toState, toParams) {
if (AuthService.isAuthenticated()) {
// if user trying to access register/forgot page after login than redirect to dashboard
if (!toState.data.requiresAuth) {
event.preventDefault();
$rootScope.$broadcast(AUTH_EVENTS.notAuthorized);
}
// user is not authenticated and trying to access page which is not permissible than send back to dashboard
if (angular.isDefined(toState.data.authorizedRoles)) {
var roles = toState.data.authorizedRoles;
AuthService.isAuthorized(roles).catch(function() { // NOTE: here we are only handling with .catch block
event.preventDefault();
$rootScope.$broadcast(AUTH_EVENTS.notAuthorized);
});
}
}
// user is not authenticated than redirect to login
else if (toState.data.requiresAuth) {
event.preventDefault();
$rootScope.$broadcast(AUTH_EVENTS.notAuthenticated);
}
});
var notAuthenticated = $rootScope.$on(AUTH_EVENTS.notAuthenticated, function() {
$log.warn('not authenticated');
$state.go('login', null, {});
return;
});
var notAuthorized = $rootScope.$on(AUTH_EVENTS.notAuthorized, function() {
$log.warn('not authorized');
$state.go('dashboard');
return;
});
// DO NOT forget to destroy
$rootScope.$on('$destroy', notAuthenticated);
$rootScope.$on('$destroy', notAuthorized);
$rootScope.$on('$destroy', stateChangeStart);
I'm using Angular 1.5 Component in my application. I have a root component for configuration my routes:
module.component("myApp", {
templateUrl: "/app/components/my-app.component.html",
$routeConfig: [
{ path: "/list", component: "myList", name: "List" },
{ path: "/login", component: "login", name: "Login" },
{ path: "/**", redirectTo: [ "List" ] }
]
});
I also have a component called login-partial inside my-App component for showing login for logout menu:
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li>
Login
</li>
</ul>
</div><!--/.nav-collapse -->
Now, inside my login-conponent's controller I want to change login-partial template (changing the login item to logout):
function loginController(account, $location) {
var model = this;
var onComplete = function (data) {
if (data === "ok") {
$location.url('/list');
}
};
model.login = function (userName, password) {
account.login(userName, password)
.then(onComplete, onError);
};
};
module.component("login", {
templateUrl: "/app/components/login.component.html",
controllerAs: "model",
controller: ["account", "toaster", "$location", loginController]
});
With directives we could raise an event using $scope.$emit() and $scope.$on(). But as far as I know the new component introduced in Angular 1.5 doesn't support events.
Is it possible with components? Any suggestion as to how one would achieve doing this?
I solved the problem using $rootScope and dispatch an event from loginController:
function loginController(account, toaster, $location, $rootScope) {
var model = this;
var onComplete = function (data) {
if (data === "ok") {
$rootScope.$emit('success', { data: true });
$location.url('/list');
}
// other code
};
var onError = function (reason) {
// other code
};
model.login = function (userName, password) {
account.login(userName, password)
.then(onComplete, onError);
};
};
Then inside partialLoginController I listen to that event:
function partialLoginController($rootScope) {
var model = this;
model.isLoggedIn = false;
$rootScope.$on('success', function (event, args) {
model.isLoggedIn = args.data;
console.log(args.data);
});
};
The use case is to change login button to text "logged in as xxx" after authentication.
I have devided my page to 3 views: header, content, footer. The login button is in the header view. When I click login, it transits to "app.login" state, and the content view changes to allow user input username and password.
Here's the routing code:
app.config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('app', {
url: '/',
views: {
'header': {
templateUrl: 'static/templates/header.html',
controller: 'AppController'
},
'content': {
templateUrl: 'static/templates/home.html',
controller: 'HomeController'
},
'footer': {
templateUrl: 'static/templates/footer.html',
}
}
})
.state('app.login', {
url: 'login',
views: {
'content#': {
templateUrl : 'static/templates/login.html',
controller : 'LoginController'
}
}
})
The html template has code like this:
<li><span ng-if='loggedIn' class="navbar-text">
Signed in as {{currentUser.username}}</span>
</li>
LoginController set a $scope.loggedIn flag to true once authentication succeeded, but how can I populate that flag to the header view?
As I understand it I can't just use $scope.loggedIn in the html template as above because the $scope is different in two controllers. I know if LoginController is a child of AppController, then I can call $scope.$emit in LoginController with an event and call $scope.$on in AppController to capture it. But in this case the two controllers are for different views, how can I make them parent-child?
I know I can use $rootScope but as I'm told polluting $rootScope is the last resort so I'm trying to find a best practise. This must be a very common use cases so I must be missing something obvious.
You can use a factory to handle authentication:
app.factory( 'AuthService', function() {
var currentUser;
return {
login: function() {
// logic
},
logout: function() {
// logic
},
isLoggedIn: function() {
// logic
},
currentUser: function() {
return currentUser;
}
};
});
Than can inject the AuthService in your controllers.
The following code watches for changes in a value from the service (by calling the function specified) and then syncs the changed values:
app.controller( 'AppController', function( $scope, AuthService ) {
$scope.$watch( AuthService.isLoggedIn, function ( isLoggedIn ) {
$scope.isLoggedIn = isLoggedIn;
$scope.currentUser = AuthService.currentUser();
});
});
In such cases I typically opt to use a service to coordinate things. Service's are instantiated using new and then cached, so you effectively get a singleton. You can then put in a simple sub/pub pattern and you're good to go. A basic skeleton is as follows
angular.module('some-module').service('myCoordinationService', function() {
var callbacks = [];
this.register = function(cb) {
callbacks.push(cb);
};
this.send(message) {
callbacks.forEach(function(cb) {
cb(message);
});
};
}).controller('controller1', ['myCoordinationService', function(myCoordinationService) {
myCoordinationService.register(function(message) {
console.log('I was called with ' + message);
});
}).controller('controller2', ['myCoordinationService', function(myCoordinationService) {
myCoordinationService.send(123);
});
Do you use any serivce to keep logged user data? Basically serivces are singletons so they are good for solving that kind of problem without polluting $rootScope.
app.controller('LoginController', ['authService', '$scope', function (authService, $scope) {
$scope.login = function(username, password) {
//Some validation
authService.login(username, password);
}
}]);
app.controller('HeaderController', ['authService', '$scope', function (authService, $scope) {
$scope.authService = authService;
}]);
In your header html file:
<span ng-if="authService.isAuthenticated()">
{{ authService.getCurrentUser().userName }}
</span>
I have a login controller
myApp.controller('LoginCtrl', ['$scope', '$http','$location', function($scope, $http, $location) {
$scope.login = function() {
var data = JSON.stringify({user: $scope.user.id, password: $scope.user.password, type: "m.login.password"});
$http.post("http://localhost:8008/_matrix/client/api/v1/login", data).then(function mySuccess(details){
$http.post('/login',details.data).success(function(response){
console.log(response);
});
$location.path('home');
}, function myError(err) {
console.log(err);
alert("Invalid username/password")
});
};
}]);
and a home controller
myApp.controller('HomeCtrl', ['$scope', '$http','$location', function($scope, $http, $location) {
console.log("Hello World from home controller");
var refresh = function() {
$http.get('/roomNames').success(function(response) {
console.log("I got the data I requested");
$scope.roomNames = response;
});
}
refresh();
}]);
As seen, if the login details are correct, the LoginCtrl changes the route to be home.html.
However, when I run the application and login successfully, the HomeCtrl is supposed to make a get request to the server for data for the logged in user.
What I want is this data to be loaded as a list in the home.html. Here's my home.html
<h3 class="subHeader"> Rooms </h3>
<ul class="nav nav-sidebar">
<!-- <li class="active">Overview <span class="sr-only">(current)</span></li> -->
<li ng-repeat="room in roomNames"><a>{{room}}</a></li>
</ul>
However, on successful login, the roomNames variable is empty initially. As soon as I refresh the page, the html list gets populated.
How do I ensure the list is populated as soon as the home.html page opens?
try using the success and errorcallbacks from $http, it would be better to place this functionality into a factory
myApp.controller('HomeCtrl', ['$scope', '$http', '$location',
function($scope, $http, $location) {
console.log("Hello World from home controller");
var refresh = function() {
$http.get('/roomNames')
.then(function successCallback(response) {
$scope.roomNames = response;
}, function errorCallback(response) {
//output an error if failed?
});
}
refresh();
}
]);
This is because, you move on to home page before your server you access. Therefore, on the home page, you will not get the data, and get them after the refresh.
Do redirected to the page only after successful authentication.
myApp.controller('LoginCtrl', ['$scope', '$http','$location', function($scope, $http, $location) {
$scope.login = function() {
var data = JSON.stringify({user: $scope.user.id, password: $scope.user.password, type: "m.login.password"});
$http.post("http://localhost:8008/_matrix/client/api/v1/login", data).then(function mySuccess(details){
$http.post('/login',details.data).success(function(response){
console.log(response);
$location.path('home'); // Redirect after success login
})
.catch(function(data){
console.error("Error in login",data);
});
}, function myError(err) {
console.log(err);
alert("Invalid username/password")
});
};
}]);