Javascript client side auth customizing Backbone.Sync, does this make sense? - javascript

I'm new to Backbone / Marionette and I am having some trouble implementing authorization on my client app. Basically I have a app that have some public and private routes and I need to automate the login flow when the user tries to do some private action, like the original private user flow halts, start the login flow and then resumes the original user flow. Pretty standard stuff...
I am trying to find a way to automate or intercept and implement this behavior from my client app. I am not using any server redirect since that strategy reloads my app all over again and destroys my state, so I am trying to add some custom code to Backbone.sync to accomplish this.
In essence what I am doing is extending Backbone.Model to use my custom sync, that custom sync should return a custom promise (not the $.ajax) like the original Backbone.sync, I always try to resolve the promise using the original sync but if I catch an 401 from server I add Sync context (method, model, options) and also my custom promise object to my globally accessible App object, then i navigate my app to login, "halting" the user flow. Once user submit login info I check if there is a Deferred object on App, if so I call Backbone.sync with the original context and resolve the initial custom promise with that result ("resuming user flow"), then I just finish navigating to the original fragment to sync URLs.
I find this a simple solution in idea but does it make sense in Backbone / Marionette context app?
Here's my customSync
function (method, model, options) {
var deferred = $.Deferred();
if (options)
deferred.then(options.success, options.error);
var sync = Backbone.sync(method, model, _.omit(options, 'success', 'error'));
sync.done(deferred.resolve);
sync.fail(function() {
if (sync.status === 401) {
// Add logic to send the user to the login page,
// or general authentication page.
App.Defer = deferred;
App.Callback = Backbone.sync;
App.Arguments = [method, model, _.omit(options, 'success', 'error')];
App.Fragment = Backbone.history.fragment;
// In this example, we'll send the user to the "login" page:
App.navigate("login", { trigger: true, replace: true });
} else {
deferred.reject.apply(sync, arguments);
}
});
return deferred;
}
And my login submit event of LoginView
loginView.on('auth:login', function (data) {
var sessionModel = new App.Models.Session();
sessionModel.login(data, function (err, user) {
if (err) return loginView.setError(err);
App.trigger('set:user', user);
var defer = App.Defer;
var callback = App.Callback;
var arguments = App.Arguments;
var fragment = App.Fragment;
delete App.Defer;
delete App.Callback;
delete App.Arguments;
delete App.Fragment;
if (defer) {
callback.apply(this, arguments).then(defer.resolve, defer.reject);
App.navigate(fragment, { trigger: false, replace: true });
} else {
App.navigate('', { trigger: true, replace: true });
}
});
});

It's an interesting idea but I'm not sure it does you much good in practice. After login you are going to redirect the user back to the route they were on when the 401 occurred. That route processing is going to end up re-making the service request anyways (unless you do a lot of work to avoid that) so resolving the previously rejected promise won't do any good. I also wouldn't expect promise libraries to all behave the way you want when a rejected promise is later resolved. From a coding perspective, if you do this try to avoid global state - you can pass the data back in the .reject arguments and keep global state clean (which will be particularly important when you have views calling multiple services asynchronously). One more thing... from a security perspective, it would be crucial to ensure that you are dealing with the same user account before and after the 401.
I have done something very similar to this when working with OAuth services where a custom sync method looks for 401s and then attempts to use the refresh_token to get a new access_token then retry the initial request - but retrying a request after kicking the user to a login screen seems a step too far.

The Backbone.sync function seems like a strange place to be handling navigation logic. Sync is for managing your model states, and not the UI. It also seems doubtful that you want to universally retry every sync that fails. What seems more likely to me is that you may want to preserve data that might otherwise be lost by redirects (e.g form data). This is easy with Backbone because you can continue to pass references to models around.
I'd recommend that you get rid of all the global variables and routing logic in your Backbone.sync override. Instead just trigger an event and pass any information you may want to have to a separate handler. Something like this:
sync.fail(function() {
if (sync.status === 401) {
App.vent.trigger("sync:failed", method, model, options);
}
});
This gives you a lot more flexibility with how to handle failures, keeps your sync function clear of navigation code, and makes it much easier to handle the nasty cases you are going to run into with globals (especially since you are likely to get a set of sync calls failing together, not just one).
Now add some handlers to listen for failures.
var redirectTo = "";
App.vent.on("sync:failed", function () {
// Handle routing
redirectTo = Backbone.history.fragment; // Use this url when navigating after auth
Backbone.history.navigate("login", true);
});
App.vent.on("sync:failed", function (method, model, options) {
// Handle model data you care about
// e.g. manage a queue of unsaved changes; clear changes that didn't save; preserve UI specific models, etc.
});
If you are dead set on retrying your promises, you should do it this way as well, especially because you are going to want to only retry selectively (not to mention the issues brought up by Robert around this).
Below is how I would setup a handler to resubmit a message after logging in:
App.vent.on("sync:failed", function (method, model, options) {
if (model.retrySaveOnLogin) { // Property I would include on models that can be safely retried
model.listenOnce(App.vent, "login:success", function () { // Some event you trigger when login is successful
this.save({}, options);
});
}
});

Related

How to prevent API calls on a minute timer loop when in the process of logging out of a web app?

In my Ionic/Angular app, I have a 60 second timer observable which just emits the current time synced with server time. Each minute I fetch permissions, settings, etc. I pass a token with each request. On logout I revoke the token. Here's a sample of what my logic looks like.
Side note: There's also a feature where a user can "change login type" where they can "become" an administrator, for example, and this process may also trigger a similar circumstance.
this.clientTimeSub = this.timeService.clientTime
.pipe(takeUntil(this.logoutService.isLoggingOut$))
.subscribe(async (latestClientTime) => {
this.clientTime = { ...latestClientTime };
// if client time just rolled over to a new minute, update settings
if (
this.clientTime?.time?.length === 7 &&
this.clientTime?.time?.slice(-1) === '0'
) {
await updateSettings();
await updatePermissions();
// etc
// These functions will:
// (1) make an api call (using the login token!)
// (2) update app state
// (3) save to app storage
}
});
When I am logging out of the app, there's a small time window where I could be in the middle of sending multiple api requests and the token is no longer valid, due to the timer rolling to a new minute just as I was logging out, or close to it. I am then presented with a 401: Unauthorized in the middle of logging out.
My naive solution was to tell this observable to stop propagation when a Subject or BehaviorSubject fires a value telling this observable that it is logging out, you can see this here .pipe(takeUntil(this.logoutService.isLoggingOut$)).
Then, in any of my logout methods, I would use:
logout() {
this.isLoggingOut.next(true);
...
// Logout logic here, token becomes invalidated somewhere here
// then token is deleted from state, etc, navigate back to login...
...
this.isLoggingOut.next(false);
}
In that small time window of logging out, the client timer should stop firing and checking if it's rolled to a new minute, preventing any further api calls that may be unauthenticated.
Is there a way I can easily prevent this issue from happening or is there a flaw in my logic that may be causing this issue?
I appreciate any help, thank you!
First of all, it is not the best way to use async-await along with RXJS. Its because RXJS as a reactive way of functional programming, have its "pipeable" operators so you can kinda "chain" everything.
So instead of having a logic of calculating time in your subscribe callback function you should rather use, for example filter() RXJ operator, and instead of using await-async you can use switchMap operator and inside it, use forkJoin or concat operator.
this.timeService.clientTime
.pipe(
// Filter stream (according to your calculation)
filter((time) => {
// here is your logic to calculate if time has passed or whatever else you are doing
// const isValid = ...
return isValid;
}),
// Switch to another stream so you can call api calls
// Here with "from" we are converting promises to observables in order to be able to use magic of RXJS
switchMap(_ => forkJoin([from(updateSettings), from(updatePermissions)])),
// Take until your logout
takeUntil(this.logoutService.isLoggingOut$)
).subcribe(([updateSettings, updatePermissions]) => {
// Basically your promises should just call API services, and other logic should be here
// Here you can use
// (2) update app state
// (3) save to app storage
})
If you split actions like in my example, in your promises you just call api calls to update whatever you are doing, then when its done, in subscribe callback you can update app state, save to app storage etc. So you can have 2 scenarios here:
Api calls from promises, are still in progress. If you trigger logout in the meanwhile takeUntil will do the thing and you will not update app state etc.
If both Api calls from promises are done, you are in a subscribe callback block and if its just a synchronous code (hopefully) it will be done. And then async code can be executed (your timer can now emit next value, its all about Event Loop in javascript)

Is there a well-established way to update local state immediately without waiting for an API response in React/Redux?

TL;DR: Is there some well-known solution out there using React/Redux for being able to offer a snappy and immediately responsive UI, while keeping an API/database up to date with changes that can gracefully handle failed API requests?
I'm looking to implement an application with a "card view" using https://github.com/atlassian/react-beautiful-dnd where a user can drag and drop cards to create groups. As a user creates, modifies, or breaks up groups, I'd like to make sure the API is kept up to date with the user's actions.
HOWEVER, I don't want to have to wait for an API response to set the state before updating the UI.
I've searched far and wide, but keep coming upon things such as https://redux.js.org/tutorials/fundamentals/part-6-async-logic which suggests that the response from the API should update the state.
For example:
export default function todosReducer(state = initialState, action) {
switch (action.type) {
case 'todos/todoAdded': {
// Return a new todos state array with the new todo item at the end
return [...state, action.payload]
}
// omit other cases
default:
return state
}
}
As a general concept, this has always seemed odd to me, since it's the local application telling the API what needs to change; we obviously already have the data before the server even responds. This may not always be the case, such as creating a new object and wanting the server to dictate a new "unique id" of some sort, but it seems like there might be a way to just "fill in the blanks" once the server does response with any missing data. In the case of an UPDATE vs CREATE, there's nothing the server is telling us that we don't already know.
This may work fine for a small and lightweight application, but if I'm looking at API responses in the range of 500-750ms on average, the user experience is going to just be absolute garbage.
It's simple enough to create two actions, one that will handle updating the state and another to trigger the API call, but what happens if the API returns an error or a network request fails and we need to revert?
I tested how Trello implements this sort of thing by cutting my network connection and creating a new card. It eagerly creates the card immediately upon submission, and then removes the card once it realizes that it cannot update the server. This is the sort of behavior I'm looking for.
I looked into https://redux.js.org/recipes/implementing-undo-history, which offers a way to "rewind" state, but being able to implement this for my purposes would need to assume that subsequent API calls all resolve in the same order that they were called - which obviously may not be the case.
As of now, I'm resigning myself to the fact that I may need to just follow the established limited pattern, and lock the UI until the API request completes, but would love a better option if it exists within the world of React/Redux.
The approach you're talking about is called "optimistic" network handling -- assuming that the server will receive and accept what the client is doing. This works in cases where you don't need server-side validation to determine if you can, say, create or update an object. It's also equally easy to implement using React and Redux.
Normally, with React and Redux, the update flow is as follows:
The component dispatches an async action creator
The async action creator runs its side-effect (calling the server), and waits for the response.
The async action creator, with the result of the side-effect, dispatches an action to call the reducer
The reducer updates the state, and the component is re-rendered.
Some example code to illustrate (I'm pretending we're using redux-thunk here):
// ... in my-component.js:
export default () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(MyActions.UpdateData(someDataFromSomewhere));
});
return (<div />);
};
// ... in actions.js
export const UpdateData = async (data) => (dispatch, getStore) => {
const results = await myApi.postData(data);
dispatch(UpdateMyStore(results));
};
However, you can easily flip the order your asynchronous code runs in by simply not waiting for your asynchronous side effect to resolve. In practical terms, this means you don't wait for your API response. For example:
// ... in my-component.js:
export default () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(MyActions.UpdateData(someDataFromSomewhere));
});
return (<div />);
};
// ... in actions.js
export const UpdateData = async (data) => (dispatch, getStore) => {
// we're not waiting for the api response anymore,
// we just dispatch whatever data we want to our reducer
dispatch(UpdateMyStore(data));
myApi.postData(data);
};
One last thing though -- doing things this way, you will want to put some reconciliation mechanic in place, to make sure the client does know if the server calls fail, and that it retries or notifies the user, etc.
The key phrase here is "optimistic updates", which is a general pattern for updating the "local" state on the client immediately with a given change under the assumption that any API request will succeed. This pattern can be implemented regardless of what actual tool you're using to manage state on the client side.
It's up to you to define and implement what appropriate changes would be if the network request fails.

Using $resource in a promise chain (fixing deferred anti-pattern)

I have a service with a method that gets me a list of project types using a $resource. It's working well for me, except that if I make multiple nearly simultaneous calls (from say, two directives) each will create another request instead of using the same response/$promise/data.
I found this which led me to this and TL;DR, apparently it's creating a redundant $q.defer() and is actually considered to be a deferred anti-pattern.
The code below works well if the calls to get project types are significantly staggered (like more than milliseconds apart). The consecutive calls are resolved with the shared.projectTypes. It also works in the sense that if the request to get project types fails, the dfr.reject() will be triggered and be caught by .catch in the calling controller.
angular.module('projects')
.factory('projectService', function(notificationService){
// an object to share data gathered by this service
var shared = {};
// $resource for projects API
var projectResource = $resource(baseApiPath + 'projects', {}, {
...,
getProjectTypes: {
method: 'GET',
url: baseApiPath + 'projects/types'
},
...
});
// loads a list of project types
var loadProjectTypes = function(){
var dfr = $q.defer();
// if we've already done this, just return what we have.
if(shared.projectTypes){
dfr.resolve(shared.projectTypes);
}
else {
// begin anti-pattern (?)
projectResource.getProjectTypes(null,
function(response){
shared.projectTypes = response.result.projectTypes;
dfr.resolve(response);
},
function(errResponse){
console.error(errResponse);
notificationService.setNotification('error', errResponse.data.messages[0]);
dfr.reject(errResponse);
});
}
return dfr.promise;
};
return {
shared: shared,
project: projectResource,
loadProjectTypes: loadProjectTypes
};
});
So, I read that having this extra var dfr = $q.defer() is not necessary as the $resource would provide all that for me. With a bit of refactoring, I ended up with this:
...
// $resource for projects API
var projectResource = $resource(baseApiPath + 'projects', {}, {
...,
getProjectTypes: {
method: 'GET',
url: baseApiPath + 'projects/types',
isArray: true,
transformResponse: function(response){
return JSON.parse(response).result.projectTypes;
}
},
...
});
// loads a list of project types
var loadProjectTypes = function(){
return shared.projectTypes || (shared.projectTypes = projectResource.getProjectTypes());
};
...
To clarify, I have added isArray and transformResponse to the resource because my API returns a lot of extra meta information and all I wanted was an array of types. In my loadProjectTypes method, I'm including the same caching we originally had, but I'm caching the result of projectResource.getProjectTypes() instead of the actual response data (even though that might be exactly what I'm caching because of the transformResponse).
This works on the happy path (reduced calls to API, returns the same thing to everyone, etc) but my main problem is with the chaining and catching of errors.
In my original anti-pattern example, if there is an error with GET /project/types, I'm using dfr.reject() which is then passed back to my controller where I have a .catch().
This is code from the controller which actually makes the original request to get project types:
$q.all([
projectService.loadProjects(),
userService.loadUserRole('project_manager'),
userService.loadUserRole('sales_representative'),
projectService.loadProjectTypes(),
clientService.loadClients()
])
.then(function(response){
// doing stuff with response
})
.catch(function(errResponse){
// expecting errors from service to bubble through here
console.error(errResponse);
});
With the anti-pattern example, the dfr.reject is causing the error to show up here in the catch, but in my supposed non-anti-pattern example, it's not happening. I'm not sure how to reject or resolve the $resource results in the same way I was before. If one of the points of promise chaining is to have one spot to handle errors from any chain link, I was doing it right.
I tried to use $q.resolve()/reject(), since I don't have dfr anymore, but this seems dumb and doesn't work anyway.
return shared.projectTypes || (shared.projectTypes = projectResource.getProjectTypes(null,
function(response){
return $q.resolve(response);
},
function(errResponse){
return $q.reject(errResponse);
}));
How do I get the chain to work so that .catch() in the controller is where the errors get handled?
Did I actually implement the anti-pattern in my original code, or was that one of the accepted ways to use $q.defer() and it wasn't an anti-pattern at all?
In the second link I posted, there is an answer that says:
"What's wrong with it? But the pattern works! Lucky you.
Unfortunately, it probably doesn't, as you likely forgot some edge
case. In more than half of the occurrences I've seen, the author has
forgotten to take care of the error handler."
However, my original code was addressing the errors. It was working, except that each caller was getting it's own promise. I feel that's where I missed something.
I might be confused, but I'm thinking that the loadProjectTypes method should return the same promise/data to anyone who calls it, no matter when it's called. It should be the one true source of anything projectTypes and only make the call once, the very first time.
Any time I look for any of this (lots of purple/visited google links on these subjects), everyone is either showing chaining with contrived examples, or only using $http, or something else. I haven't found anyone doing error catching in a promise chain that uses $resource.
UPDATE: Adding my requirements for the solution. I posted them in my answer, but wanted to include them in the original post too.
Requirement 1: Allows multiple calls to the method, but only makes one API request which updates all callers with the same data.
Requirement 2: Must be able to use result of method as actual data, just as the promise spec intends. var myStuff = service.loadStuff() should actually set myStuff to be "stuff".
Requirement 3: Must allow promise chaining so that all errors in any part of the chain can be caught by a single catch at the end of the chain. As I've found in my solution, there can be more than one chain, and more than one catch, but the point is that each chain has a catch, and any "links" in the chain that break should all report their errors to their respective catch.
Isn't that always the way, as soon as you speak your problems, you come across your solution.
Requirement 1: Only make one request per method call. This is solved with the original fix to the anti-pattern. This will either always return the $resource result by either returning the cached $resource or returning and caching at the same time.
var loadProjectTypes = function(){
return shared.projectTypes || (shared.projectTypes = projectResource.getProjectTypes());
};
Requirement 2: Be able to use the service method as a promise where I can set the value of a $scope variable directly to the result of loadProjectTypes(). Using the revised method above, I can simply state $scope.theTypes = projectService.loadProjectTypes() and it'll automatically be filled with the list of types when they come in, just as the promise spec intends.
Requirement 3: Be able to chain together multiple $resource calls and have their errors be caught by a single .catch(). By using the $promise of the result of loadProjectTypes within $q.all(), I can catch any errors in any catch I want.
$q.all([
...,
projectService.loadProjectTypes().$promise,
...
])
.then(function(response){
// my project types comes in as response[n]
})
.catch(function(errResponse){
// but any errors will be caught here
});
Technically, I can put catches in different places and they'll all work the same. Anytime I have loadProjectTypes(), I can use a .catch() and my errors will be handled there. Each loader of types can handle the API being down in it's own way. This could be really good actually. A controller might get the UI to display a message and a small directive might just display something else, or nothing at all. They each can handle the bad in their own way.
My service, directive and controller look like this now:
angular.module('projects')
.factory('projectService', function(notificationService){
// an object to share data gathered by this service
var shared = {};
// $resource for projects API
var projectResource = $resource(baseApiPath + 'projects', {}, {
...,
getProjectTypes: {
method: 'GET',
url: baseApiPath + 'projects/types',
isArray: true,
transformResponse: function(response){
return JSON.parse(response).result.projectTypes;
}
},
...
});
// loads a list of project types
var loadProjectTypes = function(){
return shared.projectTypes || (shared.projectTypes = projectResource.getProjectTypes());
};
return {
shared: shared,
project: projectResource,
loadProjectTypes: loadProjectTypes
};
});
angular.module('projects')
.directive('projectPageHeader', ['projectService', function(projectService){
return {
restrict: 'E',
scope: {
active: '#',
},
templateUrl: 'src/js/apps/projects/partials/dir_projectPageHeader.html',
replace: true,
controller: function($scope){
$scope.projectService = projectService;
// sets the types to the array of types
// as given by the transformResponse
$scope.types = projectService.getProjectTypes();
// could also do a .$promise.catch here if I wanted.
// all catches will fire if get projectTypes fails.
}
};
}]);
angular.module('projects')
.controller('projectListPageController', [
'$scope','projectService',
function($scope, projectService){
// load it all up
$q.all([
projectService.loadProjectDetails($routeParams.projectId).$promise,
userService.loadUserRole('project_manager').$promise,
userService.loadUserRole('sales_representative').$promise,
projectService.loadProjectStatuses().$promise,
projectService.loadProjectTypes().$promise,
clientService.loadClients().$promise
])
.then(function(response){
// do work with any/all the responses
})
.catch(function(errResponse){
// catches any errors from any of the $promises above.
})
}]);
Since the loadProjectTypes (or any other load_____ method) saves the types within the service it comes from, I don't really need to do any storing on the controller. projectService.shared.projectTypes is universal across the entire app. The .then() method in my controller could potentially be noop if all the services were storing the results of their loads internally (which is how I like it) unless there was some view specific thing I needed to do with them. I typically only use controllers for entire pages, or $modals. Everything else is broken up into directives and most information and logic is in services.
I'm leaving the question open in case someone has a better solution. I like the one that Jack A. posted, but I feel it makes my load___ methods more verbose than they already are. Since there are a few of them with slight differences, it leads to a lot of redundant code, or complex 'smart' methods in my actual code. It definitely solves Requirement 1 and possibly 2 and 3 though.
UPDATE (GOTCHA):
So, I've been using this pattern for a few days now and it's working really exactly as I intend. It's really streamlined our process; however, I recently came upon a gotcha when using a method like loadProjectTypes in a singular context (i.e.: outside of $q.all()).
If you just use the load method like so:
// This code is just placed in your controllers init section
loadProjectTypes()
.$promise
.then(function(response){
// ... do something with response (or noop)
})
.catch(function(errResponse){
// ... do something with error
});
You will run into a situation when that controller 'refreshes'. For example, you have the code above in controllerA, you change "pages" which uses controllerB, then you go back to the first "page" and controllerA refreshes and tries to run this again. The error you get is that "there is no .then of undefined."
Inspecting this in the console, the first time loadProjectTypes() runs, it returns the response from the $resource (which includes $promise AND all the projectType data). The second time - coming back from controllerB - it will only hold the projectType data. There is no more $promise because you are not returning the result of a $resource, you returned the cached shared.projectTypes that you set after the first time. That's why we did all this, remember? I'm not sure why this goes away since that's what you saved to shared.projectTypes, but it does, and it doesn't actually matter.
return shared.projectTypes || (shared.projectTypes = projectResource.getProjectTypes());
For me, the easiest fix was to just have loadProjectTypes().$promise as the only member of a $q.all() set:
// again, this code is just placed somewhere near the top of your controller
$q.all([
loadProjectTypes().$promise
])
.then(...)
.catch(...);
In most cases, my controllers will be getting more than one thing so this would've happened eventually, but there will always be a situation where you only need to load one thing. Using a single item set in $q.all() is the only way to have no issues when using this solution. It's not that bad really, could've been worse.
I wrote something very similar to this a while ago, with a couple key differences:
I only create the promise when the data is already in the cache and return the native promise when an actual request is initiated.
I added a third state for when a request for the resource is already pending.
A simplified version of the code looks like this:
module.factory("templateService", function ($templateCache, $q, $http) {
var requests = {};
return {
getTemplate: function getTemplate(key, url) {
var data = $templateCache.get(key);
// if data already in cache, create a promise to deliver the data
if (data) {
var deferred = $q.defer();
var promise = deferred.promise;
deferred.resolve({ data: data });
return promise;
}
// else if there is an open request for the resource, return the existing promise
else if (requests[url]) {
return requests[url];
}
// else initiate a new request
else {
var req = $http.get(url);
requests[url] = req;
req.success(function (data) {
delete requests[url];
$templateCache.put(key, data);
});
return req;
}
},
};
});

Angular route resolve for sessionStorage login variable

I've built a login system using Angular JS. When the user logs in, a session storage variable is set and they are redirected to a dashboard page (should only be accessible when logged in)"
$window.sessionStorage["isLoggedIn"] = true;
$location.path("/dashboard");
Now I want to use resolve on my any routes that required the user to be logged in. I find the documentation for this very confusing and can't understand it. If the user is not logged in and tries to access one of these pages, they need to be shown a message saying they can't access that page.
app.config(function($routeProvider) {
$routeProvider.when("/dashboard", {
templateUrl : "framework/views/dashboard.html",
controller : "DashboardCtrl",
title: "Dashboard",
resolve: {
//how does this work?!
}
});
app.factory("loginCheckService", function(){
//check sessionStorage and return?
});
You would rather listern for locationChangeStart event, perform validations (auth), prevent the route change (if required) and raise some events (unauthroized) to show the login form.
something like
app.run(function($rootScope,LoginService){
$rootScope.$on('$locationChangeStart',function(event){
if(!LoginService.isUserLoggedIn()){
event.preventDefault();
//LoginService.raiseUserNotLoggedIn(); OR
$rootScope.$broadcast('UserNotLoggedIn');
}
});
});
app.controller('LoginFormController',function($scope){
$scope.userLoggedIn=true;
$scope.on('UserNotLoggedIn',function(){
$scope.userLoggedIn=false;
});
});
Resolve allows you to define a set of tasks that will execute before the route is loaded. It is essential just a set of keys and functions, which allow you to do things like asynchronous http requests, run code snippets, set values, etc (really whatever you want), prior to page load.
So if you had a service that made a http get request and returned a promise to ensure a session exists on the server each time a route occurs, resolve guarantees that it will not load the page until the http request is fulfilled, and the promise is a success. In other words if the fulfilled promise fails the page will not load:
.config([ '$routeProvider', function( $routeProvide ) {
$routeProvider.when('/dashboard', {
templateUrl: 'framework/views/dashboard.html',
controller: 'DashboardCtrl',
controllerAs: 'dashCtrl',
resolve: {
DateOfBirth: ['Date', function( Date ) { // random example of other uses for resolve
return Date.getCurrentYear() - 37;
}],
AuthUser: ['$q', '$location', 'UserSession',
function( $q, $location, UserSession) {
return UserSession.getSession()
.then(function( success ) { // server response 200 = OK
// if you are in here the promise has succeeded and
// the page will load
// can do whatever you want
return success
}, function( error ) { // server response not 200 = Not OK
// if you are in here the promise has failed and
// the page will not load
// can do whatever you want
// if unauthenticated typically you'd:
$location.path('/login);
$location.replace();
// for this read up on promises, but promises can be
// chained, and it says move me to next error promise
// in the chain. if you don't use this it will assume
// the error was handled and pass you to the next
// success in chain. So good practice even if you're
// not chaining
return $q.reject( error );
});
}];
}
})
}]);
Another nice thing about resolve is the keys are injectable. So you can pass the result to your controller:
.controller('DashboardCtrl', ['AuthUser', 'UserSession', 'DateOfBirth'
function(AuthUser, UserSession, DateOfBirth) {
// show all the errors you want by accessing the AuthUser
// data contained in the server response, or just read the
// server response status
var self = this;
self.status = AuthUser.status;
self.response = AuthUser.data;
}]);
Then in your UI you can ngShow or ngBind blah blah on the result using dashCtrl.response or dashCtrl.status, or whatever you decide on doing with the resolved data, and show your errors knowing the page never loaded.
I'd suggest that on route you check the session instead of storing it on the client. Also, keep in mind the resolve only works on routes, but if you're making calls to the server that don't require routing you'll want to look up how to use interceptors. They allow you to peak at the outgoing and incoming server requests/responses unrelated to routing, so those which occur while you're currently on a specific page like /dashboard/home that don't trigger a route, and just simply update /home content.

AngularJs: Have method return synchronously when it calls $http or $resource internally

Is there a way to wait on a promise so that you can get the actual result from it and return that instead of returning the promise itself? I'm thinking of something similar to how the C# await keyword works with Tasks.
Here is an example of why I'd like to have a method like canAccess() that returns true or false instead of a promise so that it can be used in an if statement. The method canAccess() would make an AJAX call using $http or $resource and then somehow wait for the promise to get resolved.
The would look something like this:
$scope.canAccess = function(page) {
var resource = $resource('/api/access/:page');
var result = resource.get({page: page});
// how to await this and not return the promise but the real value
return result.canAccess;
}
Is there anyway to do this?
In general that's a bad idea. Let me tell you why. JavaScript in a browser is basically a single threaded beast. Come to think of it, it's single threaded in Node.js too. So anything you do to not "return" at the point you start waiting for the remote request to succeed or fail will likely involve some sort of looping to delay execution of the code after the request. Something like this:
var semaphore = false;
var superImportantInfo = null;
// Make a remote request.
$http.get('some wonderful URL for a service').then(function (results) {
superImportantInfo = results;
semaphore = true;
});
while (!semaphore) {
// We're just waiting.
}
// Code we're trying to avoid running until we know the results of the URL call.
console.log('The thing I want for lunch is... " + superImportantInfo);
But if you try that in a browser and the call takes a long time, the browser will think your JavaScript code is stuck in a loop and pop up a message in the user's face giving the user the chance to stop your code. JavaScript therefore structures it like so:
// Make a remote request.
$http.get('some wonderful URL for a service').then(function (results) {
// Code we're trying to avoid running until we know the results of the URL call.
console.log('The thing I want for lunch is... " + results);
});
// Continue on with other code which does not need the super important info or
// simply end our JavaScript altogether. The code inside the callback will be
// executed later.
The idea being that the code in the callback will be triggered by an event whenever the service call returns. Because event driven is how JavaScript likes it. Timers in JavaScript are events, user actions are events, HTTP/HTTPS calls to send and receive data generate events too. And you're expected to structure your code to respond to those events when they come.
Can you not structure your code such that it thinks canAccess is false until such time as the remote service call returns and it maybe finds out that it really is true after all? I do that all the time in AngularJS code where I don't know what the ultimate set of permissions I should show to the user is because I haven't received them yet or I haven't received all of the data to display in the page at first. I have defaults which show until the real data comes back and then the page adjusts to its new form based on the new data. The two way binding of AngularJS makes that really quite easy.
Use a .get() callback function to ensure you get a resolved resource.
Helpful links:
Official docs
How to add call back for $resource methods in AngularJS
You can't - there aren't any features in angular, Q (promises) or javascript (at this point in time) that let do that.
You will when ES7 happens (with await).
You can if you use another framework or a transpiler (as suggested in the article linked - Traceur transpiler or Spawn).
You can if you roll your own implementation!
My approach was create a function with OLD javascript objects as follows:
var globalRequestSync = function (pUrl, pVerbo, pCallBack) {
httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState == 4 && httpRequest.status == 200) {
pCallBack(httpRequest.responseText);
}
}
httpRequest.open(pVerbo, pUrl, false);
httpRequest.send(null);
};
I recently had this problem and made a utility called 'syncPromises'. This basically works by sending what I called an "instruction list", which would be array of functions to be called in order. You'll need to call the first then() to kick things of, dynamically attach a new .then() when the response comes back with the next item in the instruction list so you'll need to keep track of the index.
// instructionList is array.
function syncPromises (instructionList) {
var i = 0,
defer = $q.defer();
function next(i) {
// Each function in the instructionList needs to return a promise
instructionList[i].then(function () {
var test = instructionList[i++];
if(test) {
next(i);
}
});
}
next(i);
return defer.promise;
}
This I found gave us the most flexibility.
You can automatically push operations etc to build an instruction list and you're also able to append as many .then() responses handlers in the callee function. You can also chain multiple syncPromises functions that will all happen in order.

Categories