Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 sub apps, parent/child, nested components, nested router-outlet, design/coding

I have an Angular 2 app. The main screen (app?) looks like this...

Main app screen

When you click items in the top menu routerLinks, new Components load into the main view router outlet. One of those links loads up an new "Admin" Module/Component with it's own routes and new router outlet...

Admin app screen

Then when you click the routerLinks in the left nav, new admin Components will load in the new router outlet.

But...

Angular 2 does not allow more than 1 router outlet. So clicking on any routerLink in left nav simply replaces the entire inital router outlet view.

I've seen some SO posts (older, maybe deprecated) on using "bootstrap" to load subsequent Components, but I can't get that to work. I can't even import { bootstrap } from 'anywhere at all, nothing works'. So maybe that's not the way to do this.

How can I get the Admin sub app part to work?

Thank you very, very much for sharing your Angular 2 expertise :-)

EDIT: Trying suggested solutions below. No matter where I put the routes, in the base app.routes.ts or in the sub-app admin.routes.ts, no matter how I format the routerLinks, I keep getting this error...

Cannot find route

EDIT AGAIN: Here is the code in the routers and the template...

<!--
    ============================================================================
    /src/app/component/admin/admin.component.html
-->

<!-- Row for entire page columnar dispaly -->
<div class="row">

    <!-- Column 1: Left navigation, links to all admin components -->
    <div class="col col-md-4">
        <app-admin-nav></app-admin-nav>
    </div>

    <!-- Column 2: Rows of records, click to edit -->
    <div class="col col-md-8">
        <router-outlet name="admin-app"></router-outlet>
    </div>

</div>

// ============================================================================
// /src/app/app.routes.ts

import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { GameComponent } from './component/game/game.component';
import { HomeComponent } from './component/home/home.component';
import { LoginComponent } from './component/login/login.component';
import { PlayerComponent } from './component/player/player.component';
import { AuthGuard } from './service/auth/auth.service';
import { SignupComponent } from './component/signup/signup.component';
import { EmailComponent } from './component/email/email.component';
import { AdminComponent } from './component/admin/admin.component';
// import { AdminWorldComponent } from './component/admin/world/admin-world.component';
// import { AdminModuleComponent } from './component/admin/module/admin-module.component';
// import { AdminRegionComponent } from './component/admin/region/admin-region.component';

export const router: Routes = [
    { path: '', redirectTo: 'home', pathMatch: 'full' }
    , { path: 'home', component: HomeComponent }
    , { path: 'game', component: GameComponent, canActivate: [AuthGuard] }
    , { path: 'admin', component: AdminComponent, canActivate: [AuthGuard] }
    // , {
    //     path: 'admin', component: AdminComponent, canActivate: [AuthGuard],
    //     children: [
    //         { path: 'world', component: AdminWorldComponent, outlet: 'admin-app' },
    //         { path: 'module', component: AdminModuleComponent, outlet: 'admin-app' },
    //         { path: 'region', component: AdminRegionComponent, outlet: 'admin-app' }
    //     ]
    // },
    , { path: 'login', component: LoginComponent }
    , { path: 'signup', component: SignupComponent }
    , { path: 'login-email', component: EmailComponent }
    , { path: 'players', component: PlayerComponent, canActivate: [AuthGuard] }
];

export const routes: ModuleWithProviders = RouterModule.forRoot(router);

// ============================================================================
// /src/app/component/admin/admin.routes.ts

import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { AdminComponent } from './admin.component';
import { AdminWorldComponent } from './world/admin-world.component';
import { AdminModuleComponent } from './module/admin-module.component';
import { AdminRegionComponent } from './region/admin-region.component';

export const router: Routes = [
    {
        path: 'admin', component: AdminComponent,
        children: [
            { path: 'world', component: AdminWorldComponent, outlet: 'admin-app' }
            , { path: 'module', component: AdminModuleComponent, outlet: 'admin-app' }
            , { path: 'region', component: AdminRegionComponent, outlet: 'admin-app' }
        ]
    }
];

export const routes: ModuleWithProviders = RouterModule.forRoot(router);

EDIT 3: Tried changing RouterModule.forRoot to RouterModule.forChild, sadly, same error :-/

EDIT 4: Converted routing to use 2 routing modules. Was hoping maybe that would make a difference, but same error.

New routers...

// ============================================================================
// /src/app/app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';
import { GameComponent } from './component/game/game.component';
import { HomeComponent } from './component/home/home.component';
import { LoginComponent } from './component/login/login.component';
import { PlayerComponent } from './component/player/player.component';
import { AuthGuard } from './service/auth/auth.service';
import { SignupComponent } from './component/signup/signup.component';
import { EmailComponent } from './component/email/email.component';
import { AdminComponent } from './component/admin/admin.component';

export const appRoutes: Routes = [
    { path: '', redirectTo: 'home', pathMatch: 'full' }
    , { path: 'home', component: HomeComponent }
    , { path: 'game', component: GameComponent, canActivate: [AuthGuard] }
    , { path: 'admin', component: AdminComponent, canActivate: [AuthGuard] }
    // , {
    //     path: 'admin', component: AdminComponent, canActivate: [AuthGuard],
    //     children: [
    //         { path: 'world', component: AdminWorldComponent, outlet: 'admin-app' },
    //         { path: 'module', component: AdminModuleComponent, outlet: 'admin-app' },
    //         { path: 'region', component: AdminRegionComponent, outlet: 'admin-app' }
    //     ]
    // },
    , { path: 'login', component: LoginComponent }
    , { path: 'signup', component: SignupComponent }
    , { path: 'login-email', component: EmailComponent }
    , { path: 'players', component: PlayerComponent, canActivate: [AuthGuard] }
];

@NgModule({
    imports: [
        RouterModule.forRoot(appRoutes)
    ],
    exports: [
        RouterModule
    ]
})
export class AppRoutingModule { }

// ============================================================================
// /src/app/admin/admin-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdminComponent } from './admin.component';
import { AdminWorldComponent } from './world/admin-world.component';
import { AdminModuleComponent } from './module/admin-module.component';
import { AdminRegionComponent } from './region/admin-region.component';

export const adminRoutes: Routes = [
    {
        path: 'admin', component: AdminComponent,
        children: [
            { path: 'world', component: AdminWorldComponent, outlet: 'admin-app' }
            , { path: 'module', component: AdminModuleComponent, outlet: 'admin-app' }
            , { path: 'region', component: AdminRegionComponent, outlet: 'admin-app' }
        ]
    }
];

@NgModule({
    imports: [
        RouterModule.forChild(adminRoutes)
    ],
    exports: [
        RouterModule
    ]
})
export class AdminRoutingModule { }

EDIT 5: IT'S WORKING!

Removed the routing modules, returned to exporting routes config per Tyler's suggestion. He is right, the routing modules do not work. Tyler worked with me a lot so I'm accepting his answer. Thank you Tyler for your help!

Here is how you can setup a parent app with it's own router-outlet, then on the parent click a link to load up a child app with it's own new router-outlet. The child app loads/replaces the parent app router-outlet.

There is really nothing special in the parent app module or routes. They're just how I had them before this post.

The important points to note, at least in my case today, do not use a name="" attrib in the child router-outlet. This will cause "Error: Cannot match any routes...". Do not use routing modules like I tried above, this also causes "Error: Cannot match any routes...". Do not use outlet: 'blah' in the routes, this also causes "Error: Cannot match any routes...". Make sure you set up the child route config children: [] exactly as you see below in admin.routes.ts. Also, note the RouterModule.forChild(router) in the child routes. These things fixed the issue for me today.

PARENT APP

// ============================================================================
// src/app/app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AngularFireModule } from 'angularfire2';
import { firebaseConfig } from '../environments/firebase.config';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
// import { AppRoutingModule } from './app-routing.module';
import { routes } from './app.routes';

// Components
import { AppComponent } from './app.component';
import { HomeComponent } from './component/home/home.component';
import { GameComponent } from './component/game/game.component';
import { PlayerComponent } from './component/player/player.component';
import { LoginComponent } from './component/login/login.component';
import { SignupComponent } from './component/signup/signup.component';
import { EmailComponent } from './component/email/email.component';

// Admin Module
import { AdminModule } from './component/admin/admin.module';

// Services
import { AuthGuard } from './service/auth/auth.service';
import { AuthPlayerService } from './service/auth/auth-player.service';
import { MdbService } from './service/mongo/mdb.service';
import { PlayerMdbService } from './service/mongo/player-mdb.service';

@NgModule({
    declarations: [
        AppComponent
        , HomeComponent
        , GameComponent
        , PlayerComponent
        , LoginComponent
        , SignupComponent
        , EmailComponent
    ],
    imports: [
        BrowserModule
        , FormsModule
        , HttpModule
        , AdminModule
        , AngularFireModule.initializeApp(firebaseConfig)
        , NgbModule.forRoot()
        // , AppRoutingModule
        , routes
    ],
    providers: [
        AuthGuard
        , AuthPlayerService
        , MdbService
        , PlayerMdbService
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

// ============================================================================
// /src/app/app.routes.ts

import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { AppComponent } from './app.component';
import { GameComponent } from './component/game/game.component';
import { HomeComponent } from './component/home/home.component';
import { LoginComponent } from './component/login/login.component';
import { PlayerComponent } from './component/player/player.component';
import { AuthGuard } from './service/auth/auth.service';
import { SignupComponent } from './component/signup/signup.component';
import { EmailComponent } from './component/email/email.component';
import { AdminComponent } from './component/admin/admin.component';

export const router: Routes = [
    { path: '', redirectTo: 'home', pathMatch: 'full' },
    { path: 'home', component: HomeComponent },
    { path: 'game', component: GameComponent, canActivate: [AuthGuard] },
    { path: 'admin', component: AdminComponent, canActivate: [AuthGuard] },
    { path: 'login', component: LoginComponent },
    { path: 'signup', component: SignupComponent },
    { path: 'login-email', component: EmailComponent },
    { path: 'players', component: PlayerComponent, canActivate: [AuthGuard] }
];

export const routes: ModuleWithProviders = RouterModule.forRoot(router);

CHILD APP

// ============================================================================
// /src/app/admin/admin.module.ts

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { routes } from './admin.routes';
// import { AdminRoutingModule } from './admin-routing.module';
import { AdminComponent } from './admin.component';
import { AdminRecsComponent } from './admin-recs.component';
import { AdminFormComponent } from './admin-form.component';
import { AdminNavComponent } from './admin-nav.component';

import { AdminWorldComponent } from './world/admin-world.component';
import { AdminModuleComponent } from './module/admin-module.component';
import { AdminRegionComponent } from './region/admin-region.component';

@NgModule({
    imports: [
        CommonModule
        , FormsModule
        // , AdminRoutingModule
        , routes
    ]
    , declarations: [
        AdminComponent
        , AdminNavComponent
        , AdminRecsComponent
        , AdminFormComponent
        , AdminWorldComponent
        , AdminModuleComponent
        , AdminRegionComponent
    ]
    , schemas: [CUSTOM_ELEMENTS_SCHEMA]
    , exports: [
        AdminRecsComponent
        , AdminFormComponent
        , AdminNavComponent
        // , AdminWorldComponent
        // , AdminModuleComponent
        // , AdminRegionComponent
    ]
    // , bootstrap: [AdminComponent]
})
export class AdminModule { }


// ============================================================================
// /scr/app/admin/admin.routes.ts

import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { AdminComponent } from './admin.component';
import { AdminWorldComponent } from './world/admin-world.component';
import { AdminModuleComponent } from './module/admin-module.component';
import { AdminRegionComponent } from './region/admin-region.component';

export const router: Routes = [
    {
        path: 'admin', component: AdminComponent,
        children: [
            { path: 'world', component: AdminWorldComponent },
            { path: 'module', component: AdminModuleComponent },
            { path: 'region', component: AdminRegionComponent },
        ]
    }
];

export const routes: ModuleWithProviders = RouterModule.forChild(router);
like image 829
Locohost Avatar asked Apr 13 '17 12:04

Locohost


2 Answers

Yes, there is a way to do this. You need to name your router outlet like:

<router-outlet name="child1"></router-outlet>

<router-outlet name="child2"></router-outlet>

And inside your router you need to define what router-outlet should route use:

{
    path: 'home',  // you can keep it empty if you do not want /home
    component: 'appComponent',
    children: [
        {
            path: '',
            component: childOneComponent,
            outlet: 'child1'
        },
        {
            path: '',
            component: childTwoComponent,
            outlet: 'child2'
        }
    ]
}

Original post: Angular2 multiple router-outlet in the same template

like image 23
Mario Petrovic Avatar answered Sep 22 '22 17:09

Mario Petrovic


Not sure where you heard that Angular2 does not allow more than 1 router-outlet. I am using several in a large application.

Your main app.component will have a router-outlet to handle the root routes. If one of your routes lazy-loads the Admin Module, that admin module will have it's root component that contains the side menu bar and a router-outlet for all the children routes.

Example:

//app.routes

export const ROUTES: Routes = [
    // Main redirect
    { path: '', component: MainViewComponent },

    {
        path: 'admin',
        loadChildren: './admin/admin.module#AdminModule'
    }
]

Your MainViewComponent can contain the top navbar and a router-outlet.

Then the Admin router config may look like this:

export const routes: Routes = [
    {
        path: '',
        component: AdminComponent,
        children: [
            { path: '', component: Component1},
            { path: 'component2', component: Component2}

        ]
    }
];

Your root component in the Admin module may contain the side bar menu and a router-outlet to show the children components.

You can also do named router-outlets. An example of this is having two router-outlets side-by-side:

<router-outlet></router-outlet>
<router-outlet name="popup"></router-outlet>

Your router config would look like this:

{
  path: 'compose',
  component: ComposeMessageComponent,
  outlet: 'popup'
},

And you would use it like this:

<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>

Or clear the content with this:

this.router.navigate([{ outlets: { popup: null }}]);

See the docs or this article for more details.

Hope that helps.

Edit

When using the route config for a lazily loaded child, make sure your route configs are loaded properly in your modules. The root route config will be loaded in the root AppModule with RouterModule.forRoot(routes) and the child routes are in the Child module with RouterModule.forChild(routes).

Your route config and modules need to look like this(don't create a separate module just to hold routing config):

//Admin Routes

export const adminRoutes: Routes = [
    {
        path: 'admin', component: AdminComponent,
        children: [
            { path: 'world', component: AdminWorldComponent, outlet: 'admin-app' }
            , { path: 'module', component: AdminModuleComponent, outlet: 'admin-app' }
            , { path: 'region', component: AdminRegionComponent, outlet: 'admin-app' }
        ]
    }
];

//Admin Module:

import { adminRoutes } from './admin.routes';

@NgModule({
  imports: [
    ...
    RouterModule.forChild(adminRoutes),
  ]
   ...

//App Routes(lazy load Admin module)

export const appRoutes: Routes = [
   { path: 'admin', loadChildren: './admin/admin.module#AdminModule' },
   ....

//App Module

import { appRoutes } from './app.routes';

@NgModule({
  imports: [
    ...
    RouterModule.forRoot(appRoutes),
  ]
   ...

Hope that helps.

like image 155
Tyler Jennings Avatar answered Sep 20 '22 17:09

Tyler Jennings