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);
});
};
Related
I'd like to load a template using Angular controller. I have created everything needed:
routes.config.js
index.component.js
index.config ( here is the config of the parent controller )
html
js controller
connected with php controller
I already created templates and controllers exactly the same way and I have no idea what am I doing wrong. It's interesting that when I open elements tab in the browser I can see the <statistic-test></statistic-test> tags, and url also change. If I change <statistic-test></statistic-test> to another tag which is already used in another controller then it loads.
test-question.component.js
class TestQuestionController{
constructor($scope, $state,$stateParams, $compile, DTOptionsBuilder, DTColumnBuilder, API){
'ngInject';
this.API = API
this.$state = $state
if ($stateParams.mode) {
this.mode=$stateParams.mode
}
let Statics = API.service('show', API.all('statisticQuestion'))
this.userlistaction = "app.statisticanswer.userlist"
}
$onInit(){}
}
export const TestQuestionComponent = {
templateUrl: './views/app/components/statistic-question-userlist/test-question.component.html',
controller: TestQuestionController,
controllerAs: 'vm',
bindings: {}
}
I also have an html named test-question.component.html
routes.config.statistic.question.js
export function RoutesConfigStatisticQuestion ($stateProvider) {
'ngInject'
$stateProvider
.state('app.statisticquestion', {
url: '/statistic-question',
data: {
auth: true
},
views: {
'main#app': {
template: '<statistic-question></statistic-question>'
}
},
ncyBreadcrumb: {
label: 'Kérdések statisztika',
}
})
.state('app.statisticquestion.test', {
url: '/statistic-test/',
data: {
auth: true
},
views: {
'main#app': {
template: '<statistic-test></statistic-test>'
}
},
params: {
contentsId : null,
alerts: null
},
ncyBreadcrumb: {
label: 'Címke szerkesztése',
parent: 'app.statisticquestion'
}
})
If you have ANY idea how to solve this please share it with me.
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 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
I am trying to get a header to update after login. I have used both $on and $watch in this effort to no avail. When I refresh it works correctly. Code is as follows below.
header.html (missing excess nav bar code for simplicity)
<li><a ng-href="#/login" ng-hide="showMenu">Login</a></li>
<li><a ng-href="#/signup" ng-hide="showMenu">Signup</a></li>
<li>Logout</li>
app.js
$stateProvider
.state('app', {
url: '',
views: {
'header': {
templateUrl: 'views/partials/_header.html',
controller: 'HeaderCtrl'
}
}
})
header.js (The broadcast fires correctly as demonstrated by the console.logs)
angular.module('urbinsight')
.controller('HeaderCtrl', function ($scope, $rootScope, $state, $location, UserAuthFactory, AuthFactory) {
$scope.logout = function () {
UserAuthFactory.logout();
$rootScope.$broadcast('loginStateChange');
$location.path('/');
};
$scope.showMenu = AuthFactory.loggedStatus();
$rootScope.$on('loginStateChange', function(){
console.log($scope.showMenu)
$scope.showMenu = AuthFactory.loggedStatus();
console.log($scope.showMenu)
})
})
authService
angular.module('urbinsight.services')
.factory('AuthFactory', function ($window) {
var isLogged = false;
return {
check: function() {
if ($window.sessionStorage.token && $window.sessionStorage.user) {
isLogged = true;
} else {
isLogged = false;
delete this.user;
}
},
loggedStatus: function() {
return isLogged;
},
changeLoggedStatus: function() {
isLogged = !(isLogged);
}
};
})
login function + broadcast
login.submit = function () {
var username = user.username,
password = user.password;
if (username !== undefined && password !== undefined) {
UserAuthFactory.login(username, password).success(function(data) {
$rootScope.showMenu = true
// AuthFactory.isLogged = true;
AuthFactory.changeLoggedStatus();
AuthFactory.user = data.user.username;
AuthFactory.userRole = data.user.role;
$rootScope.$broadcast('loginStateChange');
$window.sessionStorage.token = data.token;
$window.sessionStorage.user = data.user.username;
$window.sessionStorage.userRole = data.user.role;
$location.path('/');
}).error(function(status) {
$window.alert('Oops something went wrong!');
});
} else {
$window.alert('Invalid credentials');
}
};
Please tell me what I am doing wrong.
You have set the "$rootScope.showMenu = true" in your login controller. But in your header controller you also have "$scope.showMenu = AuthFactory.loggedStatus();"
So i would remove this line from your header controller
$scope.showMenu = AuthFactory.loggedStatus();
Since you want your header html to react to $rootscope showMenu variable directly
Currently, we have a 'Portfolio' tool in beta. Once a user logs in to the main app, if they have been given access to the beta, they can navigate to the Portfolio tool directly, without any additional login. If not, they should be redirected to a Portfolio login page (state is called portfolio.login) where they can login or contact support/sales etc. Right now I have the check in the resolve block, however $state.go('portfolio.login') seems to fetch the right partials, but doesn't render them on screen or navigate to the appropriate URL.
Code:
angular.module('portfolio.manager').config(function ($logProvider, $stateProvider) {
'use strict';
$stateProvider
.state('portfolio.manager', {
url: '/manager',
resolve: {
CheckLoggedIn: function ($state, loggedIn) {
var _loggedIn = loggedIn.checkUser();
if (!_loggedIn) {
$state.go('portfolio.login');
console.log('not authorized');
}
},
portfolioAuthService: 'portfolioAuthService',
User: function(portfolioAuthService){
return portfolioAuthService.getUser();
},
Portfolios: function (User, portfolioManagerService) {
return portfolioManagerService.getPortfolios();
}
},
views: {
'main#': {
templateUrl: 'app/portfolio/manager/portfolio-manager.html',
controller: 'PortfolioManagerCtrl'
},
'no-portfolios#portfolio.manager': {
templateUrl: 'app/portfolio/manager/partials/no-portfolios.html'
},
'create#portfolio.manager': {
templateUrl: 'app/portfolio/manager/partials/create.html'
}
}
})
I ran in the same problem days ago. Instead of using resolve, I check if the user is logged when state changes, defining run module and listening $stateChangeStart event, then check if the current state required authentication. If so, check if the user is logged in.
angular.module('portfolio.manager').config(function ($logProvider, $stateProvider) {
'use strict';
$stateProvider
.state('portfolio.manager', {
url: '/manager',
resolve: {
portfolioAuthService: 'portfolioAuthService',
User: function(portfolioAuthService){
return portfolioAuthService.getUser();
},
Portfolios: function (User, portfolioManagerService) {
return portfolioManagerService.getPortfolios();
}
},
data: {
requiredAuthentication: true
},
views: {
'main#': {
templateUrl: 'app/portfolio/manager/portfolio-manager.html',
controller: 'PortfolioManagerCtrl'
},
'no-portfolios#portfolio.manager': {
templateUrl: 'app/portfolio/manager/partials/no-portfolios.html'
},
'create#portfolio.manager': {
templateUrl: 'app/portfolio/manager/partials/create.html'
}
}
})
})
.run(run);
run.$inject = ['$rootScope','$state','loggedIn'];
function run($rootScope,$state,loggedIn){
$rootScope.$on('$stateChangeStart',function(e,toState){
if ( !(toState.data) ) return;
if ( !(toState.data.requiredAuthentication) ) return;
var _requiredAuthentication = toState.data.requiredAuthentication;
if (_requiredAuthentication && !loggedIn.checkUser() ){
e.preventDefault();
$state.go('portfolio.login', { notify: false });
console.log('not authorized');
}
return;
});
};