Josh
Josh

Reputation: 16567

Angular 2 nested routes throwing errors

I'm working on an Angular 2 app using @angular in ASP.NET Core. I have a landing page located at the base url with its own Layout file, Index page, and controller.

What I want to add is an entire section of my website at /toolkit. To start it off, I made a MyProfileComponent to rest at /toolkit using my internal Layout, Index, and Controller. I have also created a ProfileComponent to reside at /toolkit/profile/:uniquename

The problem is that when I run the app since adding my internal routes, I get an error.

/toolkit throws this error

Exception: Call to Node module failed with error: Error: Uncaught (in promise): Error: Cannot find primary outlet to load 'ProfileComponent'

Error: Cannot find primary outlet to load 'ProfileComponent'

at getOutlet (D:\Projects..\node_modules\@angular\router\bundles\router.umd.js:3018:23)

and /toolkit/profile/sdfsad throws this error

Exception: Call to Node module failed with error: Error: Uncaught (in promise): [object Object]

I'm new to angular 2 and everything I've seen wants you to use the now obsolute Directives property on components, but that has gone away in this version of angular 2 (@angular is version 2.0.0). I'm probably doing something wrong, but here are the pieces:

_InternalLayout (extra stuff stripped out)

<head>
    <base href="/toolkit" />
</head>
<body class="menubar-top menubar-dark theme-primary">
    @RenderBody()

    @RenderSection("scripts", required: false)
</body>

/Toolkit/Index.cshtml

@{
    Layout = "~/Views/Shared/_InternalLayout.cshtml";
}

<app asp-prerender-module="@Url.Content("ClientApp/dist/main-server")">Loading...</app>

<script src="@Url.Content("~/dist/vendor.js")" asp-append-version="true"></script>

@section scripts {
    <script src="@Url.Content("~/dist/main-client.js")" asp-append-version="true"></script>
}

app.component.html

<router-outlet></router-outlet>

app.component.ts

import { Component } from '@angular/core';

@Component({
    selector: 'app',
    template: require('./app.component.html'),
})
export class AppComponent {

}

app.module.ts

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { UniversalModule } from 'angular2-universal';

import { AppComponent } from './components/app/app.component'
import { LandingPageLayoutComponent } from './components/landing/landing-page-layout.component';
import { SignInComponent } from './components/account/sign-in.component';
import { RegisterComponent } from './components/account/register.component';
import { ProfileComponent } from './components/profile/profile.component';
import { MyProfileComponent } from './components/profile/my-profile.component';

@NgModule({
    bootstrap: [ AppComponent ],
    declarations: [
        AppComponent,
        LandingPageLayoutComponent,
        SignInComponent,
        RegisterComponent,
        ProfileComponent,
        MyProfileComponent
    ],
    imports: [
        UniversalModule, // Must be first import. This automatically imports BrowserModule, HttpModule, and JsonpModule too.
        RouterModule.forRoot([
            { path: 'signin', component: SignInComponent },
            { path: 'register', component: RegisterComponent },

            {
                path: 'toolkit',
                component: MyProfileComponent,
                children: [
                    { path: '' },
                    { path: 'profile/:uniquename', component: ProfileComponent },
                ]
            },

            { path: '', component: LandingPageLayoutComponent },
            { path: '**', redirectTo: 'error' }
        ])
    ]
})
export class AppModule {
}

The root page, sign in, and register all work, although I had the same issue when I tried making sign in and register children under /account. I'm sure I am doing something silly, and once we figure it out I will try some other nesting with those pages.

EDIT

I am using this Visual Studio 2015 template if it matters. It was working fine until I started trying to add nested routes.

Upvotes: 0

Views: 687

Answers (1)

Thomas Rundle
Thomas Rundle

Reputation: 96

Based on your error and what I see in your RouterModule code in in app.module.ts, there are two possible resolutions depending on what your goal is.

1) If at the route '/toolkit', you want to see MyProfileComponent and at the route '/toolkit/profile/:uniquename' (showing full paths here) you want to see ProfileComponent instead of MyProfileComponent then the code for your router needs to look like:

    RouterModule.forRoot([
        { path: 'signin', component: SignInComponent },
        { path: 'register', component: RegisterComponent },
        {
            path: 'toolkit',
            children: [ // change here
                { path: '', component: MyProfileComponent }, // both components are siblings within 'toolkit' path
                { path: 'profile/:uniquename', component: ProfileComponent }
            ]
        },
        { path: '', component: LandingPageLayoutComponent },
        { path: '**', component: ErrorComponent } // this also needs to be changed to match a component
    ])

2) If at the route 'toolkit/profile/:uniquename' you want to see MyProfileComponent and ProfileComponent nested inside it then your router code should look like

    RouterModule.forRoot([
        { path: 'signin', component: SignInComponent },
        { path: 'register', component: RegisterComponent },

        {
            path: 'toolkit',
            component: MyProfileComponent,
            children: [
                // this line needs to be removed { path: '' },
                { path: 'profile/:uniquename', component: ProfileComponent }
            ]
        },
        { path: '', component: LandingPageLayoutComponent },
        { path: '**', component: ErrorComponent } // this also needs to be changed to match a component
    ])

(2) cont'd- Then inside my-profile.component.ts your template must have a <router-outlet></router-outlet> to handle the nested child routes

In both cases your path: **' needs to have a component associated with it. When doing redirects, you must include the pathMatch property e.g. pathMatch: 'full'

Upvotes: 1

Related Questions