How to keep route parameters even page refresh - javascript

I use the following approach to pass parameters through different routes and I am trying to keep these parameter values on page refresh or opening another tab on the routed page. However, after retrieving these parameters, they lost their values. So, is it possible to keep their values without using :id etc suffix on the routes? In this scenario, I open the SecondComponent first and then open its Child called SecondChildComponent using tabs.
first.component.ts
details(name) {
this.router.navigate(['/second'], { state: { name: name} });
}
second.component.ts
constructor(private router: Router) {
this.name= this.router.getCurrentNavigation().extras.state.name;
}
routing.module
const routes: Routes = [
{
path: 'second',
component: SecondComponent,
children: [
{
path: '',
redirectTo: 'second-child',
},
{
path: 'second-child',
component: SecondChildComponent
}
]
}
];

I'm afraid that your code don't work as you expect, please read this link about pass data using state
The brief idea is that you can get the value of the state
In the component where you go
ngOnInit() {
this.activatedRoute.paramMap
.pipe(map(() => window.history.state)).subscribe(res=>{
console.log(res)
})
}
In the "main component" using
ngOnInit() {
this.router.events.pipe(
filter(e => e instanceof NavigationStart),
map(() => this.router.getCurrentNavigation().extras.state)
).subscribe(res=>{
console.log(res)
})
}
Well about your question, the only "object" that maintain when you change the route is a "service". The only "object" that maintain when you "refresh" is localStore -or use some king of back-up in the server.
Using a service, simply has
#Injectable({
providedIn: 'root',
})
export class DataService {
data:any;
constructor() { }
}
And use
.subscribe(res=>{
this.dataService.data=res.name || this.dataService.data
this.name=this.dataService.data
})
Another aproach is if you don't receive a parameter router to first component. This idea can use e.g. if you has a typical component with a table with a button edit that links to a "detail" component. If the "detail" don't receive the value route to the component with the table
.subscribe(res=>{
if (!res.name)
this.router.navigateTo(['/table'])
})

Related

Angular 14: Routing to child route not working properly

In my Angular 14 application I have tree on the left side which contains buildings and persons inside these buildings.
+ Building 1
- Person 1
- Person 2
- Person 3
+ Building 2
- Person 4
- Person 5
When I click an entry in the tree I want to display some details on the right side of the browser window. Therefore, I created a HTML template which contains the tree and a <router-outlet> for rendering the child components.
<div class="container">
<div class="tree-container">
...
</div>
</div>
<div class="content-container">
<router-outlet></router-outlet>
</div>
</div>
The routes are defined in this way:
const routes: Routes = [
{ path: '', component: MainComponent, canActivate: [AuthGuard],
children: [
{ path: 'building/:uuid', component: BuildingComponent},
{ path: 'person/:uuid', component: PersonComponent},
]
},
];
When I click an entry I call a method in the Maincomponent routing to the corressponding child compoment:
this.router.navigate(['building', buildingUuid], {relativeTo: this.route})
or
this.router.navigate(['person', personUuid], {relativeTo: this.route})
This works fine if I switch between building and person items. In this case the child component is shown in the right part of the browser window.
But when I click two nodes of the same type after each other (e.g. Person 1 and then Person 2) I see that the URL in the browser changes, but the child component is not updated.
Any ideas, what I'm doing wrong?
It's because you are already navigated to that component, so the component is already created and will not be created again.
What you should do is to subscribe to the params in the ngOnInit, so your logic will be executed on each param change:
import { ActivatedRoute} from '#angular/router';
...
constructor(private route: ActivatedRoute) {}
...
ngOnInit() {
this.route.params.subscribe({
next: (params) => {
const uuid = params.uuid;
// Your logic
},
error: (error) => {
console.log('ERROR: ', error);
},
});
}
Note: Don't forget to unsubscribe from Observable in ngOnDestroy.

Global mixins fired multiple times - Vue

I've created a mixin to change the page titles, using document.title and global mixins.
My mixin file (title.ts):
import { Vue, Component } from 'vue-property-decorator'
function getTitle(vm: any): string {
const title: string = vm.title
if (title) {
return `${title} | site.com`
}
return 'Admin panel | site.com'
}
#Component
export default class TitleMixin extends Vue {
public created(): void {
const title: string = getTitle(this)
if (title) {
document.title = title
}
}
}
Then i registered this mixin globally in main.ts:
import titleMixin from '#/mixins/title'
Vue.mixin(titleMixin)
Then setting up the title in a Vue component:
#Component
export default class Login extends Vue {
public title: string = 'New title'
}
I have like 5 components in my project, if i use console.log in a mixin, i can see that it fired in every component, step by step, thus document.title is set by a last component created() hook.
How to correctly set a title for a CURRENT page?
As you said, a global mixin will affect every component in your Vue app, which means that the logic to set the document.title will fire in the created hook of every component in your app.
I think what you're looking for is VueRouter's beforeRouteEnter hook, which is one of the navigation guards that the library makes available to any of your components. A component's beforeRouteEnter hook fires immediately before the route changes to whichever one it's associated with.
In your case it would look like this:
#Component
export default class TitleMixin extends Vue {
public beforeRouteEnter(to, from, next): void {
next(vm => {
const title: string = getTitle(vm)
if (title) {
document.title = title
}
})
}
}
You'll notice that the next function (which needs to be called for the route to resolve) is being passed a callback which has a reference to the component's instance (vm), which we're passing to getTitle instead of this. This is necessary because the beforeRouteEnter hook does not have a reference to this. You can read the docs I linked to for more info.
Instead of creating a global mixin, try using a route meta field along with a global resolve guard.
First, we'll start by adding a meta field to each RouteConfig in the /router/routes.ts file:
import { RouteConfig } from 'vue-router'
export default [
{
path: '/login',
name: 'Login',
component: () => import(/* webpackChunkName: 'login-view' */ '#views/Login.vue'),
meta: {
title: 'Login', // Set the view title
},
},
// ... Add the title meta field to each `RouteConfig`
] as RouteConfig[]
Then, we'll create a global resolve guard, to set the title meta field as the document title, in the /router/index.ts file:
import Vue from 'vue'
import Router, { Route } from 'vue-router'
import routes from './routes'
Vue.use(Router)
const router = new Router({
// ... RouterOptions
})
// Before each route resolves...
// Resolve guards will be called right before the navigation is confirmed,
// after all in-component guards and async route components are resolved.
router.beforeResolve((routeTo, routeFrom, next) => {
const documentTitle = getRouteTitle(routeTo)
// If the `Route` being navigated to has a meta property and a title meta field,
// change the document title
if (documentTitle ) {
document.title = documentTitle
}
// Call `next` to continue...
next()
function getRouteTitle(route: Route): string {
const title: string = route.meta && route.meta.title
if (title) {
return `${title} | site.com`
}
return 'Admin panel | site.com'
}
})
export default router
You should use the mixin only in the parent component for your page (the one that holds all the page itself).
Using your vue-property-decorator should be in this way:
import { Vue, Component, Mixins } from 'vue-property-decorator';
#Component
export default class Login extends Mixins(titleMixin) {
public title: string = 'New title'
}
And do not import it globally with Vue.mixin(titleMixin). In this way it is imported for all the components.

modular routing in vuejs

I am building a simple website in which I have a route to category pages. I want to use a single dynamic route to move between pages.I am using vue-router for this project and the routes need to load different component
These are the desired routes for the website
example: '/shop/men' , '/shop/women','/shop/kids'
This my index.js file for router in which gender is appended in the last deciding which component to load the issue I am facing is how to handle this and load different component on depending on it
router-> index.js:
{
name: 'shop',
path: '/shop/:gender',
component: menCategoryViewsHandler('mencategory')
}
views -> viewHandler -> mencategory.js:
'use strict'
import Handle from '../mencategory.vue'
const camelize = str => str.charAt(0).toUpperCase() + str.slice(1)
// This is a factory function for dynamically creating root-level views,
// since they share most of the logic except for the type of items to display.
// They are essentially higher order components wrapping the respective vue file.
export default function ViewsHandler (type) {
console.log('1',type)
return {
name: `${type}-mencategory-view`,
asyncData ({store, route}) {
//#todo : add the ssr and routerbefore load change script here
return Promise.resolve({})
},
title: camelize(type),
render (h) {
return h(Handle,
{
props: {type},
},
)
},
}
}
You need to use dynamic route matching along with a wrapper component which renders the correct Category component. This would handled by passing props to components.
// CategoryResolver.vue
import menCategory from './mencategory'
import womenCategory from './womencategory'
import kidsCategory from './kidscategory'
const components = {
menCategory,
womenCategory,
kidsCategory
}
export default {
functional: true,
props: ['category'],
render(h, ctx) {
return h(`components[${category}Category`], ctx.data, ctx.children)
}
}
Then your router would be defined as such:
const router = new VueRouter({
routes: [
{ path: '/shop/:category', component: CategoryResolver, props: true }
]
})
Say menCategoryViewsHandler('mencategory') returns a component called MenCat. It must have a prop that matches the route above, in this example category. In MenCat you would define:
export default {
props: ['category'],
...
}
Vue router will pass the matching url prop into your component for you.

Angular access methods of parent routes from child routes

So to explain clearly my problem, I have a component for each of my entities in my application like Author component and Book component. And for each of them I will have two child which is a list component and a form component.
So basically my route configuration look like this :
export const routing = RouterModule.forRoot([
{
path: 'author', component: AuthorComponent,
children: [
{ path: 'author-list', component: AuthorListComponent },
{ path: 'author-form', component: AuthorFormComponent }
]
},
{
path: 'book', component: BookComponent,
children: [
{ path: 'book-list', component: BookListComponent },
{ path: 'book-form', component: BookFormComponent }
]
}
]);
In my AuthorComponent for example I have a method to delete an author that call the service :
deleteBadge = (event): void => {
// Call delete service
this._badgeService.delete(event).subscribe(
result => {
// Good
},
error => {
// Error
}
My question is how can I call that method from my route child (author list or form component) knowing that I can't call it like a normal child component using event.
PS: I put method (and many other) in the parent because I need to access to it in both child components and so to avoid redundancy.
Standard practice is to use a shared service for Component Interaction. However, if you still want to avoid using a shared service, you can use the Injector API.
In your child component, AuthorListComponent for example, do the following:
import { Injector } from '#angular/core';
import {AuthorComponent} from "./author.component";
// ....
constructor(private injector:Injector){
let parentComponent = this.injector.get(AuthorComponent);
parentComponent.deleteBadge('String passed from AuthorListComponent');
}
Here is a link to working demo.
Use a communication Service which unites several communication observables.
An example can be found in the official Angular docs: https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service

(Vue.js) Same component with different routes

I would like to use the same component for different routes in a Vue.js application.
I currently have something like this:
main.js
const routes = [
{ path: '/route-1', name: 'route-1', component: MyComponent },
{ path: '/route-2', name: 'route-2', component: MyComponent },
{ path: '/route-3', name: 'route-3', component: MyComponent },
]
const router = new VueRouter({
routes
})
myComponent.vue
<ul>
<li><router-link to="/route-1">Route 1</router-link></li>
<li><router-link to="/route-2">Route 2</router-link></li>
<li><router-link to="/route-3">Route 3</router-link></li>
</ul>
When I type the route manually in the browser, everything is working well, but when I try to navigate between the routes using some of these router-generated-links, nothing happens. The route changes but the content is still the same. Any idea how I can solve this?
Thanks!
This is expected behaviour as Vue is trying to be optimal and reuse existing components. The behaviour you want to achieve used to be solved with a setting called canReuse, but that has been deprecated. The current recommended solution is to set a unique :key property on your <router-view> like so:
<router-view :key="$route.path"></router-view>
Check out this JSFiddle example.
You can use watch property, so your component will not waste time to reloading:
index.js
You might have something like this
const routes = [
{
path: '/users/:id',
component: Vue.component('user', require('./comp/user.vue').default)
}
]
user.vue
created(){
// will fire on component first init
this.init_component();
},
watch: {
// will fire on route changes
//'$route.params.id': function(val, oldVal){ // Same
'$route.path': function(val, oldVal){
console.log(this.$route.params.id);
this.init_component();
}
},
methods: {
init_component: function(){
// do anything you need
this.load_user_data_with_ajax();
},
}
Just to make a note. If anybody is working with SSR template, things are a bit different. #mzgajner's answer does indeed recreate the component but will not trigger the asyncData again.
For that to happen, modify entry-client.js like this.
OLD:
const activated = matched.filter((c, i) => {
return diffed || (diffed = (prevMatched[i] !== c))
})
NEW:
const activated = matched.filter((c, i) => {
/*
In my case I only needed this for 1 component
*/
diffed = ((prevMatched[i] !== c) || c.name == 'p-page-property-map')
return diffed
})

Categories

Resources