Handling Dynamic URL Params via RouterLink in Angular App - javascript

In my Angular 2 app I have a tab area where users can select from a group of independent, but contextually related components. When they click on one of these links, the relevant component loads according to what's defined in the routerLink, like this:
<a class="page-content-header-item" routerLink="/page1" routerLinkActive="selected">Page 1</a>
This was working well. However, since then we've built the app to save various user-selected filter selections as params in the URL. This way when they re-load the component, they'll have their most recent selections still visible and applied to the data. So the URL might look like this after the user had made some filter selections:
http://somesite.com/page1;language_filter=true;language_selection=English
The component code for this looks like this:
public changePage(page, value, type, body)
{
this.onUserSelection(value, type, body, page);
this.route.params.subscribe(
(params: any) => {
this.page = params['page'];
this.language_filter = params['language_filter'];
this.language_selection = params['language_selection'];
}
);
this.router.navigate(
['/page1', {
page: page,
language_filter: this.language_filter,
language_selection: this.language_selection,
}]);
}
This works well for the main navigation methods, that are accomplished via a routing file, where each one looks like this:
{ path: 'page1', component: Page1Component, canActivate: [AuthGuard], data: {contentId: 'page1'} }
However, for this tab area I mentioned, it's loading components according to a hard-coded routerLink param. So I realize now that when a user navigates BACK to a component that way, as opposed to via one of the other ways we make available, it actually overrides the URL params - because it's literally loading "page1" -- because of this <a class="page-content-header-item" routerLink="/page1" routerLinkActive="selected">Page 1</a>
... and thus the URL params that had been added previously are wiped out.
So, my question is, is there a way I can edit this code:
<a class="page-content-header-item" routerLink="/page1" routerLinkActive="selected">Page 1</a>
... so it allows for some dynamic variables? Or do I have to find a new way to handle the navigation in this tab area?

Here is the solution I came to using queryParams.
First, you can pass parameters in your routerLink directive using the queryParams directive:
<a routerLink="/page1" [queryParams]="fooParams">Page 1</a>
<a routerLink="/page2">Page 2</a>
<router-outlet></router-outlet>
where fooParams is a plain object:
export class MainComponent {
fooParams = {
language_filter: true,
language_selection: 'english'
}
}
Angular will output this url like
href="localhost:4200/page1?language_filter=true&language_selection=english"
What you have to do next, is set up a way to intercept the ActivatedRoute, so you can extract the values of the params from the ActivatedRouteSnapshot. You can do this either using a resolver or directly in your component. In my example I used a resolver:
#Injectable()
export class RoutingResolve implements Resolve<any> {
resolve(routeSnapshot: ActivatedRouteSnapshot) {
const { language_filter, language_selection } = routeSnapshot.queryParams;
return { language_filter, language_selection };
}
}
And then pass that resolver in the route definition:
const ROUTES: Routes = [
{
path: 'page1',
component: PageOneComponent,
// In this route we define a resolver so we can intercept the
// activatedRoute snapshot before the component is loaded
resolve: {
routing: RoutingResolve
}
},
{
path: 'page2',
component: PageTwoComponent
}
];
Then, inside PageOneComponent you can subscribe to the resolved data and do whatever you want with it, like for example, setting the value of a form with it and updating the queryParams on form change.
For the full example, check out this plunker
Make sure to open the preview in a separate window, so you can see the route changes in your browser.
If you navigate to Page 1, change the values in the form, then navigate to Page 2, and then press back in your browser, you can see the values loaded in the form correspond to the params in the url.

Related

Ionic 6 and Angular navigates to route which is already in history

I am using Ionic 6 and Angular 12 and I have couple pages in the app flow. I wen to page_4 and when I checked my DOM structure it looks like:
<ion-router-outlet>
<page_1></page_1>
<page_2></page_2>
<page_3></page_3>
<page_4></page_4>
</ion-router-outlet>
After page_4 I want to navigate to new page_2 and send some data via state object:
this.router.navigateByUrl('/page_2', { state: {dataHasChanged: true} });
I expected:
Ionic will create again new <page_2></page_2> tag into DOM bellow <page_4></page_4>
I can retrieve state data like I usually do:
this.router.getCurrentNavigation().extras?.state?.dataHasChanged
But result is different:
App back to the previous <page_2></page_2> and my DOM structure looks like:
<ion-router-outlet>
<page_1></page_1>
<page_2></page_2>
</ion-router-outlet>
this.router.getCurrentNavigation().extras is null and cannot see my data here. But I can see state data into windows.history.state object
Can someone please explain me why this happened or at least refer me to some valuable documentation ?
If you enter a page that has already been loaded, it will not load again, or at least, not always.
You can celar or update the content of the page inside ionViewWillEnter.
check this
add it in your page.ts
ionViewWillEnter() {
...
}
To solve the problem of navigation extras empty you can share data with a Service like this. It's not the cleanest way but it works
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class DataShareService {
private __dataDict = {};
setData(key: string, data: any) {
this.__dataDict[key] = data;
}
getData(key: string) {
return this.__dataDict[key];
}
}

Create a dynamic route param in Vue Router that removes spaces

I have a bunch of boxes in my app that route to a page where you can see all of the items that are in the box. As it stands my dynamic route in the index.js file looks like this:
{
path: '/box/:idBox',
name: 'ItemListByBox',
component: ItemListByBox,
},
All of my boxIDs (idBox) are all strings such as Box 1, Box 2, etc.
Everything works totally fine and routes you to the right page, but the page address at the top winds up looking like http://localhost:8080/box/Box%202
Is there a way to have the address look like Box-2?
Cheers!
You can use a computed to transform the user input (mentioned in comments) so that spaces are replaced with dashes. You don't need to change your route configuration.
Imagine you have a data variable that gets modified by user input called boxID and may contain spaces. Pass the computed as the route param like this:
<router-link :to="{ name: 'ItemListByBox', params: { idBox: boxFormatted }}">
CREATE BOX
</router-link>
export default {
data: () => ({
boxID: 'Box 1'
}),
computed: {
boxFormatted() {
return this.boxID.replace(/\s+/g, '-')
}
}
}

Angular: Reference query params in redirectTo

I would like to provide a path that redirects to a given page based on query parameters. For example:
/redirect?page=hero&id=1
should redirect to:
/hero/1
Is there any way to do this in the route config? Something like:
{ path: 'redirect?page&id', redirectTo: ':page/:id' }
I can get the redirectTo to respect path parameters but not query parameters. Any ideas?
You can try to use redirectTo: '/:page/:id' and provide extracted from your URL page and id values using custom UrlMatcher():
...
const appRoutes: Routes = [
{
path: 'hero/:id',
component: TestComponent
},
{
matcher: redirectMatcher,
redirectTo: '/:page/:id'
}
];
...
/**
* custom url matcher for router config
*/
export function redirectMatcher(url: UrlSegment[]) {
if (url[0] && url[0].path.includes('redirect')) {
const path = url[0].path;
// sanity check
if (path.includes('page') && path.includes('id')) {
return {
consumed: url,
posParams: {
page: new UrlSegment(path.match(/page=([^&]*)/)[1], {}),
id: new UrlSegment(path.match(/id=([^&]*)/)[1], {})
}
}
}
}
return null;
}
STACKBLITZ: https://stackblitz.com/edit/angular-t3tsak?file=app%2Ftest.component.ts
There is another issue when using redirectTo: ..., active link is not updated, actually isActive flag is not set to true, it is seen on my stackblitz when acrive redirection links are not colored in red
No, there is no way of doing it by a configuration. YOu see, Angular's router does not explicitly define query parameters - any url can have an arbitrary number of query params, and the paths '/page/id' and '/page/id?key=value' are treated as the same in Angular and do map to the same component. There are other, more cumbersome workarounds. One is to create a dummy component and redirect based on ActivatedRoute.queryParams Observable from the component's ngOnInit method. You can easily see why this is a bad idea.
Another way is to create a resolver, this way you maybe can dismiss the component declaration and just redirect from the resolver, again, based on the ActivatedRoute.queryParams Observable, which seems cleaner.
But I do not really get why one would need such a route in a front end application, if you want someone to visit '/page/id', then just navigate them to the page, without any intermediary tricks.

How to get query params in root component?

In angular 2, is it possible to get the query params in the root component in an "angular way"?
I have a simple plnkr with a root component and two routes/states.
I try to get the parameters with :
export class App implements OnInit {
constructor(private route: ActivatedRoute) {
}
ngOnInit(){
console.log(this.route.snapshot.queryParams);
}
}
If I open the example with a query parameter : plnkr the object in the console log is empty.
But when I click the "One" or "Two" links, it can get the query parameters.
I suppose the root can't see the query parameters because it's not an activated route.
Would the solution be to create a fake root route? Or is there another way to read the url (not talking about native JS).
My use case : Regardless of the route I'm on, I need to be able to check if there is a "token" in the url, read from it and then remove it.
Have a look at my change:
https://plnkr.co/edit/b45VUvfVqUl5u2EuzNI9?p=preview
{
path: '**',
component: App
}
I've added a default route - which should be added to the router. This basically says for any other route passed in then go here.
<a routerLink="**" [queryParams]="{token:12345678}">Home</a>
In the links i added a home link, similar to your other links. When you navigate to one or two, then back to home you will see the token passed in the object.
This is because the token was specified in the link.
This only really works if the link will have the token, either hard coded or dynamically generated by another component.
This might help:
https://angular.io/docs/ts/latest/guide/router.html#!#query-parameters

Ember Modal on a route accessible from anywhere

I'm building an app that has a user settings panel that pops up in a modal dialog. The panel should be accessible from any page in the app. I'm trying to figure out the best way to build this in Ember.
I would like to build it in such a way that when the app redirects to the "/settings" route, the modal dialog appears with the current route in the background as you would expect. Then when the modal is closed the app redirects back to that route.
If the user goes directly to "/settings" from her browser then the modal will appear with a default page in the background.
Here is what I have so far:
export default Ember.Route.extend({
defaultParentRoute: "project.index",
beforeModel: function(transition){
// get the name of the current route or if
// user came directly to this url, use default route name
var parent = this.get("defaultParentRoute");
var application = this.controllerFor('application');
if(application && application.get('currentRouteName')) {
parent = application.get('currentRouteName');
}
this.set("parentRoute", parent);
},
renderTemplate: function(){
// make sure the current route is still rendered in the main outlet
this.render(this.get("parentRoute"));
// render this route into the 'modal' outlet
this.render({
into: 'application',
outlet: 'modal'
});
},
actions: {
removeModal: function(page){
// go back to the previous route
this.transitionTo(this.get("parentRoute"));
}
}
});
This works pretty well when navigating to the the route from a link in the app. However if a user goes straight to "myapp/settings" in her browser then the default page template gets rendered but without any data or it tries to use the 'model' data from my settings route.
How do I make sure the template underneath the modal gets rendered with the appropriate data?
Here's a JS Bin to demonstrate. Try clicking on 'settings' from any page in the app, then refresh the browser while the settings modal is open.
This organization seems a bit unnatural given Ember conventions. Generally the URL is supposed to represent a serialized version of state sufficient to reconstruct where the user was (see this old discussion).
It seems you want to put the modal state and the current route into the URL. It might be more natural for the settings modal panel to be accessible from other routes but to not change the URL, and then have another separate route which is dedicated to settings and shows only the settings.
Modal panels seem more like a drop-down menu, the opening and closing of which do not change the URL, even though they represent a minor state change.
If the reason you want to have the settings modal reflected in the URL is so that people can bookmark it or share the link, one option would be to have a permalink available on the settings page that gets them to the other dedicated route that is shareable.
The tricky bit with not having the settings as a route is that there is not an obvious place to load the model which behaves as nicely or works as easily as a route's model hook (i.e., which waits until the promise is resolved to complete the transition).
One way around this is to put functionality to load the settings model in a settings service, which can be injected anywhere:
SomeController = Ember.Controller.extend({
settings: Ember.inject.service()
});
And have the settings service only show itself once the model has loaded.
SettingsService = Ember.Service.extend({
settingsLoaded: false,
ensureLoaded: function() {
var _this = this;
return new Ember.RSVP.Promise (resolve) {
if (_this.get('settingsLoaded')) {
resolve()
} else {
_this.store.find('settings', someId).then(function(model) {
_this.set('model', model);
_this.set('settingsLoaded', true);
resolve();
});
}
};
}
});
Finally you can have a function on some controller that wants to show the settings modal only show it once the settings are loaded:
showSettings: function() {
this.get('settings').ensureLoaded().then(function() {
... # something to show the modal pane.
});
}

Categories