Hello I'm learning Angular and I'm trying to do a global navigation bar across all angular (1.5) app.
So, to do that navigation bar I've created a directive
navigation.html
<nav class="navbar navbar-default" id="header">
...
non important tags
...
<li ng-if="!session.logged">Enter</li>
<li ng-if="session.logged"><a ng-click="logout()">Logout</a></li>
</nav>
navigation.js
export default function navigation(Auth, Session) {
return {
restrict: "E",
replace: true,
scope: {},
templateUrl: directivesPath + "navigation.html",
link: function(scope, element, attrs) {
scope.session = Session.sessionData();
scope.logout = function() {
Auth.logout();
scope.session = Session.sessionData();
window.location = "#/";
}
}
}
}
I've put than directive in an index.html like this
<!DOCTYPE html>
<html lang="es" ng-app="movieApp">
<head>
...
</head>
<body>
<navigation></navigation>
<div ui-view></div>
</body>
</html>
And I have controllers, for example these two
UsersCtrl.$inject = ["$scope", "$http", "Session"];
export function UsersCtrl($scope, $http, Session) {
$http.get('http://localhost:3000/users').success(function (data) {
console.log(data);
$scope.users = data.data;
$scope.session= Session.sessionData();
});
}
export default angular.module('movieControllers').controller("LoginCtrl", ["$scope", "$rootScope", "Auth", "Session",
function($scope, $rootScope, Auth, Session) {
console.log("User is logged? " + Auth.loggedIn());
if (Auth.loggedIn() === true) {
window.location = "#/users/" + Auth.currentUser.username;
}
const button = document.getElementById('login');
button.addEventListener("click", function() {
const username = document.login.username.value;
const password = document.login.password.value;
Auth.login({username: username, password: password}, function() {
$scope.session= Session.sessionData();
window.location = "#/users/" + username;
// $scope.$digest();
});
});
}]);
The Auth and Session services just make a call to the backend, keep the
user data and retrieve that user data.
The problem is that when I login, the app redirect to the show of the user, but
the nav still showing <li>Enter</li>, but if I refresh the browser the nav show
correctly the <li>Logout</li> ant the <li>Enter</li> is hidding.
If I put the navigation directive inside the template of a controller it
behaves correctly.
What I'm doing incorrectly?
(Sorry for my English, I'm still learning the language too)
Your directive does not watch for session changes. When you assign your session object in your link function you may wrap it in $apply call to inform about the changes, like that:
function navigation(Auth, Session) {
return {
restrict: "E",
replace: true,
scope: {},
templateUrl: directivesPath + "navigation.html",
link: function(scope, element, attrs) {
scope.$apply(function() {
scope.session = Session.sessionData();
});
scope.logout = function() {
Auth.logout();
scope.$apply(function() {
scope.session = Session.sessionData();
});
window.location = "#/";
}
}
}
}
Better yet, look how isolate scope in directives work (https://docs.angularjs.org/guide/directive) and check $watch function as well.
Related
I am trying to communicate between two directives. I tried the service way, it didn't work. Maybe I am doing something wrong.
<list-data testing="trial" template-url="grid.html" link-passed="data.json"></list-data>
My directive and service:
app.directive('listData', function($http, serVice){
return {
restrict: 'E',
scope: {
testing: '#',
linkPassed: '#'
},
templateUrl: function(elem,attrs) {
return attrs.templateUrl || 'some/path/default.html'
},
link: function($scope){
var url = 'js/' + $scope.linkPassed;
$http.get(url).then(success, error);
function success(data){
$scope.iconUrl = data.data;
}
function error(response){
console.log(response)
}
$scope.tryingToClick = function(icon){
serVice=icon.name;
console.log(serVice)
}
}
};
});
app.directive('render', function(serVice){
return {
restrict: 'E',
template: '{{rendering}}',
link: function($scope){
$scope.rendering = serVice.name;
console.log(serVice)
}
};
});
app.factory('serVice', function(){
return{items:{}};
})
grid.html is just a simple grid layout where I am trying to show the data in grid.
<div class="col-sm-6 grid" ng-repeat="icon in iconUrl">
<p ng-click="tryingToClick(icon)">{{icon.iconUrl}}</p>
</div>
I need to pass the data when I click the function tryingToClick and the icon passes to the render directive. I cannot use $rootscope here, nor make new controllers. I will be using the logic here in a pretty big enterprise app, just that I made a very simple version of it on my localhost, just to get the logic right.
Your service doesn't look quite right. I'd use
app.factory('serVice', function() {
var settings = {};
// optionally set any defaults here
//settings.name = 'me';
return settings;
});
and you're not setting the name property of the service here:
serVice=icon.name;
It should be
serVice.name = icon.name;
given that you're looking for the name property later: $scope.rendering = serVice.name;
What do you mean by not creating more controllers? Do you mean that you can't create more on the app or that you can't use controllers within the directives?
From what I understood of your question I threw this codepen together for experimentation http://codepen.io/ihinckle/pen/JWGpQj?editors=1010
<div ng-app="directiveShare">
<directive-a an-array="[1,2,3,4,5]"></directive-a>
<directive-b></directive-b>
</div>
angular.module('directiveShare', [])
.directive('directiveA', function(){
return {
restrict: 'E',
scope: {
anArray: '<'
},
controller: function($scope, aService){
$scope.clicked = aService.setIcon;
},
template: `
<ul>
<li ng-repeat="item in anArray" ng-click="clicked(item)">item</li>
</ul>`
}
})
.directive('directiveB', function(){
return {
controller: function($scope, aService){
$scope.displayIcon = aService.getIcon;
},
template: `
<h1>{{displayIcon()}}</h1>
`
}
})
.factory('aService', function(){
var srvc = {};
srvc.setIcon = function(x){
srvc.icon = x;
};
srvc.getIcon = function(){
if(srvc.icon){
return srvc.icon;
}else {
return '';
}
};
return srvc;
});
I used getters and setters in the service and controllers on the directives to expose the functions.
I just started learning the MEAN stack and am trying to build an authentication page from scratch (even though I know I could use the out of the box authentication but am doing this to have a background understanding) but am finding it a bit tricky to update texts on my navbar.
My index.html looks like this;
<nav class="navbar navbar-default navbar-fixed-top" ng-controller="HeadController">
<div class = "container-fluid">
<ul class="nav navbar-nav navbar-right">
<li>{{user.name}}</li>
</ul>
</div>
</nav>
<div class="container" ng-view></div>
My app.js looks like this;
var app = angular.module('app',['ngRoute'])
.service("userService", function () {
this.user = {};
this.getUser = function () {
return this.user;
};
this.setUser = function (user) {
this.user = user;
};
})
.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/login', {
templateUrl: 'partials/login.html',
controller: 'LoginCtrl as login'
})
.when('/user/profile',{
templateUrl: 'partials/profile.html',
controller: 'ProfileCtrl as profile'
})
.otherwise({
redirectTo: '/login'
});
}]);
app.controller("HeadController", [
'$scope',
'userService',
function ($scope, userService) {
$scope.user = userService.getUser();
}]);
app.controller("LoginCtrl", [
"$location",
"userFactory",
"userService",
function ($location, userFactory, userService) {
var login = this;
login.user = {
username: '',
password: '',
isRemember: true
};
login.login = function () {
userFactory.login(login.user)
.success(function (user) {
userService.setUser(user);
$location.path("/user/profile");
})
.error(function (err) {
console.log(err);
});
};
}]);
finally, my userFactory.js looks like this;
function login(user) {
return $http({
url: '/api/login/',
method: "POST",
data: user,
headers: {
'Content-Type': 'application/json'
}
});
}
return{login:login};
Whenever I login, I expect {{user.name}} be updated but that doesn't happen. I don't know how to get this done. What can I do to make this happen?
The problem is that userService.setUser(user) replaces the services's reference in memory to the "user" object after the previous user object (and reference in memory) had been bound to the $scope of HeadController. There are few ways to fix this:
1. Mutate (instead of replacing) the original object with setUser:
this.setUser = function(newUserData) {
angular.extend(this.user, newUserData);
};
2. Make a dynamic reference to "name" in your template
<li>{{ userService.getUser().name }}</li>
3. (the preferred choice IMO, although definitely not the easiest to implement) Make custom event handlers in your user service so that when you call setUser, callback "listeners" will get invoked. This would allow you to do something like this in HeadController:
userService.onUserUpdate(function(newUser) {
$scope.user = newUser; // or userService.getUser();
});
and to keep the template/view cleaner and more declarative, the way you currently have it:
<li>{{ user.name }}</li>
This can be implemented like this. In userService add:
var privateUserUpdateListeners = [];
this.onUserUpdate = function(cb)
if (angular.isFunction(cb)) {
privateUserUpdateListeners.push(cb);
// Return a callback that can be used to deregister the listener.
// In production code, you may want to wrap this function
// to ensure that it may only get called once.
return function() {
var index = privateUserUpdateListeners.indexOf(cb);
privateUserUpdateListeners.splice(index, 1);
};
}
};
this.broadcastUserUpdate = function(user) {
privateUserUpdateListeners.forEach(function(cb) {
cb(user);
});
};
Then in setUser you could add a broadcast like:
this.setUser = function(user) {
this.user = user;
this.broadcastUserUpdate(user);
};
That should do the trick.
My problem is quite specific so I haven't been able to find an answer for this particular scenario anywhere, I did manage to get the functionality I require but would like to know if there is a better way of doing it. I will start by explaining the problem.
In index.html I have the following:
<html>
...
<lumx-navbar></lumx-navbar>
<div class="wrap">
<div ui-view></div>
</div>
...
</html>
lumxnavbar is a directive for the navigation bar of the application
nav.js
module.exports = function(appModule) {
appModule.directive('lumxNavbar', function(UserFactory, $window, $rootScope) {
return {
template: require('./nav.html'),
controller: function($scope) {
$scope.nav = require('../../static-data/nav.json');
$scope.user = $rootScope.user;
$scope.logout = function() {
UserFactory.logout();
$scope.user = '';
$window.location.href = '/#/login';
};
}
};
});
};
nav.html
<header>
...
<span ng-show="user" class="main-nav--version">Logged in as: {{user}}</span>
...
</header>
So the application starts with a login page at which point there is no user variable available anywhere. Once the user logs in there will be a user returned from a service.
my routes look like this (I am using angular-ui-router)
$stateProvider
.state('anon', {
abstact: true,
template: '<ui-view/>',
data: {
access: false
}
})
.state('anon.login', {
url: '/login',
template: require('../views/login.html'),
controller: 'LoginCtrl'
});
$stateProvider
.state('user', {
abstract: true,
template: '<ui-view/>',
data: {
access: true
}
})
.state('user.home', {
url: '/home',
template: require('../views/home.html'),
controller: 'HomeCtrl'
})
...
so the idea is that once the user logs in the navbar will change from
to this:
the only way I have found to accomplish this reliably is to do the following
instantiate a variable in $rootScope
appModule.run(function($rootScope, $location, UserFactory, $state) {
$rootScope.user = UserFactory.getUser();
...
then set the same variable from the login controller
...
$scope.login = function (username, password) {
UserFactory.login(username, password).then(function success(response) {
$rootScope.user = response.data.user.username;
$state.go('user.home');
}, handleError);
};
...
so this will update the navbar because of this line in the directive $scope.user = $rootScope.user; but my question is whether there is a better way of doing this without using $rootScope or would this be a suitable use for it?
Any input would be appreciated...:)
I have following routing with athentication, which is done via a PHP-Script and MySQL:
app.config
app.config(['$routeProvider',
function ($routeProvider) {
$routeProvider.
when('/login', {
title: 'Login',
templateUrl: 'partials/login.html',
controller: 'authCtrl'
})
.when('/logout', {
title: 'Logout',
templateUrl: 'partials/login.html',
controller: 'logoutCtrl'
})
.when('/dashboard', {
title: 'Dashboard',
templateUrl: 'partials/dashboard.html',
controller: 'authCtrl'
})
.otherwise({
redirectTo: '/login'
});
}])
.run(function ($rootScope, $location, Data) {
$rootScope.$on("$routeChangeStart", function (event, next, current) {
$rootScope.authenticated = false;
Data.get('session').then(function (results) {
if (results.uid) {
$rootScope.authenticated = true;
$rootScope.uid = results.uid;
$rootScope.name = results.name;
$rootScope.email = results.email;
} else {
var nextUrl = next.$$route.originalPath;
if (nextUrl == '/signup' || nextUrl == '/login') {
} else {
$location.path("/login");
}
}
});
});
});
authCtrl
app.controller('authCtrl', function ($scope, $rootScope, $routeParams, $location, $http, Data) {
$scope.login = {};
$scope.signup = {};
$scope.doLogin = function (customer) {
Data.post('login', {
customer: customer
}).then(function (results) {
Data.toast(results);
if (results.status == "success") {
$location.path('dashboard');
}
});
};
$scope.logout = function () {
Data.get('logout').then(function (results) {
Data.toast(results);
$location.path('login');
});
}
});
Now I want to change the navigation depending on the login-status. If user is logged in there should bei a logOUT-Button and a link to the dashboard. If the user isn't logged in it should look like this
sample for unlogged-in user
<header id="navigation">
<nav id="main">
<ul>
<li id="login"><i class="fa fa-power-off"></i> Login</li>
</ul>
</nav>
</header>
<main ng-view>
Content goes here
</main>
What is the best way for creating the navigation? Should it be a seperate template?
When things get complex, I'd rather use ui-router or maybe angular new router (haven't try this one yet) since they support multiple views in the same page. Thus nav becomes its own view, content its own view, etc. And to communicate between the views I'll use message passing with data in either $rootScope or some kind of shared state service.
So something like this... in the beginning, the login shared state is set to logged out. As the last part of login functionality, I'd set the login shared state and set it to logged in. Like I said, I rather make this a shared state service call since I can have it to also do $rootScope.$broadcast some sort of onLoginStateChange and pass the new value there.
Then I'd set up my nav view to listen to this onLoginStateChange using $scope.$on and set its internal view model state in its controller and bind that to ng-if directive so it will display Login if false, or Logout if true (logged in).
I have a single-page AngularJS app, working with Express, node.js, and MongoDB via Mongoose. Using Passport for user management/authentication.
I'd like the navbar items to change based on whether a user is logged in or not. I'm having trouble figuring out how to implement it.
I find out if a user is logged in through an http request:
server.js
app.get('/checklogin',function(req,res){
if (req.user)
res.send(true);
else
res.send(false);
On the front end, I have a NavController calling this using Angular's $http service:
NavController.js
angular.module('NavCtrl',[]).controller('NavController',function($scope,$http) {
$scope.loggedIn = false;
$scope.isLoggedIn = function() {
$http.get('/checklogin')
.success(function(data) {
console.log(data);
if (data === true)
$scope.loggedIn = true;
else
$scope.loggedIn = false;
})
.error(function(data) {
console.log('error: ' + data);
});
};
};
In my nav, I am using ng-show and ng-hide to determine which selections should be visible. I am also triggering the isLoggedIn() function when the user clicks on the nav items, checking whether the user is logged in during each click.
index.html
<nav class="navbar navbar-inverse" role="navigation">
<div class="navbar-header">
<a class="navbar-brand" href="/">Home</a>
</div>
<ul class="nav navbar-nav">
<li ng-hide="loggedIn" ng-click="isLoggedIn()">
Login
</li>
<li ng-hide="loggedIn" ng-click="isLoggedIn()">
Sign up
</li>
<li ng-show="loggedIn" ng-click="logOut(); isLoggedIn()">
Log out
</li>
</ul>
</nav>
Problem
There are other places in my app where the user can log in/out, outside of the scope of the NavController. For instance, there's a login button on the login page, which corresponds to the LoginController. I imagine there's a better way to implement this across my entire app.
How can I 'watch' whether req.user is true on the back end and have my nav items respond accordingly?
you can use $rootScope to share info across the entire app:
.controller('NavController',function($scope,$http, $rootScope) {
$scope.isLoggedIn = function() {
$http.get('/checklogin')
.success(function(data) {
console.log(data);
$rootScope.loggedIn = data;
})
.error(function(data) {
console.log('error: ' + data);
});
};
};
now you can change the value of loggedIn from other places in your app by accessing $rootScope.loggedIn in the same way it is done in the code above.
With that said, you should abstract the relevant code into a service and a directive. This would allow you to have one central place to handle, log in, log out, and the state of $rootScope.loggedIn. If you post the rest of the relevant code I could help you out with a more concrete answer
You can broadcast that event when user logs in successfully. And no need to keep polling your server if user is logged in you can keep a variable in memory that tells if you have a valid session or not. You can use a token-based authentication which is set in the server side:
services.factory('UserService', ['$resource',
function($resource){
// represents guest user - not logged
var user = {
firstName : 'guest',
lastName : 'user',
preferredCurrency : "USD",
shoppingCart : {
totalItems : 0,
total : 0
},
};
var resource = function() {
return $resource('/myapp/rest/user/:id',
{ id: "#id"}
)};
return {
getResource: function() {
return resource;
},
getCurrentUser: function() {
return user;
},
setCurrentUser: function(userObj) {
user = userObj;
},
loadUser: function(id) {
user = resource.get(id);
}
}
}]);
services.factory('AuthService', ['$resource', '$rootScope', '$http', '$location', 'AuthenticationService',
function ($resource, $rootScope, $http, $location, AuthenticationService) {
var authFactory = {
authData: undefined
};
authFactory.getAuthData = function () {
return this.authData;
};
authFactory.setAuthData = function (authData) {
this.authData = {
authId: authData.authId,
authToken: authData.authToken,
authPermission: authData.authPermission
};
// broadcast the event to all interested listeners
$rootScope.$broadcast('authChanged');
};
authFactory.isAuthenticated = function () {
return !angular.isUndefined(this.getAuthData());
};
authFactory.login = function (user, functionObj) {
return AuthenticationService.login(user, functionObj);
};
return authFactory;
}]);
services.factory('AuthenticationService', ['$resource',
function($resource){
return $resource('/myapp/rest/auth/',
{},
{
'login': { method: "POST" }
}
);
}]);
services.factory('authHttpRequestInterceptor', ['$injector',
function ($injector) {
var authHttpRequestInterceptor = {
request: function ($request) {
var authFactory = $injector.get('AuthService');
if (authFactory.isAuthenticated()) {
$request.headers['auth-id'] = authFactory.getAuthData().authId;
$request.headers['auth-token'] = authFactory.getAuthData().authToken;
}
return $request;
}
};
return authHttpRequestInterceptor;
}]);
controller:
controllers.controller('LoginCtrl', ['$scope', '$rootScope', 'AuthService', 'UserService',
function LoginCtrl($scope, $rootScope, AuthService, UserService) {
$scope.login = function () {
AuthService.login($scope.userInfo, function (data) {
AuthService.setAuthData(data);
// set user info on user service to reflect on all UI components
UserService.setCurrentUser(data.user);
$location.path('/home/');
});
};
$scope.isLoggedIn = function () {
return AuthService.isAuthenticated();
}
$scope.user = UserService.getCurrentUser();
}])
You can add user's session data inside the index.html using some templating library like EJS.
Just add ejs middleware:
var ejs = require('ejs');
// Register ejs as .html.
app.engine('.html', ejs.__express);
And then, when returning the index.html render the session data into the response.
res.render( "/index.html", {
session : {
user_data : JSON.stringify(req.user)
}
});
You'll now have access to this data in the index.html, now you need to load it into Angular app.
I used preload-resource example, but you can use your own way.
You can also use $localStorage if you want the login to persist outside of the current session. I've found this library has been super helpful for these types of situations. (https://github.com/grevory/angular-local-storage)