I am currently developing a new project that uses AngularJS and Twitter Bootstrap 3.0 for styling.
The project also uses ASP.NET MVC WebAPI to provide a RESTful API with role based security as well as generating bearer and refresh tokens using Owin and OAuth.
One of the key aspects of the project is that it must be able to refresh the navbar menu by adding and/or removing menu options depending on the roles/permissions assigned to individual users. If a user is currently logged into the application and a system administrator decides to add or removed roles for the logged in user I would like AngularJS to automatically reload the navbar, which will magically show or hide options based on the roles the user has.
I do not know very much about AngularJS at this point and would like to know if this is possible? I have heard something called $scope.apply() and that it could be used for this, but as an inexperienced AngularJS user I am not sure how this could be used.
This is a portion of my view that displays the menu bar, and as you can see I am using functions to show/hide menu options. I'd like these functions to be re-evaluated again if their values change once a new refresh token has been generated by a WebAPI call.
<li data-ng-if="isAuthenticated()" class="dropdown">
Invoice <b class="caret"></b>
<ul class="dropdown-menu">
<li><a data-ng-if="isUserInInvoiceRole()" data-ui-sref="addInvoice">Add Invoice</a></li>
<li data-ng-if="isUserInInvoiceRole()" class="divider"></li>
<li><a data-ng-if="isUserInCreditNoteRole()" data-ui-sref="addCreditNote">Issue Credit Note</a></li>
<li data-ng-if="isUserInCreditNoteRole()" class="divider"></li>
<li><a data-ui-sref="showDaybook">Daybook</a></li>
<li class="divider"></li>
<li><a data-ui-sref="showCustomerLedger">Customer Ledger</a></li>
</ul>
</li>
At present the only way I can force the navbar to be refreshed is by doing a page refresh via F5, which proves that the role based system works, but I'd like it to work automatically by reloading the navbar.
Controller
'use strict';
appModule.controller('indexController', ['$scope', '$state', 'authService',
function ($scope, $state, authService) {
$scope.isAuthenticated = function () {
return authService.isAuthenticated();
};
$scope.isUserInCreditNoteRole = function () {
return authService.isAuthenticated() && authService.isUserInCreditNoteRole();
};
$scope.isUserInInvoiceRole = function () {
return authService.isAuthenticated() && authService.isUserInInvoiceRole();
};
$scope.isUserInOrderRole = function () {
return authService.isAuthenticated() && authService.isUserInOrderRole();
};
$scope.isUserInAdminRole = function () {
return authService.isAuthenticated() && authService.isInAdminRole();
};
}]);
Auth service
Here is the auth service with the relevant code. Bear in mind that the [roles.xxx] are just constants.
'use strict';
appModule.factory('authService', ['$http', 'roles',
function ($http, roles) {
var authServiceFactory = {};
var authentication = {
isAuth: false,
roles: "Anon"
};
var checkRoles = function (access) {
var result = false;
for (var a in access) {
console.log(access[a]);
for (var b in access[a]) {
if (authentication.roles.indexOf(access[a][b]) >= 0) {
result = true;
break;
}
}
if (result)
break;
}
console.log('result ' + result);
return result;
};
var isUserInInvoiceRole = function () {
return checkRoles([roles.invoice]);
};
var isUserInOrderRole = function () {
return checkRoles([roles.order]);
};
var isUserInCreditNoteRole = function () {
return checkRoles([roles.creditNote]);
};
var isInAdminRole = function () {
return checkRoles([roles.admin]);
};
var isAuthenticated = function () {
return authentication.isAuth;
}
authServiceFactory.isAuthenticated = isAuthenticated;
authServiceFactory.isUserInCreditNoteRole = isUserInCreditNoteRole;
authServiceFactory.isUserInInvoiceRole = isUserInInvoiceRole;
authServiceFactory.isUserInOrderRole = isUserInOrderRole;
authServiceFactory.isInAdminRole = isInAdminRole;
return authServiceFactory;
}]);
I have managed to resolve this myself after understanding how AngularJS watches updates to a controllers' scope.
I simply repopulated the authentication object in the authentication service, after a new refresh token had been retrieved and the nav bar updated instantly.
Related
I build chat function in my web app and i am about to create chat functionality between logged clients. Here is my screen from application to show exactly what i want to solve
Screen of my app
As you can see i got list of online users stored in scope in sidebar. Its created as partial view in my Asp.Net with .cshtml and i render content in "white box" using angular routing.
Problem is i use same controller twice and it creates new scope for each html so i got data in my sidebar, but in my content view i dont have any data. I am thinking about passing my data to rootscope, but i dont know if its good idea.
So my question is. Is there anything how i can clone my data from one controller to another or how i can solve this without changing functionality and if i can keep my views controlled with one controller.
Here is my PrivateChatController.js
(function () {
'use strict';
app.controller('PrivateChatController', ['$rootScope', '$scope', 'SignalRService', '$location', 'PrivateChatService', PrivateChatController]);
function PrivateChatController($rootScope, $scope, SignalRService, $location, PrivateChatService) {
//angular stuff
$scope.online_users = [];
$scope.isChatHidden = false;
$scope.openPrivateChatWindow = function (index) {
// $scope.isChatHidden = true;
angular.forEach($scope.online_users, function (value, key) {
if (index == key) {
$rootScope.currentPrivateChatUser = ({
UserName: value.UserName,
ConnectionId: value.connectionId,
});
$location.path("/details/" + value.UserName);
}
});
};
$scope.closePrivateChatWindow = function (index) {
$scope.isChatHidden = false
};
//signalR stuff
var chatHub = $.connection.chatHub;
$.connection.hub.logging = true;
chatHub.client.foo = function () { };
registerClientMethods(chatHub);
$.connection.hub.start()
.done(function(){ console.log('Now connected, connection ID=' + $.connection.hub.id); })
.fail(function () { console.log('Could not Connect!'); });
function registerClientMethods(chatHub) {
//user object
chatHub.client.newOnlineUser = function (user) {
var newUser = ({
connectionId: user.ConnectionId,
UserName: user.UserName
});
$scope.online_users.push(newUser);
$scope.$apply();
};
//compare scope online users with server list of online users
chatHub.client.getOnlineUsers = function (onlineUsers) {
//loop through scope
angular.forEach($scope.online_users, function (scopeValue, scopeKey) {
//loop through received list of online users from server
angular.forEach(onlineUsers, function (serverListValue, serverListKey) {
if (!(serverListValue.ConnectionId == scopeValue.connectionId)) {
var newUser = ({
connectionId: serverListValue.ConnectionId,
UserName: serverListValue.UserName
});
$scope.online_users.push(newUser);
$scope.$apply();
}
})
})
};
chatHub.client.onUserDisconnected = function (id, user) {
var index = 0;
//find out index of user
angular.forEach($scope.online_users, function (value, key) {
if (value.connectionId == id) {
index = key;
}
})
$scope.online_users.splice(index, 1);
$scope.$apply();
};
}};})();
Consider using services as a layer for data sharing. It should also contain chat related logic, in my opinion controllers should be as thin as possible.
Move chatHub.client.getOnlineUsers function to the service and create getter for users.
Further read
By clicking the link i change view, this re-instantiating my controller. This is why the activeMenu is empty. If icreate a service and store the currently active menu in there, it won't be empty.How can i create service to do that? This is my first time that i need to create service?This is my plnkr... http://plnkr.co/edit/gJko3umteXXEye7o9StR?p=preview
Problem is when user click on menu from layout then this re-instantiating my controller
Layout:
<li class="dropdown">
<ul class="submenu">
<li>#Translate("MY_ACCOUNT")</li>
<li ng-click="GetLoader();">#Translate("SETTINGS")</li>
<li ng-click="GetLoader();">#Translate("TICKET_HISTORY")</li>
<li ng-click="GetLoader();">#Translate("TRANSACTIONS")</li>
<li ng-click="GetLoader();">#Translate("BONUS_ACCOUNT")</li>
<li>#Translate("ODDS_REPRENSETATION")</li>
<li>#Translate("HELP")</li>
<li>#Translate("LOGOUT")</li>
</ul> </li>
You can do it like this:
app.service('navigation', function () {
this.activeLink = '';
});
app.controller('MainCtrl', function($scope, navigation) {
$scope.activeMenu = 'Home';
$scope.activeLink = navigation.activeLink;
$scope.changeActiveLink = function(link) {
$scope.activeLink = link;
this.activeLink = link;
}
});
The service is a singleton, it's intatiaded once per you application load, so it will keep the assigned value between diffent states of your app.
You can make it a bit smarter if you won't store a simple string value but an object - this way we'll be able to keep the reference to the service's value rather than the value itself and we won't need to set both $scope.activeLink = link; and this.activeLink = link; explicitly:
app.service('navigation', function () {
this.active = {
link: '',
menu: 'Home'
};
});
app.controller('MainCtrl', function($scope, navigation) {
$scope.activenav = navigation.active;
$scope.changeActiveLink = function(link) {
navigation.active.link = link;
}
});
(just remember to make necessary changes in your html code in that second case)
I am currently facing a problem with my project's design.
I am using angularjs framework and my task is to provide a translations for a webpage, but the translations need to be provided form the xml file o the BE side.
So since I#ve found out that angulars i18n is configurable on the FE side i had to use another strategy.
I've decided to make a service which fetches the data during a resolve period before everything else is loaded:
app.factory('dictionaryService', ['$http', '$rootScope', function ($http, $rootScope) {
return {
getDictionary: function (defaultLanguage) {
var chosenLanguage = null;
if (angular.isUndefined($rootScope.defaultLanguage) || $rootScope.defaultLanguage == null) {
chosenLanguage = defaultLanguage;
$rootScope.defaultLanguage = chosenLanguage;
} else {
chosenLanguage = $rootScope.defaultLanguage;
}
var translation = new Array();
translation[chosenLanguage] = new Array();
return $http.get('Translation/GetCurrentDictionary/', {
params: {
language: chosenLanguage
}
});
},
GetLanguagesSetup: function () {
return $http.get('Translation/GetLanguagesSetup/');
}
}
}]);
and then resolve it as follows:
$routeProvider.when("/diagnose", {
controller: "diagnoseCtrl",
templateUrl: "/app/views/diagnose.html",
resolve: {
startupData: function (dictionaryService, $q) {
var def = $q.defer();
var translation = new Array();
var startupData = new Array();
var defaultLanguage = "EN";
var dict = dictionaryService.getDictionary(defaultLanguage).then(function (JSONData) {
var keys = Object.keys(JSONData.data.data);
var chosenLanguage = JSONData.data.lang;
translation[chosenLanguage] = {};
for (i = 0; i < keys.length; i++) {
translation[keys[i]] = JSONData.data.data[keys[i]];
}
startupData['translations'] = translation;
def.resolve(startupData);
}).catch(function (e) {
console.log("Translation fetching exception, " + e);
return $q.reject(e);
});
return def.promise;
}
}
});
So as you can see I am storing my fetched translations in a startupData. Then in a controller which is using it I am assigning this data to the $rootScope. It seems already here as a not the best solution, but I could not come up with a different one
Then I have created a translation service which gets the direct translation text:
app.factory('translationService', ['$rootScope', '$http', function ($rootScope, $http) {
var translations = null;
return {
getText: function (key) {
if ($rootScope.cachedTranslations == undefined) {
return key;
}
var result = $rootScope.cachedTranslations[key];
if (result == null) {
return key;
} else {
return result;
}
}
}
}]);
The biggest problem with this solution is, that I am not using promises, but I do not want to make an http query to BE for each translation.
The other problem is with the html template provided by the designers:
<body ng-controller="mainController">
<loading-screen ng-show="!isDataLoaded"></loading-screen>
<div id="header" class="headerView" ng-controller="headerController" ng-show="isDataLoaded">
some header stuff
...
<button ng-bind="option1" ng-click="redirectTo('#subpage1')"></button>
<button ng-bind="option2" ng-click="redirectTo('#subpage2')"></button>
<button ng-bind="option3" ng-click="redirectTo('#subpage3')"></button>
<button ng-bind="language" ng-if="availableLanguages.length > 1" ng-repeat="language in availableLanguages" ng-click="setLanguage(language)"></button>
</div>
</div>
<
<div id="content" ng-view ng-show="isDataLoaded">
</div>
<footer id="footer" class="footer" ng-show="isDataLoaded">
<status-bar></status-bar>
</footer>
Resolve applies only for ng-views's controller, but header stuff needs to be translated as well, so I need to make a headerCtrl somehow wait before it tries to apply translations.
So I have made another unpopular decision to inform all controllers about the finished startup via a broadcast message and to wait until it is all done while showing the loading screen.
It looks fine and is pretty responsive (1sec per startup is acceptable at this point).
The problem is, that I see many design mistakes with this attempt and I just can not come up with the better design.
So my main question is:
How can I make it better? 1st service returns a whole array which is used by the 2nd service so I do not know how to combine it with promises?
I am afraid that with the development of the application I will find myself in a global variables and global events hell
Thanks in advance for your help!
It seems like you are looking for the angular-translate-loader-static-files extension for angular-translate. See the documentation here.
This together with proper configuration of $translateProvider will allow you to fetch json files with translations from the backend or even swap translations on demand - for example user changes language setting, controller reconfigures $translateProvider. Your job is done - everything will be fetched and updated automatically without a page reload.
I want $http.get method to work when a form is submitted.
Here is my code. The object $scope.questions is being set when the method is called but the data doesn't show up in the div. Moreover, when the $http.get method is outside the signIn() function it works just fine.
$scope.signIn = function(data) {
$location.path('/profile');
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(questionData) {
$scope.questions = questionData;
console.log($scope.questions);
});
};
<div>
User Profile
<br/>Question Posted
<br/>
<input ng-model="query.title" id="value" type="text" placeholder="Search by Title..." ">
<div>
<ul>
<li ng-repeat="question in questions | filter: query ">
{{question.title}}
</li>
</ul>
</div>
<br/>
</div>
You need to move your $location.path('/profile') inside your http request. Remember that a http request is async call. You should redirect after getting the data not before.
$scope.signIn = function(data) {
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(questionData) {
$scope.questions = questionData;
console.log($scope.questions);
$location.path('/profile');
});
};
If you're redirecting to another route with a completely separate scope you will lose any scope you're setting in the success handling.
From what I'm reading you're clicking a button to do an action. After that action you're redirecting to another page with a separate controller and trying to persist the data.
Unfortunately, Angular hasn't figured out a great way to do this. The easiest way to persist data through controllers and scope is to create a service that will store it in one controller and grab it in another controller.
For instance:
$scope.signIn = function(data) {
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(questionData) {
$location.path('/profile');
storageService.store("question", questiondata)
});
};
Your new factory to persist data through:
angular.module('moduleName').factory('storageService', [
function () {
return {
store: function (key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
get: function(key) {
return JSON.parse(localStorage.getItem(key));
},
remove: function(key) {
localStorage.removeItem(key);
}
}
}
]);
Other controller to access data:
$scope.question = storageService.get("question");
// remove localstorage after you've grabbed it in the new controller
storageService.remove("question");
An alternative to doing the somewhat 'hacky' way of using localStorage to persist data through controllers is to use ui-router and have a resolve on the route you're redirecting to.
For instance:
$scope.signIn = function(data) {
$state.go('profile');
};
In your route file:
.state('profile', {
url: '/profile'
controller: profileControllerName,
templateUrl: 'profileHtmlTemplate.html',
resolve: {
'questions': [function() {
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(res) {
return res.data;
});
}]
}
}
In your profile controller:
Inject your 'questions' resolve into your controller and assign `$scope.question = questions;
This will make the HTTP call as soon as you click the route, return the data if successful, then render the page. It will NOT render the page if the resolve does not return success. This will ensure your data will be loaded before you load the page that depends on that data.
I would highly recommend using services to hold your HTTP calls for specific parts of your application. If you have a GET questions, POST question, PUT question. I would create a questionService and make all my HTTP methods there so you don't have to clutter your routes. You would only have to call:
.state('profile', {
url: '/profile'
controller: profileControllerName,
templateUrl: 'profileHtmlTemplate.html',
resolve: {
'questions': [function() {
return questionService.getQuestions(id).then(function(res) {
return res.data;
})
}]
}
}
I have an AngularJS app, and I want to implement G+ sign-in. I've gone through their samples, and they work as standalone apps.
https://developers.google.com/+/web/signin/
In my Angular app, I am able to display the G+ sign-in button. But I'm stuck on the callback. Do I put the callback function in my controller js file?
If so, and given this controller:
app.controller('myController', function ($scope) {
function signinCallback(authResult) {
On my data-callback, how do I name it so that it goes to signinCallback inside myController?
<span id="signinButton">
<span
class="g-signin"
data-callback="signinCallback"
data-clientid="123456789.apps.googleusercontent.com"
data-cookiepolicy="single_host_origin"
data-requestvisibleactions="http://schemas.google.com/AddActivity"
data-scope="https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.profile.emails.read"
</span>
</span>
The Google+ PhotoHunt sample app demonstrates an AngularJS integration with Google+. The sample is available in Ruby, Java, Python, and C#/.NET for web.
Of note should be the following code in the AngularJS front-end:
Markup to render the button in:
<span id="signin" ng-show="immediateFailed">
<span id="myGsignin"></span>
</span>
JavaScript to glue the markup to code:
$scope.signIn = function(authResult) {
$scope.$apply(function() {
$scope.processAuth(authResult);
});
}
$scope.processAuth = function(authResult) {
$scope.immediateFailed = true;
if ($scope.isSignedIn) {
return 0;
}
if (authResult['access_token']) {
$scope.immediateFailed = false;
// Successfully authorized, create session
PhotoHuntApi.signIn(authResult).then(function(response) {
$scope.signedIn(response.data);
});
} else if (authResult['error']) {
if (authResult['error'] == 'immediate_failed') {
$scope.immediateFailed = true;
} else {
console.log('Error:' + authResult['error']);
}
}
}
$scope.renderSignIn = function() {
gapi.signin.render('myGsignin', {
'callback': $scope.signIn,
'clientid': Conf.clientId,
'requestvisibleactions': Conf.requestvisibleactions,
'scope': Conf.scopes,
'apppackagename': 'your.photohunt.android.package.name',
'theme': 'dark',
'cookiepolicy': Conf.cookiepolicy,
'accesstype': 'offline'
});
}
Within processAuth, you should see an access token and can update your UI to reflect this. You can also see the full controller's JavaScript code on GitHub.
I am not sure if this works, but I would try it like this:
module.factory("GPlusAuthService", function ($q, $window) {
var signIn;
signIn = function () {
var defered = $q.defer();
$window.signinCallback = function (response) {
$window.signinCallback = undefined;
defered.resolve(response);
};
gapi.auth.signIn({
clientid: "123456789.apps.googleusercontent.com"
cookiepolicy: "single_host_origin"
requestvisibleactions: "http://schemas.google.com/AddActivity"
scope: "https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.profile.emails.read",
callback: "signinCallback"
})
return defered.promise;
};
return {
signIn: signIn;
}
});
module.controller('myController', function ($scope, GPlusAuthService) {
$scope.signIn = function() {
GPlusAuthService.signIn().then(function(response) {
});
}
});
<span id="signinButton" ng-controller="myController">
<span class="g-signin" ng-click="signIn()"></span>
</span>
Function that is going to be called after user agrees to sign in is specified in data-callback, this function needs to be globally accessible, that is bound to window object.
Accessing global object from controller is an anti-pattern, as a middle ground you can use $window provided by Angular, which you can mock in your tests