AngularJS ui-router save previous state - javascript

I want to save the current state, I don't mean saving url and params, I want to save the entire view with scopes.. In fact, I want to implement a facebook like search system, but differently. I want to display the results on the entire page, so I'll replace the current main State, and when the search bar will be cleared, then we will restore the previous state. I don't want to rebuild the state to restore the state instantly (no server requests) + to avoid complicated operations (eg. infinite scroll data..). I've found ui'router extras (sticky state) plugin, but we need to define what state to save with states definitions..
I apologize for my bad english..

You can save the date in the local storage using angular local storage. It will save you a trip to the server for data.

This is an architectural decision. The answer to this question will not be short and can be very subjective and different people will have different ways to implementing it.
What I would suggest is that you leverage local storage and do all actions on data present in local storage.
The way you would do this is to first understand that all server requests need to be done through A service that return what's present in localstorage is network is offline.
So all AJAX calls will be done from the controller like this
var getMeRequest = AuthService.getMe();
getMeRequest.promise.then(function(response){
if(response.cached) {
// $update views if need be
} else {
//Some code
}
});
And in the AuthService file, you'd do it like this:
// In AuthService
this.getMe() = function () {
if(network.online) {
//Make AJAX call
// Update localstorage with whatever response
return {response: locastorageData, cached:false};
} else {
return {response: localstorageData, cached:true};
}
};
This will not only let you make all the ajax calls as if you were online, but also allow the controller to update the view based on whether the response is cached or not.

Related

How to have a request scoped variable in remix JS?

I'm new to remix Js and wanted to understand what the best way is to get this done.
I have an app that calls a function which in turn calls an API to load user details. This user information is used to paint multiple things like header, footer and load the appropriate view for the user.
The thing I notice is that currently for a single page load request from the browser, the function (and in turn the API) is called independently multiple times by the module that renders the header, footer, page etc. What I wanted to know is if it is possible to have the user details saved in a variable so that for a single request all the modules can use this variable and it needs to be only fetched once per browser request.
I tried the following but it did not work
let userDetails;
export function getUserDetails(userId){
if(!userDetails){
console.log("Calling API to fetch user details);
//call the API
userDetails = async getUserById(userId);
}
return userDetails;
}
One option we have is to save the user details in the browser/session but that is not allowed due to some policies. What would be the best way to achieve this so that we only need to call the API once per request from the browser and save subsequent calls for a single page load to fetch the same info ?
TLDR : what would be the best way to initialize an object in an remix server module so that its value could be accessible by other modules and it has to be set only once per a request from a browser.
Dataloader can be used to solve this issue.
It offers request-level caching (and batching) which means parallel loaders on the initial HTML request can cooperate while remaining independent.
Example usage with remix can be found here.
// Root.tsx
export const loader = async ({ context }) => {
const user = await context.loaders.userById.get("user_1");
return json({ user });
}
// ...
// Child.tsx
export const loader = async ({ context }) => {
// This will await the same promise created in the Root.tsx loader
const user = await context.loaders.userById.get("user_1");
return json({ user });
}
// ...
Having said that, I would warn that the abstraction cost of dataloader is quite high and to only use it if you really can't afford to call into your API multiple times (loaders run in parallel so it's more an issue of upstream load over performance).
It's also worth noting that SPA navigations won't see the benefit because each loader is called as a separate request from the browser so a request-level cache will be ineffective.

How to pass data from ActionFunction to Loader function in Remix.run?

I'm making an application with Remix.run. It consists of multiple steps where user fills forms and in the end gets success screen. All form steps are located on single route, but success is on it's own route.
How everything works
I have redux-like (actually XState, but it is not important) message processing on server. It receives current (or initial for initial load) state, message type and data from fields. It then returns new state to client and page is rendered based on that state. Server don't have any storage an I don't want to introduce one, since it will complicate things.
// form page
export async function loader() {
return json(await getInitialState())
}
export async function action({request}) {
let fd = await request.formData();
let {current_state, message_type, ...data} = Object.fromEntries(fd.entries());
return json(await getNextState(current_state, {type: message_type, data}));
}
export function unstable_shouldReload({submission}) {
return !submission
}
export default FormPage() {
let loaderData = useLoaderData();
let actionData = useActionData();
// awful, isn't it?
let currentState = actionData || loaderData;
}
My problems
First my problem was that loader was triggered on every form submit. So, ActionFunction returns new state, but LoaderFunction returns initial state. It would be fine, but to get initial state, loader runs API, so it's wasted calls. I solved it with unstable_ShouldReload, and it's kind of ok, but not really.
Now I got to final step of the form and feel that I have same problem, but now I have separate route, so I can't just prevent running LoaderFunction. I need LoaderFunction to actually run on new page, and get data passed from previous steps for it. Basically, it's the same problem - I need to use data posted in form usable inside loader.
I tried to replace code above using session cookie with all the data, but I have other problems with it. Action returns cookie with session data, loader reads it and returns new state. Unfortunately it fails in few scenarios. If there is current session, user can reload page, prevent sending info again and get result as data was sent. It is not how web "usually" works. I can change it, by destroying session on each load, but then very first POST request yields no result, if there is no JS on user side. Otherwise it is best solution yet (but limited by size of the cookie).
Question
How to pass data from last action to next loader, or process POST request in loader itself?
How to pass data from last action to next loader
The only way is using sessions and or cookies. If you want to only be able to read the session data once and ensure it's not there anymore after a reload you could use session.flash(key, value), then in your loader, when you do session.get(key) it will remove the flash key from the session, if you commit the session in the loader then the cookie will be updated to don't have that key anymore, after a reload it will not be there.
process POST request in loader itself
This is not possible, loader functions only handle GET and OPTIONS requests, for every other request method only the action function is used.

In marionette mvc pattern, where to put different get API calls

For example I have the following server routes set up for my user entity:
GET /users/ // gets collection of users
GET /users/:id // gets user :id
GET /users/me // gets the current user
At the beginning of my app I want to get the current user from the server and store it... Something along the lines of:
App.addInitializer(function () {
$.get('/users/me')
.done(function processCurrentUser (userJson) {
App.user = new User(userJson);
});
});
My question is where this API call should actually reside. Would it be better to have something along the lines of:
App.addInitializer(function () {
App.user = new User();
App.user.fetchMe(); // performs the api call above
});
Or should I be doing something inside of a controller?
Thanks for the help!
When doing a fetch, I always worry about how its asyn behavior is going to affect the components that depend on that data. If there are no downriver components that will need the data before it can be reasonably expected to return, then there's technically nothing wrong with your approach.
There is, however, another possible way of loading your globals. What I often do (and for a user's list, too, it so happens) is bootstrap the data to the initial load page. I generally load it on the window variable. So for your example, in your backend template,
<script>
window.globals = {};
window.globals.currentUser = #Html.Raw(Json.Encode(ViewBag.User))
</script>
Of course, you can replace #Html.Raw(Json.Encode(ViewBag.User)) (we use C#) with your favorite backend model.
Then in your app start you're guaranteed to have the models:
App.addInitializer(function () {
App.user = new User(window.globals.currentUser);
});

How to improve data handling with meteor

Still on my meteor app, i'd like to now how to improve my data handling from minimongo.
Used to SQL / PHP, I'd like to know how to find() an object from my minimongo collection only one time, and access each of its properties with helpers, without having to re-access the collection each time.
Until now, what I did was something like that :
Template.profile.helpers({
name: function(e, tmpl){
return Meteor.users.FindOne({_id: Meteor.userId()}.profile.name;
},
phone: function(e, tmpl){
return Meteor.users.FindOne({_id: Meteor.userId()}.profile.phone;
}
[...]
});
But it's getting boring and i guess there must be a more efficient way to deal with it, something where I could load my entire users information only one time, and then display with a helper taking one parameter, to display the data like that : {{data name}}, {{data phone}}
With only one helper like that :
Template.profile.helpers({
data: function(aString){
if (aString == "phone)
return [...].phone;
}
}
[...]
});
Of course, I can use a session value, but I'm not sure it's as relevant as I could do.
Another thing : how to end a Meteor session ? Because with PHP, the session ends at the closure of the browser, and cookies stay for a given duration, but with meteor session, i never have to reconnect as day after day, my logs seems to stay.
Would someone guide me through this or give me some good habits / tips ? I'm still reading the doc, but it's quite huge :S
Thanks you !
One thing to note here -- you're not actually sending a request to the server when you call Mongo.Collection.find() on the client -- that's getting handled by the miniMongo instance on the client, so it's not really that inefficient. Also, in this particular instance, the current user is always available as Meteor.user() -- no need to use Meteor.users.find({_id: Meteor.userId()}); (see docs for more info)
However, if you really wanted to cache that value, you could do something like this:
// JS
var data; // scoped so it's available to the whole file
Template.profile.onCreated({
data = Meteor.users.findOne({_id: Meteor.userId()}); // above scope makes this new definition available to the whole file
});
Then, to do what you're describing with the string arguments, you can do something like...
Template.profile.helpers({
data: function(aString) {
if (data) { return data[aString]; }
}
});
Perhaps a better option even is to send the Template a data context -- something like this in your HTML file:
{{> profile currentUser}} <!-- currentUser returns Meteor.user() -->
Which is directly available to your helpers as this:
Template.profile.helpers({
data: function(aString) {
return this[aString];
}
});
You can also pass data contexts through IronRouter if you're using that for your routing.
As for the Meteor session, the model is different than the model for PHP. In PHP, you store session information on the server, and every time you access the server, the browser (or client, more generally) sends along the session ID so it can look up anything pertaining to your session (see this question for a potentially better explanation). Meteor keeps track of clients that are connected to it, so the session will stay open as long as your browser is open. If you need to manipulate Meteor user sessions, take a look at this question.
I recommend finding the collection helpers package on Atmosphere. This will enable you to write currentUser.name in a template and it'll automatically return users name helper function returns.
There is no problem working like this. It doesn't matter if the function is called multiple times. It won't hurt your apps performance.
PHP and meteor sessions are different. A meteor session lasts for as long as browser window remains open or the page is refreshed. You are right that meteor sessions are not the way to go for your problem.

backbone.js cache collections and refresh

I have a collection that can potentially contain thousands of models. I have a view that displays a table with 50 rows for each page.
Now I want to be able to cache my data so that when a user loads page 1 of the table and then clicks page 2, the data for page 1 (rows #01-50) will be cached so that when the user clicks page 1 again, backbone won't have to fetch it again.
Also, I want my collection to be able to refresh updated data from the server without performing a RESET, since RESET will delete all the models in a collection, including references of existing model that may exist in my app. Is it possible to fetch data from the server and only update or add new models if necessary by comparing the existing data and the new arriving data?
In my app, I addressed the reset question by adding a new method called fetchNew:
app.Collection = Backbone.Collection.extend({
// fetch list without overwriting existing objects (copied from fetch())
fetchNew: function(options) {
options = options || {};
var collection = this,
success = options.success;
options.success = function(resp, status, xhr) {
_(collection.parse(resp, xhr)).each(function(item) {
// added this conditional block
if (!collection.get(item.id)) {
collection.add(item, {silent:true});
}
});
if (!options.silent) {
collection.trigger('reset', collection, options);
}
if (success) success(collection, resp);
};
return (this.sync || Backbone.sync).call(this, 'read', this, options);
}
});
This is pretty much identical to the standard fetch() method, except for the conditional statement checking for item existence, and using add() by default, rather than reset. Unlike simply passing {add: true} in the options argument, it allows you to retrieve sets of models that may overlap with what you already have loaded - using {add: true} will throw an error if you try to add the same model twice.
This should solve your caching problem, assuming your collection is set up so that you can pass some kind of page parameter in options to tell the server what page of options to send back. You'll probably want to add some sort of data structure within your collection to track which pages you've loaded, to avoid doing unnecessary requests, e.g.:
app.BigCollection = app.Collection.extend({
initialize: function() {
this.loadedPages = {};
},
loadPage: function(pageNumber) {
if (!this.loadedPages[pageNumber]) {
this.fetchNew({
page: pageNumber,
success: function(collection) {
collection.loadedPages[pageNumber] = true;
}
})
}
}
});
Backbone.Collection.fetch has an option {add:true} which will add models into a collection instead of replacing the contents.
myCollection.fetch({add:true})
So, in your first scenario, the items from page2 will get added to the collection.
As far as your 2nd scenario, there's currently no built in way to do that.
According to Jeremy that's something you're supposed to do in your App, and not part of Backbone.
Backbone seems to have a number of issues when being used for collaborative apps where another user might be updating models which you have client side. I get the feeling that Jeremy seems to focus on single-user applications, and the above ticket discussion exemplifies that.
In your case, the simplest way to handle your second scenario is to iterate over your collection and call fetch() on each model. But, that's not very good for performance.
For a better way to do it, I think you're going to have to override collection._add, and go down the line dalyons did on this pull request.
I managed to get update in Backbone 0.9.9 core. Check it out as it's exactly what you need http://backbonejs.org/#Collection-update.

Categories