How to persist the view of a route in angular - javascript

Is there any way to persist the view of a page in angular so that as a User I can get the same view when I revisit the same route.
For eg. RouteA has a search bar and when user search something it loads results below, now if user has searched something on that page and
after that he leave that page and move to RouteB for some other operation. When he will revisit the page it should have the same view, that is searched term in the search bar and loaded data in the grid.

For your use case, you can save the FormGroup instance before the user navigates to a different route.
if (this.searchFormGroup.valid) {
this.searchService.setLastSearch(this.searchFormGroup);
}
where the definition for setLastSearch could be like
setLastSearch(searchFormGroup) {
this.lastSearch = searchFormGroup;
}
And when the user revisits the page, in the ngOnInit method check if there is already a saved search(the formGroup instance you saved before navigating).
this.lastSearch = this.searchService.getLastSearch();
. If the answer is yes use the patchValue to populate the current formGroup instance. Something like below
if (this.lastSearch) {
this.patchLastSearchedValue();
}
where the code for patchLastSearchedValue could be like
patchLastSearchedValue(): void {
Object.keys(this.lastSearch.value).forEach(name => {
this.searchFormGroup.controls[name].patchValue(
this.lastSearch.controls[name].value
);
});
}

Related

Angular: data passed from another component becomes undefined on the page reload

In my angular app, I need a component to pass data to another component which do not have parent child relationship.I have an Orders table in one component with each row representing an order. When user clicks on any specific row, I need navigation to OrderDetails component& pass the order object representing the clicked row along with it
validation.component.html
<tr *ngFor="let order of allOrders" (click)="onNavToOrderDetails(order)">
<td>{{order.id}}</td>
</tr>
validation.component.ts
onNavToOrderDetails(order) {
this.router.navigate(['orderdetails'], { state: {data: order} });
}
orderdetails.component.ts
order;
ngOnInit(): void {
this.order=history.state.data;
console.log(this.order);
}
orderdetails.component.html
<p>{{order.id}}</p>
orderdetails.component.html displays order id when navigated from validation.component.html but refreshing the orderdetails page cause order id to disappear. I understand on the page refresh history.state.data becomes undefined but how to get around this issue? Since the app is a SPA, storing the data from the validationcomponent to a service and using that service in the orderdetailscomponent won't work either.
Page refresh means reloading the entire angular app ,and order object stored in the serivce by the validation componentwill also disappear. How to solve this issue? I want previously stored data in a serivce to stay unaffected and display it again on page reload?
There are 3 ways to handle it:
Use sessionStorage (don't go for localStorage) , but then make sure to maintain sessionStorage data as per the scenarios
ngOnInit(): void {
if(history.state.data){
this.order=history.state.data;
sessionStorage.setItem('order_page_info', JSON.stringyfy(this.order));
}else{
this.order = JSON.parse(sessionStorage.getItem('order_page_info'))
}
}
Use Cache (not recommended)
Rather than passing entire data, pass the id as router data and make server call to fetch details for the id. This would maintain id in url and so you can fetch details on refresh by calling the server
try putting ngOnChanges
ngOnChanges detects changes from other component specially from api call of services
ngOnChanges(){
this.order=history.state.data
}

Send Info after browserHistory.goBack

I'm building an App with reactjs and in some point I have a SearchForm, then I click on one of the items and in that view I have a back button.
What I want is to populate the search form with the previous data. I already achieved it with localStorage from javascript. And saving the state on componentWillUnmount, the problem is this data is always loaded even if I don't arrive from the go back.
Is there a way to add a kind of state to the goBack to tell it?
So, the best way here is to not use localStorage, and instead use history state.
When the user updates the form, you can do this (optionally debounce it for better performance):
history.replaceState(this.props.location.pathname, {
formData: JSON.stringify(formData),
});
Then when the component loads, you can access that state like this:
const {location} = this.props;
const routeState = location.state && location.state.formData;
if (routeState) {
this.setState({
data: JSON.parse(routeState),
});
}
If they refresh the page, or go to another page and go back, or go back and then go forward, they'll have that state. If they open it in a new tab, they won't have the state. This is usually the flow you want.

AngularJs change back button location while redirecting to new location

In our shop the user selects a product (/shop/products) and then gets redirected to the first customization page (/shop/details). He does some customization and clicks "next". In our controller (productDetailsController) we create a new object (Order) with the selected properties and redirect to the next page (shop/individualization?orderId=2) where that order is further customized. Whenever the user now uses the browser back button we want to make that Order available to the previous page via parameter. So we need to change the url that the back button is directing to (/shop/details?orderId=2 instead of /shop/details).
In short:
/shop/products -nextButton- /shop/details -nextButton- shop/individualization?orderId=2
shop/individualization?orderId=2 -BROWSER-BACK- /shop/details?orderId=2
If I just use $location.replace() inside the controller it will back-button from shop/individualization?orderId=2 to the product selection /shop/products.
If I do two $location.path() inside one digest cycle it will just ignore the first one:
// inside the order creation promise...
var search = {orderId: createdOrder.id};
$location.path("/shop/details").search(search);
$location.path("/shop/individualization").search(search);
I can't use replace() when navigating from /shop/products to /shop/details because using the back button from there still needs to navigate to /shop/products.
Any suggestions?
Thanks!
Outlining a possible solution:
a service keeps track of the order (could be just the orderId, depends on your exact use case)
shop/individualization (or the action of shop/details that navigates to shop/individualization) sets the orderId in the service
both shop/individualization and shop/details define reloadOnSearch: false
both shop/individualization and shop/details bind the URL search parameter to the service; the controller logic could be:
app.controller("XXX", function($scope, orderService, $location) {
// initialization
var orderId = $location.search("orderId");
if( orderId ) {
orderService.setOrderId(orderId); // setOrderId() could handle loading the order too
}
else {
orderId = orderService.getOrderId();
// reloadOnSearch is false, so this doesn't trigger a navigation
if( orderId ) $location.search("orderId",orderId);
}
// ...rest of controller logic
});
Keep in mind:
What you are doing makes shop/details bookmarkable, so that the user can return at any time to see an old order. The system should be prepared to handle this case, e.g. reload the order from the server. If bookmarking is not desired, things are simplified: just use the service and drop the search param altogether.
You may also want to remove the order object from the orderService at some time.

How to redirect user after successful login?

Update:
After implementing the below suggestion by Rob Sedgwick, it has become apparent that the redirect only works when the user manually "F5" refreshers the browser (Chrome). What I need to achieve is that this happens automatically in the code so the redirect happens without the user having to hot refresh. Thanks for help with this last part.
At the moment ManageTodosView from what I understand is the first action after the user has been logged in. It prints a list of to do items set up by the user. A working example can be found here http://parseplatform.github.io/Todo/ and the code is https://github.com/ParsePlatform/Todo
I'm using to code to really get user logins to work, I'm not to worries about what the output of the rest of the code is because the long term plan will be to remove it, for the time being its helpful to keep in place to show that the app functioning correctly.
I'm using this code as a base to build a web app. At the moment, once the user is logged in, they are displayed data on the same page.
I want to be able to change this so that after they login, the user is redirected to a different page and the information is then displayed to them there.
The reason for this is that the index page is just a landing/login page and I want to redirect them to a more structured HTML page with menus, etc.
Within the JS code, do I just put in a redirect, something like:
self.location="top.htm";
to this area of the code?
// The main view for the app
var AppView = Parse.View.extend({
// Instead of generating a new element, bind to the existing skeleton of
// the App already present in the HTML.
el: $("#todoapp"),
initialize: function() {
this.render();
},
render: function() {
if (Parse.User.current()) {
new ManageTodosView();
} else {
new LogInView();
}
}
});
I have added the JS code to this JSFiddle
Update:
To address the issue of the page needing a manual fresh before the redirect works, insert
window.location.href="/someurl";
into the following code section within the todoe.js file and comment out the new ManageTodosView(); code.
Parse.User.logIn(username, password, {
success: function(user) {
window.location.href="user_home.html";
//new ManageTodosView();
self.undelegateEvents();
delete self;
},
Try this one also
window.open('url','_parent');
I would suggest a more robust template for integrating Parse Todo samples with real apps. 'Marionette' offers lots of value in real world.
If you take the time to look over the app's structure and then look at the 'loginSuccess' function (scroll to very bottom of link), its pretty straightforward to redirect. You can either use the router as shown OR you can use the Marionette aggregated events which would look like:
vent.trigger('header:loggedIn', _user);
somewhere else in any module within the app....
vent.on('header:loggedIn', function (user) {
that.setViewNew(user);
});
...
setViewNew : function (user) {
User = user;
var viewOptionsUser = {
collection : this.roleList,
events : {
'keypress #new-todo': 'createTaskOnEnter'},
parentUser : User};
this.headerRegion.show(new HeaderView(viewOptionsUser));
this.mainRegion.show(new RoleRoleListCompositeView(viewOptionsUser));
}
Try something like this to redirect your user
window.location = 'www.google.com'

Ember.js when using ModelFor('') in conjunction with the back button fails to load data

I am using Ember data and The RESTAdapter with an extension for Django.
Here is a JSBin
Here is how our routes are set up:
App.Router.map(function() {
this.resource('locations');
this.resource('location', {path:'locations/:location_id'}, function() {
this.resource('items', function() {
this.route('create');
});
this.resource('item', { path:'item/:item_id' }, function() {
this.route('edit');
});
});
});
App.LocationsRoute = Ember.Route.extend({
model: function () {
return this.get('store').find('location');
}
});
App.ItemsRoute = Ember.Route.extend({
model: function(){
//Get the model for the selected location and grab its item list.
//This will do a call to /locations/1/items
return this.modelFor('location').get('items');
}
});
Now this all works fine when we navigate to locations/1/items. The user is presented with a list of items relevant to the location with id 1.
When the user clicks one of these items it brings the url to #/locations/1/item/1 and displays the details of the item with id 1.
Now what doesnt work is this:
When I hit the back button the #/locations/1/items route loads but it does not have its data any more and no REST call to api/locations/1/items occurs. Even though the data displayed just fine when we first navigated to #/locations/1/items.
It is like Ember said "Well we already loaded that data, so we dont need to call the api again" but the data is somehow not being displayed in our template.
If I change the ItemsRoute model to this:
return this.get('store').find('item');
The scenario above works perfectly fine but the data is not based on our location.
Is there something I am missing with using this.modelFor? Or is my route set up incorrect?
Let me know if theres any extra info you need.
Update 1:
Having changed the model function to this I have discovered some new insights:
model: function(){
//Get the model for the selected location and grab its item list.
//This will do a call to /locations/1/items
var location = this.modelFor('location');
var items = location.get('items');
return items;
}
Upon first load of #/locations/1/items the location variable holds the location model and the items variable holds something which has a 'content' property, 'isFulfilled: true' and some other things.
This correctly works and displays the list of items. When i click on a particular item and got to #/locations/1/items/1 then hit the back button. The breakpoint triggers and location is still correctly populating with the location model.
However the items variable seems to just be a PromiseArray, do I need to somehow wait for the promise to be fulfilled before this model hook returns? I thought Ember already did this automatically? I suppose this means that the route is loading before the promise is fulfilled and thats why there is not data displayed?
Therefore my list is not populated correctly.
I'm on a phone, so I'll update this a bit later with more, but the problem is your location resource isn't a child of locations. Becaude of that, ember says why waste time fetching that route if it isn't a part of that resource path. It only hits the location route, which I'm betting you don't have a model hook for fetching the single location (at least based on the code above).
Ok, here is the deal. I have fixed the issue but have no idea why it fixed the issue.
Without further ado:
Here is a jsbin example of the exact setup I have. The only difference between the real one and the jsbin is the fact that I am using the RestAdapter instead of the FixtureAdapter. This is not technically true because I am using the ember-django-rest-adapter which extends the REST one.
The issue described in the question does not present itself in the jsbin but the exact setup with the ember-django-rest-adapter does present the issue.
The fix was to break the cyclic relationship between User -> Site -> Items -> User
For example if I comment out the 'locations' relationship in the User model, the back button works.
Or if I comment out the 'owner' relationship to User in the Item model the back button works.
I might ask a separate question to see what the reasoning behind the problem is, although if someone can shed any light in to why this is happening I'll happily accept the answer here.

Categories

Resources