I am learning use of multiple router-outlet.
While using navigateBy function of router, i am not able to view my child route and getting error. But if i access it via routerLink in html i get the desired output.
So in below code, navigation to songs is working , but navigation to films is not.
const routes = [
{
path: 'bollywood',
component: BollywoodComponent,
children: [
{
path: 'films',
component: FilmsComponent,
},
{
path: 'songs',
component: SongsComponent,
},
],
},
{
path: 'hollywood',
component: HollywoodComponent,
},
{
path: 'tollywood',
component: TollywoodComponent
},
];
App Component html
<button class="f-margin-16" (click)="navigateToBollywoodSection()"> Bollywood </button>
<button class="f-margin-16" (click)="navigateToHollywoodSection()"> Hollywood </button>
<button class="f-margin-16" [routerLink]="['tollywood']" > Tollywood </button>
<br>
Router outlet starts now
<router-outlet> </router-outlet>
Bollywood Component html
<button (click)="navigateToFilms()"> Films </button>
<button [routerLink]="['songs']"> Songs </button>
<router-outlet> </router-outlet>
navigateToFilms() {
this.router.navigate(['/films']);
}
StackBitz Link
https://stackblitz.com/edit/angular-ivy-1nzkus?file=src/app/bollywood/bollywood.component.html
In router.navigate, you can pass relativeTo as the second param.
navigateToFilms() {
this.router.navigate(['films'], {relativeTo: this.activatedRoute} );
}
This way navigation will happen relative to the current route.
In the constructor you can add the ActivatedRoute dependency.
constructor(private activatedRoute: ActivatedRoute) {}
If you use <button [routerLink]="['songs']"> Songs </button>, the navigation by default happens relative to the current route.
Use it like this
this.router.navigate(['bollywood/films']);
If you want to use a navigation route then you can use this code.
because without a specific parent route you can't access child routing.
this.router.navigate(['/bollywood/films']);
Using the Nav on the website and going from
http://localhost:4200/mens
to
http://localhost:4200/mens/shirts
Works fine but when clicking on another category from with shirts like Hats the component/page doesn't reload and i'm unsure why as i'm not getting a error and it work fine when you click in the first category but within that category if you click on another it doesn't.
const routes: Routes = [
{
path : '',
component: WebsiteComponent,
children: [
{
path : '',
component: HomePageComponent,
},
{
path : 'mens',
component: MensPageComponent
},
{
path : 'mens/:category',
component: MensPageComponent
}
]
}
];
How are you getting your params in the men's component?
Here is a stackblitz that works https://stackblitz.com/edit/angular-buahdy
It uses route.params observable that emits when the route changes, if you are using a snapshot it will not change as it is not an observable.
category$ = this.route.params.pipe(map(params => params.category));
constructor(private route: ActivatedRoute) { }
and in the template show the category with the async pipe
Category {{ category$ | async }}
When you go from mens/shoes to mens/shirts the men's component does not reload, only the category param has changed. Subscribing to the param with route.params is how you trigger an update in your component.
When using the children property in the route. The component will need a router-outlet for those children routes to be rendered.
WebsiteComponent template:
<!-- some code -->
<!-- where children routes are rendered -->
<router-outlet></router-outlet>
Suppose we have the following routes:
{
path: 'a',
component: AComponent,
children: [
{
path: '',
component: BComponent
},
{
path: '',
component: CComponent,
children: [
{ path: '', component: DComponent }
]
}
]
}
And the following URL is pasted into the browser's address bar:
http://localhost:4200/a
Questions
How does the Router know what component to display?
Would all the four components (A, B, C, D) be displayed?
Where would each component be displayed?
Does every parent component always have its own RouterOutlet, so each component along a route of parent/child/grand-child/etc. gets displayed inside its parent's respective RouterOutlet?
Usually, when displaying a route with child routes, each component is displayed inside its parent RouterOutlet. But, if only AComponent had a RouterOutlet, where would BComponent, CComponent and DComponent be displayed?
Given that you aren't getting any sort of error the router will give you the first matching route it found. In this case BComponent. Only one component can be shown in a router outlet (to do otherwise you need something like auxRoute).
If CComponent had a route like 'c' you could access it from the route http://localhost:4200/a/c then you would get CComponent with DComponent in CComponents router outlet.
Hope that helps.
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
I have a few routes that each load 3 components. Two of the components are the same on all routes.
When I move between those routes I want to pass new data and on some init event of the component I want to fill the data of that component so it reflects on the UI.
Also I want to retrigger bootstrap animations of components being loaded.
How would I go about doing that.
Because right now, I don't know where in the component lifecycle would I fetch the data and rerender the component with this new data.
Concretly in myapps/1 and /newapp/ I have a main view component and a sidebar component. In the /newapp/ URL I want all icons on the sidebar to be red (nothing on the main view has been filled) and the main view should be empty.
On the myapps/1 I want to load the data from the server into the main view and if it's all filled I want icons on the sidebar to be green.
What now happens I load myapps/1, click on the second item in the sidebar and the main view changes to the second view. Then I router.push("/newapp/); and sidebar stays on the second item and second main view.
So router.push("/myapps/"); doesn't reload my sidebar and my main view.
EDIT:
Here you see my routes, sidebars and default are the same.
const routes = [
{
path: "/myapps/newproject",
components: {
default: ProjectMain,
"breadcrumbs": BreadcrumbsNewProject,
"sidebar": SidebarProject,
}
},
{
path: "/myapps/:id",
components: {
default: ProjectMain,
"breadcrumbs": BreadcrumbsCurrentProject,
"sidebar": SidebarProject,
}
},
{
path: "/myapps/newversion/:id",
components: {
default: ProjectMain,
"breadcrumbs": BreadcrumbsNewVersion,
"sidebar": SidebarProject,
}
}
];
This is my ProjectMain.vue
<template>
<div class="wrapper wrapper-content">
<component :is="currentProjectMain"></component>
</div>
</template>
This is my router-view in index.html:
<router-view name="sidebar"></router-view>
And I have another one in index.html, the default one:
<router-view></router-view>
When I click on some item on Sidebar i emit event to the ProjectMain to switch out the component. So like this:
In Sidebar:
eventBus.$emit("currentProjectMainChanged", "appOverview");
or
eventBus.$emit("currentProjectMainChanged", "appSettings");
And than in ProjectMain:
eventBus.$on("currentProjectMainChanged", function(data) {
if(data === "appOverview") {
self.currentProjectMain = CurrentProjectAppOverview;
}else if(data === "appSettings") {
self.currentProjectMain = CurrentProjectSettings;
}
});
If I got to "/myapps/:id". It loads the sidebar and ProjectMain and I get a little animation of the sidebar and the ProjectMain with this bootstrap classes:
<div class="animated fadeInUp"> and both components got through the entire lifecycle.
By default appOverview is selected in sidebar and CurentProjectAppOverview.vue is loaded as a component in ProjectMain.
Than I click on appSettings in the sidebar and class is added to that item in the sidebar to mark it as selected and in the ProjectMain CurrentProjectSettings.vue is loaded as a component.
But then in the breadcrumbs I have a button to go to "/myapps/newversion/:id"
Here is the problem. When I click to go to "/myapps/newversion/:id" (router.push("/myapps/newversion/" + id);) the second item on the sidebar remains selected (appSettings) and in ProjectMain CurrentProjectSettings.vue remains loaded, and I don't get the bootstrap animation of the sidebar and ProjectMain, and the components don't go through their lifecycle.
What I want here when I click the button to go to "/myapps/newversion/:id" is my bootstrap animation (<div class="animated fadeInUp">), I want the Sidebar and ProjectMain to go through their entire lifecycle. And I want the default item to be selected on the sidebar (so appOverview) and default component to be loaded in ProjectMain (so CurentProjectAppOverview.vue).
There are colored buttons for the each item in the sidebar. If I go to "/myapps/newversion/:id" from "/myapps/:id", I want to load the data from the server into CurrentProjectAppOverview.vue and if all data is filled I want the button on the sidebar to be green and if not it should be red.
So In short when moving between this two routes I want to be able to load data and fill the fields I want, and I want bootstrap animation and default views to be selected and loaded and they should go through their entire lifecycle. Now router just reuses them as they are.
So something like "ReloadComponent: true", or destroy and recreate component.
I could duplicate SidebarProject.vue and ProjectMain.vue and rename them and in each route load basically a copy but that would mean I have to write the same code in different .vue files.
Before there was an option to set YouComponent.route.canReuse to false, which would do what I want, I think, but it is removed in the newest version.
I was facing the same problem. I will quote a found answer, which is really simple and great.
The simplest solution is to add a :key attribute to :
<router-view :key="$route.fullPath"></router-view>
This is because Vue Router does not notice any change if the same component is being addressed. With the key, any change to the path will trigger a reload of the component with the new data.
Partially thanx to this: https://forum.vuejs.org/t/vue-router-reload-component/4429
I've come up with a solution that works for me:
First I added a new function to jQuery which is used to retrigger bootstrap animation when the component loads.
$.fn.redraw = function(){
return $(this).each(function(){
var redraw = this.offsetHeight;
});
};
In component:
export default{
created:function(){
this.onCreated();
},
watch: {
'$route' (to, from) {
//on route change re run: onCreated
this.onCreated();
//and trigger animations again
$("#breadcrumbsCurrentProject").find(".animated").removeClass("fadeIn").redraw().addClass("fadeIn");
}
}
}
I call the onCreated(); method when the component loads and when the route reloads or the ID of the same route changes but the component stays the same I just call the method again. This takes care of the new data.
As for re triggering bootstrap animations I use redraw(); function I add to jQuery right on the start of the app. and this re triggers the animations on URL change:
$("#breadcrumbsCurrentProject").find(".animated").removeClass("fadeIn").redraw().addClass("fadeIn");
It looks like you are using vue-router.
I don't have your code, but I can show you how I handled this. See named-views.
Here is my router-map from my bundle.js file:
const router = new VueRouter({
routes: [
{ path: '/',
components: {
default: dashboard,
altside: altSide,
toolbar: toolbar
},
},
{ path: '/create',
components: {
default: create,
altside: altSide,
toolbar: toolbar
},
},
{ path: '/event/:eventId',
name: 'eventDashboard',
components: {
default: eventDashboard,
altside: altSide,
toolbar: eventToolbar,
},
children: [
{ path: '/', name: 'event', component: event },
{ path: 'tickets', name: 'tickets', component: tickets},
{ path: 'edit', name: 'edit', component: edit },
{ path: 'attendees', name: 'attendees', component: attendees},
{ path: 'orders', name: 'orders', component: orders},
{ path: 'uploadImage', name: 'upload', component: upload},
{ path: 'orderDetail', name: 'orderDetail', component: orderDetail},
{ path: 'checkin', name: 'checkin', component: checkin},
],
}
]
})
I have named-views of "default", "altside" and "toolbar" I am assigning a component to the named view for each path.
The second half of this is in your parent component where you assign the name
<router-view name="toolbar"></router-View>
Here is my parent template:
<template>
<router-view class="view altside" name="altside"></router-view>
<router-view class="view toolbar" name="toolbar"></router-view>
<router-view class="view default"></router-view>
</template>
So, what I've done is I've told the parent template to look at the named-view of the router-view. And based upon the path, pull the component I've designated in the router-map.
For my '/' path toolbar == the toolbar component but in the '/create' path toolbar = eventToolbar component.
The default component is dynamic, which allows me to use child components without swapping out the toolbar or altside components.
This is what I came up with:
import ProjectMain from './templates/ProjectMain.vue';
import SidebarProject from './templates/SidebarProject.vue';
//now shallow copy the objects so the new object are treated as seperatete .vue files with: $.extend({}, OriginalObject);
//this is so I don't have to create basically the same .vue files but vue-router will reload those files because they are different
const ProjectMainA = $.extend({}, ProjectMain);
const ProjectMainB = $.extend({}, ProjectMain);
const ProjectMainC = $.extend({}, ProjectMain);
const SidebarProjectA = $.extend({}, SidebarProject);
const SidebarProjectB = $.extend({}, SidebarProject);
const SidebarProjectC = $.extend({}, SidebarProject);
const routes = [
{
path: "/myapps/newproject",
components: {
default: ProjectMainA,
"breadcrumbs": BreadcrumbsNewProject,
"sidebar": SidebarProjectA
}
},
{
path: "/myapps/:id",
components: {
default: ProjectMainB,
"breadcrumbs": BreadcrumbsCurrentProject,
"sidebar": SidebarProjectB
}
},
{
path: "/myapps/newversion/:id",
components: {
default: ProjectMainC,
"breadcrumbs": BreadcrumbsNewVersion,
"sidebar": SidebarProjectC
}
}
];
This basically tricks the vue-router into thinking he's loading different .vue components, but they are actually the same. So I get everything I want: reloading of the component, animation, life cycle, and I don't have to write multiple similar or the same components.
What do you think, I'm just not sure if this is the best practice.