Load initial data after log in with an angularjs app - javascript

I have a completely separate (from backend) AngularJS app. It uses tokens (JWT) for authentication. I'm also using ui-router and Restangular.
My problem:
I have a sidebar/profile area that displays information from the currently logged in user on every "page". It is implemented as a directive+controller and is outside of the ui-view directive context. Inside of the controller for this sidebar I'm making a request to get the profile information and attach it to the $scope. Now, when a user first visits the app that sidebar and controller get invoked and the request is made to get the profile information. However, if and since the user is not logged in, the request fails and no data is bound with the $scope.
In the meantime (notice this all happens really fast), the event listener (listening for $stateChangeSuccess from ui-router) determines that the user is not logged in (essentially if they don't have a token) and does a $state.go('login') to forward the user to the login view. The user logs in, and a $state.go('dashboard') is invoked to bring them back to the dashboard. However, being that sidebar is outside of the context of the router the controller for it is not instantiated again to cause a new request for profile information. So the sidebar is just empty with no profile information.
I'm aware that there are going to be several possible solutions to this, but I'm trying to find a descent one.
How would you architect an angular app in order to solve or avoid the problem I'm having?
P.S. I think I may be having analysis paralysis over this.

It's hard for me to answer without seeing your code specifically. If I understand correctly your directive is firing prior to the user logging in, and since there is no user profile, the side bar doesn't initiate correctly. What I would suggest is possibly doing an ng-if on the tag that fires the directive something like:
<side-bar ng-if='userData' />
That way the tag isn't inserted into the DOM until the userData exists and therefore doesn't fire the directive on the login page.

Assuming that the sidebar is the highest angular controller in your application and the other controllers are nested inside it you should be able to put a function on it that will load the information that you need. Then you can call $rootScope.loadme() anywhere you inject $rootScope.
var mainMod = angular.module('mainMod',[]);
mainMod .controller('mainController', function($scope)
{
$scope.loadMe = function()
{
//load stuff here
};
$scope.loadMe();
});
mainMod .controller('secondController', function($rootScope, $scope)
{
$rootScope.loadMe();
});
SudoCode probably wont work with copy paste but the idea should be sound.

Related

angular ui-router - how to detect direct navigation to a state

I'm using $transitions to detect state changes and take some action based on the difference between some data defined in the 'from' and 'to' states. This is working great for me, but this code is not hit when the user navigates directly to a state via url. For example, browsing to mysite/search does not trigger this, but navigating within the site from the home page to the search page triggers this.
Is there any way to detect that the state was directly navigated to? That would allow me to execute the logic that I need to.
$transitions.onSuccess({}, trans => {
myFunc(trans.$from().data, trans.$to().data);
return true;
});
My problem was that I didn't register the transaction early enough; it was in a service that wasn't loaded until after transitions were handled for the initial launch of the app.

Do not load directive for certain route(s)

I'm attempting to build a 'signin' page for my app that loads whenever a user is not authenticated. The navbar and footer in my app contain data which can only be loaded for authenticated users. Therefore I would like to conditionally hide the navbar and footer directives whenever my route is /signin.
Here is the body of my index.html:
<body ng-app="gameApp">
<dm-navbar ng-if="!$root.notAuthenticated"></dm-navbar>
<ng-view></ng-view>
<dm-footer ng-if="!$root.notAuthenticated"></dm-footer>
</body>
...and in my signinController I set $rootScope.notAuthenticated = true
This successfully removes gm-navbar and gm-footer from the DOM. However, the controller logic associated with these directives still fires (as I see 401 errors in the console).
Well there are a couple of ways. Are you keeping state of the logged in user in some object. Like the controller for /signin holds $scope.userInfo? If so, you can just do :
<gm-navbar ng-if="userInfo"></gm-navbar>
This will hide the directive if the userInfo variable is loaded with the logged in user. This way, you can abstract this approach to all pages that need to hide this, not just /signin
How you populate the user info is up to you, but let's say you're using JWT, you would send a /GET request to the server with the token, and the server would respond with user info, etc.

Backbone js prevent url hash change when back/forward

I am working on a very basic SPA using Backbone.js. My app has few routes. Among them there are 2 that give me issues: the index route ("/#index") and menu route ("/#mainmenu").
A simple workflow in my app is as follows: the user fills a form -> clicks to login -> trigger ajax request -> if login successful go to "/#mainmenu" route. if login failed, remain on "/#index" route.
On "/#mainmenu" if the user clicks on logout -> ajax request -> if logout success go to "/#index". if logout failed remain on "/#mainmenu".
The issues that I am struggling with are:
A clean way to trigger transition to "/#mainmenu" after successful login (I currently use router.navigate("mainmenu", {trigger: true}); but read that should avoid using this approach, in derrick bailey's article https://lostechies.com/derickbailey/2011/08/28/dont-execute-a-backbone-js-route-handler-from-your-code/ )
A clean way to prevent the user to go back to the "/#index" when pressing Back button in the browser from "/#mainmenu" route. I will also would like to preserve the url hash to reflect the current view.
Prevent the user to go forward to "/#mainmenu" after successful logout.
Is that even possible to prevent url hash change when clicking browsers back/forward buttons?
When I say "clean" I refer to "what are the best practices?". I partially solved some issues by saving url hashes and restore the appropriate hash (by router.navigate(currentRoute, {replace: true}); ) but I feel that it's a hacky approach.
Any feedback is welcome and much appreciated.
One way to solve this problem is by applying an async before filter on the routes that require an auth status check before the actual callback route is executed.
For example:
https://github.com/fantactuka/backbone-route-filter
The philosophy of avoiding {trigger: true} is based on the fact that when the router gets triggered with this flag, the entire initialization procedure for that route gets triggered. You will lose the benefit of having previously defined appstates because the app will have to re-initialize all content while this work had alrady been done before.
In practice, I think that it is useful to assess what your web app actually does. If losing appstate isn't an issue because the views you want to render are entirely new, then I don't see a problem with creating a client side redirect that re-inintializes your app.
If, on the other hand, your app has many views already rendered for which you want to maintain the same state as before, you can listen for an auth state event on each component that requires it, and make only those views re-render accordingly if they need to.
I don't think there's anything wrong with triggering routes, have been doing this without any issue for 2+ years. It all boils down to your requirements, read the article looks like a lot of work to me.
There are multiple ways to do this. First, you can disable back/forward buttons using window.history.forward(). Second, my favourite, is to do the processing in Router#execute. A sample might look like :
execute: function(callback, args, name) {
if (!loggedIn) {
goToLogin();
return false; //the privileged route won't trigger
}
if (callback) callback.apply(this, args);
}

Angular.js - Change url from login page causes logged in area glitch

I have a confusing situation somebody probably already had.
I am implementing login / authentication with angular.js.
I've done everything like described in this link
https://medium.com/opinionated-angularjs/techniques-for-authentication-in-angularjs-applications-7bbf0346acec
Now I separated www.url.com/login.html and www.url.com/index.html#dashboard.
Dashboard is the logged in area.
If I am on the login page (www.url.com/login.html) and in url i enter www.url.com/index.html#dashboard the dashboard starts loading and then redirects me back to login.html.
The problem is that I see the dashboard page for a few seconds and then I get redirected after seeing the dashboard loading.
Using window.location in $rootScope.$on('$stateChangeStart', function (event, next) ..
Is there any way to bypass / override this behaviour?
That article is not the show of brightests techniques. There are lots of $rootScope.$broadcast which are considered a code smell. Anyway, as I can see, he is using AnguarUI router. So, that component (same as generic router) has one feature you are looking for - resolves.
Idea is simple - that definition describe a variable that can be injected into Controller. But, if that variable is a promise, Controller will delay rendering of the view until that promise is resolved. So there will be no glitch.
Egghead made a screencast from which you can get the idea how this feature works. But - integrating this feature into login process can be hard if you are not very talented and creative programmer. I didn't find any tutorial about that.

Should i use $rootScope to store http requests that only need to be made once

I am using AngularJs for a new application. I feel I have solved a problem but i'm not sure i have done it in the best way possible so would like to check before gunning ahead.
Let's for examples sake say i have the two controllers AccountsCtrl and ContactsCtrl, every time each is called a request to a REST server for all the accounts or contacts is made for each controller respectively. When within the controller any data changes are kept in sync in the angular models (and the server backend) to reflect this and hence the UI. Every time the user switches between each controller they have to make call to the server to fetch the data which it already had (which was up to date) last time it was loaded.
Currently this causes a very small lag. I would like to make it persistant i.e. not loaded each time the controller is loaded to save on the lag and the server requests. I have tried saving the data into the $rootScope and this works great but i'm not sure it's the right thing to do?
The question is how best solve this problem? Is the $rootScope the best way to tackle this?
I would store the data and code that interacts with the web server in one or two Angular services. Each time a controller is created (e.g., when you go back to the page a second time), the (appropriate) service should decide whether to return the cached data or make an Ajax request to your REST server.
Your service(s) would be injected into your controllers.
See also https://stackoverflow.com/a/12009408/215945 and https://groups.google.com/d/topic/angular/eegk_lB6kVs/discussion
First of all, you should do your REST call via a service:
app.factory('RestService', ['$http', function($http) {
return {
getSomething: function(url) {
var result = {};
$http.jsonp(url).success(function(data) {
result.list = data;
});
return result;
}
};
...
}];
You don't need $rootScope. Just define a parent controller which scopes both controllers, e.g. RootController, and store the data there. This is because child scopes (e.g. from AccountsCtrl) inherit what is defined in parent scopes.
<div ng-controller="RootController">
<div ng-controller="AccountsCtrl">
</div>
<div ng-controller="ContactsCtrl">
</div>
</div>
In practice this is almost the same as using $rootScope, but you don't need to inject it and you keep your controller logic similar as other controllers.

Categories