Persisting data between routes without going to the server? - backbone.js

I am working on a log in for my backbone application and came on an issue I am not sure how to solve without making a call to the server. This brought up a discussion in my team about what the way other folks are handing this kind of thing in backbone because we think we will be running into a similar thing moving forward.
It's Friday and I'm probably just brain dead, but here goes...
We have a User Model. The login method of the View creates a new user Model and call's it's login method passing in the user's credentials and a callback function which has an object that contains the users information.
Here is the login method for our View:
login: function(event){
event.preventDefault();
var user = new App.User;
user.login($('#username').val(), $('#password').val(),
(function(msg) {
// success callback
if (msg.loggedIn) {
console.log("Authenticate successful: " + JSON.stringify(msg));
var data = { user : msg, bob : "bob", trigger:true };
console.log("Prepared data: " + JSON.stringify(data));
App.router.navigate('home',data);
} else {
console.log("Authenticate unsuccessful: " + JSON.stringify(msg));
}
}).bind(this),
function(msg) {
// failure callback
console.log("Authenticate communication failure: " + JSON.stringify(msg));
alert("Communication fail!");
App.router.navigate('login',{trigger:true});
});
}
What we are trying to figure out is how to best make this Model data available to another route (home) so we can use it in the View.
So I have this router:
routes: {
'': 'home',
'home': 'home',
'login': 'login'
},
home: function(data){
console.log(data);
}
Once we have logged the user in we need to update the route and have access to that users data, but don't want to have to make a trip back to the server to fetch it.
I am concerned because we are building a kind of "wizard" where the user may need to move forward and backward through some steps and I don't want to have to hit the server every time they navigate through the application, but it's seeming like we are going to either need to save stuff to a global variable (don't want to do this) or make a trip back to the server every time. I'm sure others have had to deal with similar issues. Just looking for some insight.
Thanks!

"it's seeming like we are going to either need to save stuff to a global variable (don't want to do this)"
To state the obvious: You're going to need to preserve state. Your options are either to transfer the state to the server and back, or hold state on the client. Since you've already identified that you don't want to pass the state via a server, you're left with preserving state between different pages (routes) on the client.
And that's what global variables are for. It sounds icky, I know, but it's also one of the main benefits that Single-Page Applications bring to the table. Statefulness. And that state will always be held by some global object.
There are better and worse ways of managing state. Having a global variable called data that you keep assigning and reassigning is obviously the worst way. You should figure out a pattern that makes sense for your requirement.
If I understood your code sample correctly, what you want to store is the information about the current user. It also seems that you already have a global variable App. It occurs to me that it would be a good idea to keep some kind of session info:
login: function(data){
doLogin({
success: function(userData) {
App.session.currentUser = userData;
App.router.navigate('home', { trigger:true });
}
});
},
home: function(data){
if(!App.session || !App.session.currentUser) {
App.router.navigate('login', { trigger:true });
return;
}
var user = App.session.currentUser;
//do something with user
}
State is not necessarily evil. What's evil is depending on global state throughout the application, which leads easily to untestable spaghetti code. But if you resolve the state dependency as "high up" in the chain as possible (e.g. in the Router), and pass the values down using constructors and method arguments, you can still keep the testability and side-effectlessness in the rest of the codebase.
Sorry that I don't have a silver bullet for you. There are some libraries, Backbone.StateManager among them, which can help in managing state, transitions and such, but essentially they don't do anything you can't do for yourself.

Use localStorage!
Modify your code to do the following:
// success callback
if (msg.loggedIn) {
console.log("Authenticate successful: " + JSON.stringify(msg));
var data = { user : msg, bob : "bob", trigger:true };
var dataString = JSON.stringify(data);
console.log("Prepared data: " + dataString;
window.localStorage.setItem("userdata",dataString);
App.router.navigate('home',data);
Now whenever you need to check if the user is logged in, do the following:
try {
var userData = window.localStorage.getItem ("userdata");
} catch (e) {
// Do something
}
The try-catch is necessary to make sure that your code doesn't barf if the authentication has never been successful.

Its too late to reply but there is another much better way to do this depending on the router engine you are using and with no local variables.
I would try to give a general example using backbone which applies to all.
Generally your router would be in one place where things are being handled. Assuming its backbone lets have the routes defined as follows.
var router = Backbone.Router.extend({
routingData: {}, // this will have the routing data
routes: {
'': 'home',
'home': 'home',
'login': 'login'
},
navigate: function(url, data) {
this.routingData["data"] = data; // whenever navigation is done, this will be reset
//This is the routing code whichever the f/w may be.
Backbone.Router.prototype.navigate(route, { trigger: true });
},
home: function(data) {
var params = this.routingData["data"]; //retreiving the routing params
console.log(params);
}
})
Now if you want to pass data, you can do
router.navigate(<URL>,<SOME DATA>)

Related

My flux store gets re-instantiated on reload

Okay. I'm kinda new to react and I'm having a #1 mayor issue. Can't really find any solution out there.
I've built an app that renders a list of objects. The list comes from my mock API for now. The list of objects is stored inside a store. The store action to fetch the objects is done by the components.
My issue is when showing these objects. When a user clicks show, it renders a page with details on the object. Store-wise this means firing a getSpecific function that retrieves the object, from the store, based on an ID.
This is all fine, the store still has the objects. Until I reload the page. That is when the store gets wiped, a new instance is created (this is my guess). The store is now empty, and getting that specific object is now impossible (in my current implementation).
So, I read somewhere that this is by design. Is the solutions to:
Save the store in local storage, to keep the data?
Make the API call again and get all the objects once again?
And in case 2, when/where is this supposed to happen?
How should a store make sure it always has the expected data?
Any hints?
Some if the implementation:
//List.js
componentDidMount() {
//The fetch offers function will trigger a change event
//which will trigger the listener in componentWillMount
OfferActions.fetchOffers();
}
componentWillMount() {
//Listen for changes in the store
offerStore.addChangeListener(this.retriveOffers);
}
retrieveOffers() {
this.setState({
offers: offerStore.getAll()
});
}
.
//OfferActions.js
fetchOffers(){
let url = 'http://localhost:3001/offers';
axios.get(url).then(function (data) {
dispatch({
actionType: OfferConstants.RECIVE_OFFERS,
payload: data.data
});
});
}
.
//OfferStore.js
var _offers = [];
receiveOffers(payload) {
_offers = payload || [];
this.emitChange();
}
handleActions(action) {
switch (action.actionType) {
case OfferConstants.RECIVE_OFFERS:
{
this.receiveOffers(action.payload);
}
}
}
getAll() {
return _offers;
}
getOffer(requested_id) {
var result = this.getAll().filter(function (offer) {
return offer.id == requested_id;
});
}
.
//Show.js
componentWillMount() {
this.state = {
offer: offerStore.getOffer(this.props.params.id)
};
}
That is correct, redux stores, like any other javascript objects, do not survive a refresh. During a refresh you are resetting the memory of the browser window.
Both of your approaches would work, however I would suggest the following:
Save to local storage only information that is semi persistent such as authentication token, user first name/last name, ui settings, etc.
During app start (or component load), load any auxiliary information such as sales figures, message feeds, and offers. This information generally changes quickly and it makes little sense to cache it in local storage.
For 1. you can utilize the redux-persist middleware. It let's you save to and retrieve from your browser's local storage during app start. (This is just one of many ways to accomplish this).
For 2. your approach makes sense. Load the required data on componentWillMount asynchronously.
Furthermore, regarding being "up-to-date" with data: this entirely depends on your application needs. A few ideas to help you get started exploring your problem domain:
With each request to get offers, also send or save a time stamp. Have the application decide when a time stamp is "too old" and request again.
Implement real time communication, for example socket.io which pushes the data to the client instead of the client requesting it.
Request the data at an interval suitable to your application. You could pass along the last time you requested the information and the server could decide if there is new data available or return an empty response in which case you display the existing data.

Synchronous service using async service in Angular [duplicate]

This question already has answers here:
AngularJS : Initialize service with asynchronous data
(10 answers)
Closed 7 years ago.
I have a link generator service that is able to generate links to specific content types (users' details page, content items' details pages etc).
This service is really easy to use and has synchronous functions:
links.content(contentInstance); // /items/123
links.user(userInstance); // /users/234
I now have to introduce separate routing for logged in user to change from /users/id to /users/me.
The only change I'd need to add to my link generator service is to check whether userInstance.id == loggedInUser.id and return a different route URL. This is not a problem as long as my logged-in user's info would be synchronously available. but it's not...
I have a userService.getMyInfo() that returns a promise. The first time it's called it actually makes a server request but subsequent calls return a resolved promise with already cached data.
So how should I implement my user link URL generation in my link generator service?
Edit
Ok. So to see better what I have at the moment and where I'm having the problem. I'm pretty aware that async will stay async and that it can't be converted to synchronous (and it shouldn't be).
This is some more of my code, that will make it easier to understand.
linkGenerator.user
angular
.module("App.Globals")
.factory("linkGenerator", function(userService) {
...
user: function(userInstance) {
// I should be calling userService.getMyInfo() here
return "/users/{0}/{1}".format(userInstance.id, userInstance.name);
},
...
});
userService.getMyInfo
angular
.module("App.Globals")
.service("userService", function($q, cacheService, userResource) {
...
getMyInfo: function() {
if (cacheService.exists("USER_KEY"))
// return resolved promise
return $q.when(cacheService.get("USER_KEY"));
// get data
return userResource
.getCurrentUser()
.$promise
.then(function(userData) {
// cache it
cacheService.set("USER_KEY", userData);
});
},
...
});
Controller
angular
.module("App.Content")
.controller("ItemDetailsController", function(linkGenerator, ...) {
...
this.model = { ... };
this.helpers = {
...
links: linkGenerator,
...
};
...
});
View
View uses ItemDetailsController as context notation.
...
<a ng-href="{{::context.helpers.links(item.author)}}"
ng-bind="::item.author.name">
</a>
...
Notes
As you can see my view generates links to item authors. The problem is that my linkGenerator (as you can see from the code may not have the information yet whether it should generate one of the correct links to user details view.
I know I can't (and don't want to) change my async code to synchronous, but what would be the best way to make this thing work as expected?
One possible solution
For the time being I've come up with a solution that does the trick, but I don't really like it, as I have to supply my logged in user's ID to linkGenerator.user(userInstance, loggedInUserId) function. Then I set up my routing so that I add resolve to my route where I call userService.getMyInfo() which means that my controller is not being instantiated until all promises are resolved. Something along this line:
routeProvider
.when("...", {
templateUrl: "path/to/my/details/template",
controller: "ItemDetailsController".
controllerAs: "context",
resolve: {
myInfo: function(userService) {
return userService.getMyInfo();
}
}
})
...
Then I also add an additional helper to my controller
this.helpers = {
...
links: linkGenerator,
me: myInfo.id,
...
};
And then I also change link generator's function by adding the additional parameter that I then supply in the view.
linkGenerator.user = function(userInstance, loggedInUserId) {
if (userInstance.id === loggedInUserId)
return "users/me";
return "users/{0}/{1}".format(userInstance.id, userInstance.name);
}
and in the view
<a ng-href="{{::context.helpers.links.user(item.author, context.helpers.me)}}"...
And I don't to always supply logged in user's ID. I want my service to take care of this data on its own.
There is no way to make anything in JavaScript that is asynchronous at some point synchronous again. This is a ground rule of how concurrency works - no blocking for waiting for stuff is allowed.
Instead, you can make your new method return a promise and use the regular tools for waiting for it to resolve.
links.me = function(){
var info = userService.getMyInfo();
return info.then(info => { // or function(info){ if old browser
// generate link here
return `/users/${info.id}`; // or regular string concat if old browser
});
}
Which you'd have to use asynchronously as:
links.me().then(function(link){
// use link here
});

is it possible to use getReactively on $rootScope variables?

I have a case that I want to update the ui based on the user so if a user was log-out and log-in as different user, a score of the user in the ui is changed. The way I thought about doing it is by looking at $rootScope.currentUser.
from my testing it seems like there is some kind of access to the $rootScope, but it is not getting updated on user change (unless I have a bug in my code). here is the relevant code:
$meteor.autorun($scope, function() {
if (null !== $rootScope.currentUser) {
$scope.userForScore = $scope.$meteorObject(Userinfo, {user_id: $scope.getReactively('currentUser')._id});//$rootScope.currentUser._id
}
});
$scope.userScore = function(){
if ($scope.userForScore === undefined || $scope.userForScore.score === undefined) {
return 1;
}
return $scope.userForScore.score;
};
Userinfo - is a collection I created in the server.
Yes it is possible and it works exactly like you did it.
Your problem was unrelated to getReactively, I've created a pull request to your repo to fix it:
https://github.com/Urigo/angular-meteor/issues/415#issuecomment-113925910
You can use Meteor.user() which is a reactive var. No need to add an additional layer of watchers.

AngularJS best practice REST / CRUD

What is the best practice of doing CRUD operations via REST with AngularJS?
Specially what is the Angular-Way here. By this I mean the way using the least code and the most default angular settings to achive this.
I know $resource and it's default operations. Where I'm not sure is how to implement/name the endpoints and which controllers to use.
For this example I would like to implement a simple user-management system which creates / updates /deletes / lists users. Since I'm implementing the Server-Endpoints by myself I'm completely free in doing it in the most angular friendly way.
What I like as answer is something like:
Server-Endpoints:
GET /service/users -> array of users
GET /service/user/new -> return an empty user with default values which has no id
POST /service/user/new -> store a new user and create an id. return the saved user.
POST /service/user/:ID -> save an existing user. Return the saved user
DELETE /service/user/:ID -> delete an existing user
Angular-Services:
.factory( 'User', [ '$resource', function( $resource ){
return $resource( '/service/user/:userId', { userId: '#id' } )
[...]
}])
Routing:
.when( '/users', {
templateUrl: BASE + 'partials/user-list.html',
controller: 'UserListCtrl' } )
.when( '/user/new', {
templateUrl: BASE + 'partials/user-edit.html',
controller: 'UserNewCtrl' } )
.when( '/user/:userId', {
templateUrl: BASE + 'partials/user-edit.html',
controller: 'UserEditCtrl' } )
...
Controllers:
UserListCtrl:
$scope.data = User.get(...)
UserNewCtrl:
$scope.user = User.get( { userId: "new" } )
...
Note that I'm not interessted in opinion what is the best (tm) way to do this but I'd like to know what is the Angular intended way (which I think should produce the least code because it can use the most default).
EDIT:
I'm looking for the whole picture. What I would love would be an answer like e.g.: "You can do this using online 3 Endpoints [...], 2 routes [...] and 2 controllers [...] if you do it this way using that defaults ..."
There is no Angular prescribed way for what you are asking. It's up to you to determine the implementation detail.
Typically I only use two controllers and templates per resource:
ListController
FormController
The Form controller is used for both Edit and Create operations. Use the resolve option in your route definitions to pass in either User.get() or User.new() and a flag indicating if this is an edit or create operation. This flag can then be used inside your FormController to decide which save method to call. Here's a simple example:
.when( '/users', {
templateUrl: BASE + 'partials/user-list.html',
controller: 'UserListCtrl' } )
.when( '/user/new', {
templateUrl: BASE + 'partials/user-form.html',
resolve: {
data: ['User', function(User) { return User.new(); }],
operation: 'create'
}
controller: 'UserFormCtrl' } )
.when( '/user/:userId', {
templateUrl: BASE + 'partials/user-form.html',
resolve: {
data: ['User', '$route', function(User, $route) { return User.get($route.current.params.userId); }],
operation: 'edit'
}
controller: 'UserFormCtrl' } )
And your form controller:
app.controller('UserFormCtrl', ['$scope', 'data', 'operation', function($scope, data, operation){
$scope.data = data;
$scope.save = function() {
if (operation === 'edit') {
// Do you edit save stuff
} else {
// Do you create save stuff
}
}
}]);
You can go a step further and create a base list and form controller to move stuff like error handling, server-side validation notifications etc. In fact for the majority of CRUD operations you can even move the save logic to this base controller.
My research into a similar quest has lead me to this project "angular-schema-form" https://github.com/Textalk/angular-schema-form.
For this approach...
You make a JSON-Schema that describes your data. Then augment it with another little JSON-struct that describes a "form" (ie. view specific info that does not belong in the data schema) and it makes a UI (form) for you.
One cool advantage is that the Schema is also useful in validating the data (client and server side), so that is a bonus.
You have to figure out what events should fire off GET/POST/... back to your API. but that would be your preference, eg. Hit the API for every key stroke OR the classic [Submit] button POST back style OR something in between with a timed Auto Save.
To keep this idea going, I think that it is possible to use StrongLoop to make a quick API, which (again) uses your data's schema (augmented with some storage details) to define the API.
no <3 uses of that schema, see [http://json-schema.org/] which is central to this approach.
(read: no less than three :)
You maybe mixing things up. CRUD operations at API level are done using $resource and these may or may not map to UI.
So using $resouce if you define resource as
var r = $resource('/users/:id',null, {'update': { method:'PUT' }});
r.query() //does GET on /users and gets all users
r.get({id:1}) // does GET on /users/1 and gets a specific user
r.save(userObject) // does a POST to /users to save the user
r.update({ id:1 }, userObject) // Not defined by default but does PUT to /users/1 with user object.
As you see the API is resource full but is in no way linked to any UI view.
For view you can use the convention you have defined, but nothing specific is provided by Angular.
I think what you are looking for can be found in http://www.synthjs.com/
Easily create new RESTful API resources by just creating folders and
naming functions a certain way.
Return data or promises from these
functions and they'll be rendered to the client as JSON.
Throw an
error, and it'll be logged. If running in dev mode, the error will
also be returned to the client.
Preload angular model data on page load (saving an extra
roundtrip).
Preload html view on page load (saving another extra
roundtrip!)
A simplified project structure
where front-end code (angular code, html, css, bower packages, etc)
is in the 'front' folder and back-end code (node code and node
packages) are in the 'back' folder.
A command-line tool for
installing third party packages, using npm + bower, that auto-updates
manifest files.
Auto compilation of assets on request for dev, and
pre-compilation for prod (including minification and ngmin).
Auto-restarts the server when changes are detected.
Support for
various back-end and front-end templates to help get a new project
going quickly.

Callbacks in views

This may be more of a question around design pattern - I hope it makes sense.
I am using backbone - am developing a relatively simple app where user can add requests (where the request model is simply heading, description and price). The only requirement to add the requests is that a user is 'logged in'.
However I wish that the user 'adds' the request before checking if the user is logged in (remove one barrier). By that I mean that the user fills the form in, if not registered they have to register and then the request is just sent, if they were logged in it would just be sent. So initially the 'add request' view is rendered, when save is triggered this will call the save on the model which calls the ajax request on the server. The response will either return true (the user was logged in and the request added) or false (the user was not logged in).
So assuming that the user was not logged in - then I would wish to render a new view 'register' which has the option for the user to switch to 'login' (another view). So User in theory could go from
Request (save) -> Register -> Login -> Request (save)
So presuming that the user then registered (filled in the form which then called the registers view save method which then called the registers model save and returned ok). I would then wish to once again call the 'request' save method once again (as now the user would be logged in). However I do not want the register/login tied to the Request view - as in theory a new view (lets say I had a sent message view) would want similar functionality e.g. try and make the request, if failed switch view to register - perform save and then call some callback.
So the question is what is the right way to do this?
1) Do I create some closure inside the request view referencing the 'save' function and store it in a global to be called by register/login onsuccess?
2) Create a closure as above and pass that to the register etc (and if so how would I pass this given register/login is a view).
3) Pass a reference to 'this' of the request view?
So far all I have is, so in the else I would render the 'register' view but would love to know best way to do this without it getting very ugly?
save : function(event){
if(this.model.isValid() == true) {
this.$("#general_error").hide();
this.model.set({'formattedPrice' : TB_H.formatPrice(this.model.get('currency'), this.model.get('price'))});
self = this;
this.model.save(this.model.toJSON(), {
success: function(m, y) {
if(y.status == true) {
self.model = new TB_BB.Request();
Backbone.ModelBinding.bind(self);
Backbone.Validation.bind(self);
$('#add-offer-button').show();
} else {
if(y.errors[0] == 'not logged in') {
this.$("#general_error").html('You are not logged in');
this.$("#general_error").show();
} else {
_.each(y.errors, function(key, val) { this.$("#general_error").html(key) });
this.$("#general_error").show();
}
}
}, error : function(m,y) {
this.$("#general_error").show();
this.$("#general_error").html("Something bad happened - please try again")
}
}
);
}
Greatly appreciate any help!
I noticed this after asking a similar question, which I just deleted. I'm thinking this isn't the way most people are doing it in backbone.
what I did instead was pass my different routes to the same method and if the ids were not null, I'd call the route.
So I have a view
base_view = Backbone.Views.extend({
initialize: function(id,a_id,b_id){
this.id = id;
this.a_id = a_id;
this.b_id = b_id;
Myapp.data = new Myapp.Model.Base();
Myapp.data.url = '/data_url/'+id;
Myapp.data.fetch(Myapp.data, {
success: function(response){
// i have some nested collections, and models so i fill those in here
Myapp.mainModel = new First_Child_Collection(response.attributes.first_child_array);
}, error: function(){
alert('oops couldn't get data');
}
});
Myapp.data.bind("fetched",this.render,this);
},
render: function(){
new Main_View();
new Sub_View_1(this.id);
new Sub_View_2(this.a_id);
new Sub_View_3(this.b_id);
}
});
Then in my routes, rather than having a new route for each, I have
routes: {
"app/new": "new",
"app/:id/edit/a/:a_id/b/:b_id": "edit"
}
edit {
new base_view(id,a_id,b_id);
}
I'm not sure if this is perfect, but I think it is DRY'r than the other options. I just check that a_id or b_id are not undefined before passing those views.
Hope it helps.

Resources