92

I am new to angular 4. What I'm trying to achieve is to set different layout headers and footers for different pages in my app. I have three different cases:

  1. Login, register page (no header, no footer)

routes: ['login','register']

  1. Marketing site page (this is the root path and it has a header and footer, mostly these sections come before login)

routes : ['','about','contact']

  1. App logged in pages (I have a different header and footer in this section for all the app pages but this header and footer is different from the marketing site header and footer)

routes : ['dashboard','profile']

I run the app temporarily by adding a header and footer to my router component html.

Please advise me a better approach.

This is my code:

app\app.routing.ts

   const appRoutes: Routes = [
        { path: '', component: HomeComponent},
        { path: 'about', component: AboutComponent},
        { path: 'contact', component: ContactComponent},
        { path: 'login', component: LoginComponent },
        { path: 'register', component: RegisterComponent },
        { path: 'dashboard', component: DashboardComponent },
        { path: 'profile', component: ProfileComponent },


        // otherwise redirect to home
        { path: '**', redirectTo: '' }
    ];

    export const routing = RouterModule.forRoot(appRoutes);

app.component.html

<router-outlet></router-outlet>

app/home/home.component.html

<site-header></site-header>
<div class="container">
    <p>Here goes my home html</p>
</div>
<site-footer></site-footer>

app/about/about.component.html

<site-header></site-header>
<div class="container">
    <p>Here goes my about html</p>
</div>
<site-footer></site-footer>

app/login/login.component.html

<div class="login-container">
    <p>Here goes my login html</p>
</div>

app/dashboard/dashboard.component.html

<app-header></app-header>
<div class="container">
    <p>Here goes my dashboard html</p>
</div>
<app-footer></app-footer>

I saw this question on stack-overflow but i didn't get a clear picture from that answer

Vega
  • 27,856
  • 27
  • 95
  • 103
ninja dev
  • 1,687
  • 2
  • 12
  • 12
  • This link has couple of ways to do this : https://blog.angularindepth.com/angular-routing-reusing-common-layout-for-pages-from-different-modules-440a23f86b57 – Rakeshk Khanapure May 14 '19 at 19:58

4 Answers4

174

You can solve your problem using child routes.

See working demo at https://angular-multi-layout-example.stackblitz.io/ or edit at https://stackblitz.com/edit/angular-multi-layout-example

Set your route like below

const appRoutes: Routes = [
    
    // Site routes goes here 
    { 
        path: '', 
        component: SiteLayoutComponent,
        children: [
          { path: '', component: HomeComponent, pathMatch: 'full'},
          { path: 'about', component: AboutComponent }
        ]
    },
    
    // App routes goes here
    { 
        path: '',
        component: AppLayoutComponent, 
        children: [
          { path: 'dashboard', component: DashboardComponent },
          { path: 'profile', component: ProfileComponent }
        ]
    },

    // no layout routes
    { path: 'login', component: LoginComponent},
    { path: 'register', component: RegisterComponent },
    // otherwise redirect to home
    { path: '**', redirectTo: '' }
];

export const routing = RouterModule.forRoot(appRoutes);
vikrant
  • 2,169
  • 1
  • 21
  • 27
Rameez Rami
  • 5,322
  • 2
  • 29
  • 36
  • 6
    Demo helped really a lot, i had a little hard time to understand this, mainly i could not get what empty path: '' means, that if there was path: 'admin' generated links would match admin/profile, but since its empty it matches main url links like '/profile' and if child path is also path: '' it matches home page ''. Also was not much clear what exactli are those component: components, that i just have to leave in original one and post all content in newly generated components. But big tnx :) – Jiro Matchonson Dec 28 '17 at 16:30
  • how to make this work when modules are loaded lazily?? – ksh Jul 04 '19 at 13:40
  • @KarthikHande This will work fine even on lazy loading. (all of my works are on lazy loading). ill try and make a demo suing module loading but can't promise an ETA on this. ping here if you are still having a hard time implementing this on lazy loading modules – Rameez Rami Jul 05 '19 at 06:31
  • As being new to angular, @RameezRami I'm having confusion regarding, how will the browser to know which routes to go, when both of the above layout starts with path: ' ' blank route. Even though Its working perfectly, but can you explain a bit please – Ahsan Alii Oct 24 '19 at 07:56
  • 1
    @AhsanAlii In simple terms, forget about how the browser handles this. All the route paths are resolved by angular. `{path: '',}` doesn't mean the path ends there, it can have child routes. but if you use `{path: '',pathMatch: 'full',}` it means the scope of your route level ends there. Try to play with the stack-bits demo I have shared and do some experiments by moving `pathMatch` to diffrent levels of route definition. – Rameez Rami Oct 24 '19 at 11:22
  • Thanks for the answer and the comments, they were really helpful to me. However I got a follow-up question: In my layout I'd like to have a button that calls a different function "on click" depending on the page that has been loaded. Is such dynamic content possible with this approach? – werwuifi Feb 23 '23 at 21:24
10

you can use child e.g.

const appRoutes: Routes = [
    { path: '', component: MainComponent,
        children:{
            { path: 'home'  component:HomeComponent},
            { path: 'about', component: AboutComponent},
            { path: 'contact', component: ContactComponent},
               ..others that share the same footer and header...

        }
    },
    { path: 'login', component: LoginComponent },
    { path: 'register', component: RegisterComponent },
    { path: 'admin', component:AdminComponent, 
         children{
            { path: 'dashboard', component: DashboardComponent },
            { path: 'profile', component: ProfileComponent }
               ..others that share the same footer and header...
         }
    }
    { path: '**', redirectTo: '' }
];

MainComponent and AdminComponent like

<app-header-main></app-header-main>
<router-outlet></router-outlet>
<app-footer-main></app-footer-main>

the post talk about separate in diferent files the routes

Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • as per the answer the url will be 'admin/dashboard', 'admin/profile', i don't want this to happen.. i want to set urls as 'dashboard', 'profile'. is there any way for that? – ninja dev Oct 02 '17 at 17:44
  • I want to say that if you have two differents footer you can, make a footer using a @input to and *ngIf to show one or another view, or make two footers. Anyway it's only an example. You can put as "children" of path: '',component:MainComponent, your DashboardComponent, and profile and forget the path:admin – Eliseo Oct 02 '17 at 19:20
  • its helpfull if you are wotking with admin routes. Thanks for give me the idea. – Gurpreet Singh Nov 21 '18 at 05:44
  • 1
    how to override content for app-header-main and app-footer-main? – Ievgen Mar 07 '19 at 10:04
3

There are cases where the layout and shared elements don't really match the routing structure, or some elements have to be hidden/shown depending on a per-route basis. For such cases I can think of the following strategies (let's take an example of app-header-main component - but it will apply to any shared page element obviously):

Inputs & css classes

You can provide inputs or css classes to control the inner appearance of your shared elements, such as:

  1. <app-header-main [showUserTools]="false"></app-header-main>

or

  1. <app-header-main class="no-user-tools"></app-header-main> and then use :host(.no-user-tools) to show/hide what needs to be

or

  1. at a route level (child or not):

    {
      path: 'home',
      component: HomeComponent,
      data: {
        header: {showUserTools: true},
      },
    },
    

And access it through ActivatedRoute like so: this.route.data.header.showUserTools

TemplateRef input

Inside app-header-main component:

@Input() rightSide: TemplateRef<any>;

Input of type TemplateRef<any> where you could feed an ng-template element directly

<app-header-main [rightSide]="rightside"></app-header-main>
<ng-template #rightside>your content here</ng-template>

Named slot transclusion

You can author the app-header-main so that it uses named slot transclusion

Inside of app-header-main template:

<ng-content select="[rightSide]"><ng-content>

Usage:

<app-header-main class="no-user-tools">
  <div rightSide>your content here</div>
</app-header-main>
user776686
  • 7,933
  • 14
  • 71
  • 124
0

You can solve the problem using ng-content + ViewChild injection of layout into each page component that uses that specific layout.

Using the router for this common use case always seemed like a workaround to me. What you want is similar to Layouts in Asp.Net MVC or MasterPages in WebForm etc.

After struggling with this I ended up with something like this:

see working demo: https://stackblitz.com/edit/angular-yrul9f

shared.component-layout.ts

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

@Component({
  selector: 'shared-component-layout',
  template: `
  <div *ngIf="!hideLayoutHeader" style="font-size: 2rem;margin-bottom: 10px;">
    Layout title: {{layoutHeader}}
    <ng-content select=".layout-header">    
    </ng-content>
  </div>
  <ng-content select=".layout-body">
  </ng-content>
  `
})
export class SharedComponentLayout {
  layoutHeader: string;
  hideLayoutHeader: boolean;
}

page.component-base.ts

import { Component, ViewChild } from '@angular/core';
import { SharedComponentLayout } from './shared.component-layout';

export abstract class PageComponentBase {
    @ViewChild('layout') protected layout: SharedComponentLayout;
}

login.component.ts - without header

import { Component } from '@angular/core';
import { PageComponentBase } from './page.component-base';

@Component({
  selector: 'login-component',
  template: `
  <shared-component-layout #layout>
    <div class="layout-body">
      LOGIN BODY
    </div>
  </shared-component-layout>
  `
})
export class LoginComponent extends PageComponentBase {

  ngOnInit() {
    this.layout.hideLayoutHeader = true;    
  }
}

home.component.ts - with header

import { Component } from '@angular/core';
import { PageComponentBase } from './page.component-base';

@Component({
  selector: 'home-component',
  template: `
  <shared-component-layout #layout>
    <div class="layout-body">
      HOME BODY
    </div>
  </shared-component-layout>
  `
})
export class HomeComponent extends PageComponentBase {

  ngOnInit() {    
    this.layout.layoutHeader = 'Home component header';
  }
}
Cesar
  • 130
  • 2
  • 7