64

I have a login page - just 2 input (no header, no footer, no sidebar)

When a user signs in he should be navigated to a page with header, footer and right navbar.

the only thing that changes on the inner page is the right side content.

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

@Component({
  selector: 'pm-app',
  template: `
  <div>
      <router-outlet></router-outlet>
   </div>
   `,
   styleUrls:["app/app.component.css"],
    encapsulation: ViewEncapsulation.None
})
export class AppComponent {
  pageTitle: string = 'Acme Product Management';
}

I understand this app.component is like master page where you can add the header and footer and the <router-outlet></router-outlet> is where the content changes based on the routing.

How do I make one layout for the login page and another layout with header, footer and right sidebar for the inside page?

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
baaroz
  • 19,119
  • 7
  • 37
  • 47

4 Answers4

78

You could use child routes to use different layouts for different views.

Here is a common example of using child routes in Angular2

I like to use child routes to separate secure pages and unsecure pages in my angular 2 applications.

In my app root I have two directories

/Public

&

 /Secure

Now in the root directory I also have

/app.routing.ts

from there I create a layouts folder,

/layouts

In that directory I create

/layouts/secure.component.ts
/layouts/secure.component.html

&

/layouts/public.component.ts
/layouts/public.component.html

from this point I can divert my routes to one of the two layouts depending if the page is meant to be secure or public. I do this by creating a route to each layout in my root routes.ts file.

/app.routes.ts

const APP_ROUTES: Routes = [
    { path: '', redirectTo: '/home', pathMatch: 'full', },
    { path: '', component: PublicComponent, data: { title: 'Public Views' }, children: PUBLIC_ROUTES },
    { path: '', component: SecureComponent, canActivate: [Guard], data: { title: 'Secure Views' }, children: SECURE_ROUTES }
];

Notice that I register my child routes for each layout. That is the exported value of each separate route file. One is in the public directory and one is in the secure directory.

/public/public.routes.ts

export const PUBLIC_ROUTES: Routes = [
    { path: '', redirectTo: 'home', pathMatch: 'full' },
    { path: 'p404', component: p404Component },
    { path: 'e500', component: e500Component },
    { path: 'login', component: LoginComponent },
    { path: 'register', component: RegisterComponent },
    { path: 'home', component: HomeComponent },
    { path: 'benefits', component: BenefitsComponent },
    { path: 'services', component: ServicesComponent },
    { path: 'education', component: EducationComponent },
    { path: 'products', component: ProductsComponent },
    { path: 'fcra', component: FcraComponent },
    { path: 'croa', component: CroaComponent },
    { path: 'building', component: BuildingComponent },
    { path: 'tips', component: TipsComponent },
    { path: 'maintenance', component: MaintenanceComponent }
];

All of these routes are now accessible as the child routes for my public layout. Which now leads us to protecting our secure views.

So in the secure directory I essentially do the same thing,

/secure/secure.routes.ts

export const SECURE_ROUTES: Routes = [
    { path: '', redirectTo: 'overview', pathMatch: 'full' },
    { path: 'items', component: ItemsComponent },
    { path: 'overview', component: OverviewComponent },
    { path: 'profile', component: ProfileComponent },
    { path: 'reports', component: ReportsComponent },
    { path: 'recommendations', component: RecommendationsComponent },
    { path: 'score-simulator', component: ScoreSimulatorComponent },
    { path: 'payment-method', component: PaymentMethodComponent },
    { path: 'lock-account', component: LockAccountComponent }
];

This allows me to use auth to protect those child routes now. If you remember in

/app.routes.ts we did this for the secure routes,

{ path: '', component: SecureComponent, canActivate: [Guard], data: { title: 'Secure Views' }, children: SECURE_ROUTES }

Notice the [Guard]. This allows us to protect all of the child routes for the secure layout. This is one reason I use child routes. I could give you a lot more but I feel this is the most reasonable explanation.

Just to take things a small step further and put this in perspective for you this is how I [Guard] the secure pages. Creating a service and implementing CanActivate

@Injectable()
export class Guard implements CanActivate {

    constructor(protected router: Router, protected auth: Auth ) {}

     canActivate() {
        if (localStorage.getItem('access_token')) {
            // logged in so return true
            return true;
        }
        // not logged in so redirect to login page
        this.router.navigate(['/home']);
        return false;
    }
}

This allows you to serve public layout with <router-outlet></router-outlet> then use a different header and footer in the layout. Then use <router-outlet></router-outlet> in the secure layout again and obviously a different header and footer. Let me know if I have left anything unclear and I will update the answer.

wuno
  • 9,547
  • 19
  • 96
  • 180
  • 1
    So I believe the trick in your code is to always redirect from the root path: '' to a child route. So what if I'd like to have a full layout under path: www.example.com but a completely different layout under path: www.example.com/login ? – papaiatis Feb 16 '17 at 19:23
  • I would be glad to help you. Can you be more specific? What problem are you facing? – wuno Feb 16 '17 at 19:24
  • Okay, so I'm making a Dashboard app. I want to have a full width layout with fancy tables and graphs, etc on the home page, so on http://localhost:3000. User can log in on this page: http://localhost:3000/login. The login page should be just a simple form, no full width layout at all. How would you do that? – papaiatis Feb 16 '17 at 19:28
  • Ok cool do you know how to create a component? You just need to create a component and then add the route like this, { path: 'login', component: LoginComponent } – wuno Feb 16 '17 at 19:39
  • See my question here: http://stackoverflow.com/questions/42283235/multiple-layouts-in-nested-routes-in-angular-2 – papaiatis Feb 16 '17 at 19:45
  • I answered it for you. if this question helped you please up vote it. And then we can continue this in the question I just answered for you. – wuno Feb 16 '17 at 20:21
  • Hi, this looks very promising but I can't get it work with RouterModule.forRoot / RouterModule.forChild. I'm using this seed project : https://github.com/mgechev/angular-seed – Stefan Koenen Feb 23 '17 at 21:24
  • Ill tell you what buddy. Go ahead and upvote me and I will download the seed for you and show you. – wuno Feb 24 '17 at 01:09
  • It appears you are already setup to do this. But lazy loading is being used. Which is awesome. SO what are you trying to accomplish and I will show you how. – wuno Feb 24 '17 at 01:20
  • I'm trying to define the routes per module, and include the routing-module in the seperate modules like the seed is doing. The way you showed us here will work I think but I dont think it's possible what i want :) (?) – Stefan Koenen Feb 24 '17 at 21:28
  • Actually there is a difference They way they are doing it is with lazy loading. My example is by using components. If you want to create a new modules like in the seed you just need to create a new folder and copy all the same files as the other modules in then name them as your new module would be named. Then make sure to import the MyNewMod.module.ts file into the app.module.ts file and add it to the imports array, Does that make sense? I downloaded the seed and created a new module and added it to the nav bar just like explained in this comment. – wuno Feb 24 '17 at 21:32
  • Hmm and layout switching is working?? Do you mind if I contact you on FB or can you put it on github? :) – Stefan Koenen Feb 24 '17 at 21:39
  • Sure. I am actually trying to solve the same problem. I am needing to implement lazy loading into my app as well and maintain the same chile template theme. Look at my profile on here and hit me up. – wuno Feb 24 '17 at 21:42
  • It works. But body and html tags are repeated twice @wuno – mohamad javad Taherian May 15 '17 at 08:24
  • 1
    @mohamadjavadTaherian I have had that problem before. That is probably happening because you are calling twice. But if that is not the case then I suggest walking through your app from index.html on. Make sure that your logic for rendering your views make sense and you are not repeating anything to cause the page to render twice. When I dealt with the problem I ended up making a new project and getting the main landing page to work. Then I moved in each component one at a time to track it down. Good luck! – wuno May 15 '17 at 09:48
  • @wuno: I'm currently facing the same problem that I need two layouts for a different purpose (one layout for all my auth stuff [register, login, reset password ...] and one layout for everything else). All my features shall be in lazy loaded modules. Could you share the solution you found? (I already upvoted ;-) ) – Alex Feb 26 '18 at 07:50
  • @Alex I am sorry I am not sure how to help you more than the answer in this question. I answered this when Angular 2 was new. Now it seems to be on Angular 20 million. I have been writing a lot of React lately. I will say that I did have to convert this project to lazy loading at one point. So you can indeed take this example and make the routes lazy load instead. – wuno Feb 26 '18 at 22:42
  • @wuno: Thanks for your answer. Lazily loaded routes are what I try to do. But even with my eager loaded test setup, the "public"/"privet" layouts are not applied to the corresponding modules/routes. I already wrote a SO post https://stackoverflow.com/questions/48989260/angular2-shared-layout-for-multiple-modules . Maybe you have some different insight. Thanks for your help – Alex Feb 26 '18 at 23:08
  • I have an issue with this solution. Clickng a routerlink to load another child component causes any components on the public layout html to reload, loosing the state. Is there a fix for this? – Noobie3001 Jun 09 '18 at 19:51
  • @Noobie3001 I have not used angular since NG2 came out. There is a very high probability that at the time I answered this question things could of operated a little different. If the site is reloading and you are losing state, I would say it is one of 2 things. 1. Something you are doing is different, or 2. Something has changed in the newest version of Angular. – wuno Jun 09 '18 at 21:50
  • @wuno Excellent post, I followed this step by step with a fresh angular app to make sure I had a good understanding of it. However, app.component is loaded when the app starts. How do I prevent this and go to my LoginComponent which is Public? In other words I do not want app.component.html to be my home page, I want the login component to be the home page and when user is logged in I want the dashboard component to be the home page. – Blake Rivell Aug 09 '18 at 15:56
  • @BlakeRivell I wrote this a good while back and have not wrote NG in over a year. What comes to mind would be redirecting the person to the login page if they are not logged in so it is always checking. that way the home page is correct, if they are not logged in they will be redirected to the login page. I am glad you liked the post I sometimes fear that it needs to be updated cause it has been so long but it appears not! I hope you are able to get it to work! – wuno Aug 10 '18 at 03:49
39

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 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);
Rameez Rami
  • 5,322
  • 2
  • 29
  • 36
  • 2
    Thanks! This (https://stackblitz.com/edit/angular-multi-layout-example) document help me to setup different layouts in angular2 app. – ruchit Dec 18 '17 at 11:04
  • 1
    How does this resolve an empty path to the correct layout? I'm very confused as to how it decides to use the SiteLayoutComponent instead of the AppLayoutComponent. – dlarkin77 Feb 14 '18 at 09:55
  • great! this was a life saver – Sunday Okpokor Oct 12 '18 at 13:01
  • Will it not be another simple solution to exclude the header and footer components from the main component's html and add it in in inside pages where needed. Yes it could be a tedious thing for large websites but for smaller ones it could be manageable as you will just have to add 2 lines in each page to render the components – Subbu Mar 31 '20 at 18:08
  • I am doing the exact same thing but my routing doesn't work. Here is de details, any idea what I am missing here ? https://stackoverflow.com/q/65365743/10587542 – Muj Dec 19 '20 at 12:33
  • @ruchit any idea stackoverflow.com/q/65365743/10587542 – Muj Dec 19 '20 at 15:01
2

You can have multiple router-outlet and redirect where you want. For example login page can be the landing page if user is not logged in and if use is logged in you redirect user to a master page where you have header and footer and all other pages can be child of that component. For example we have our app like this

<my-app></my-app>

in my-component you have a

<router-outlet></router-outlet> ¨ you have one login component and you can use that routing for that page like this

 { path: '', component: LoginComponent}

as told earlier this can be landing page and you don't want to show anything else then login details. Once user sign in you redirect user to master component and master component will have routing like this

{ path: 'masterpage', component: MasterComponent}

and master component will have router outlet as well

<router-outlet></router-outlet>

now when you want to show user other pages like about us. then you will redirect user to about us page like this

{ canActivate: [AuthGuard], component: MasterComponent , path: 'masterpage',
  children:[
           {   canActivate: [AuthGuard],
               component: AboutUsComponent ,
               path: 'aboutus'
           }]
}

and this way you will have details like header and footer every where use navigate. I hope this helps!

Jorawar Singh
  • 7,463
  • 4
  • 26
  • 39
0

Notice the router-outlet. That is where you content will be displayed. This is an example of my app.component.html. In this case the redirect is to the home.component and its displayed in the router outlet.

<div class="ui-grid ui-grid-responsive ui-grid-pad">
    <div class="ui-g borderDiv" [ngStyle]="appPageHeaderDivStyle">
        <div class="ui-g-12" *ngIf="!isLoggedIn">
            <div class="ui-g">
                <div class="ui-xl-2">
                    <img class="logo" src="./app/resources/KetoLightLogo.png">
                </div>
                <div class="ui-xl-7">
                </div>
                <div class="ui-xl-3 ui-g-nopad" style="width: 320px; margin-left: 80px; float: right; ">
                    <div>
                        <p-menubar class="pMenuBar" [model]="items"></p-menubar>
                    </div>
                </div>
            </div>
        </div>

        <div class="ui-g-12" *ngIf="isLoggedIn">
            <div class="ui-g">
                <div class="ui-xl-2">
                    <img class="logo" src="assets/KetoLightLogo.png">
                </div>
                <div class="ui-xl-4">
                    <label class="ui-widget loggedInLabel">Logged in as: jbaird@arsbirder.com</label>
                </div>
                <div class="ui-xl-6 ui-g-nopad" style="width: 525px;  margin-left: 270px; float: right; ">
                    <p-menubar [model]="items"></p-menubar>
                </div>
            </div>
        </div>
    </div>
</div>

<router-outlet></router-outlet>

<div class="ui-grid ui-grid-responsive ui-grid-pad">
    <div class="ui-g borderDiv" [ngStyle]="appPageHeaderDivStyle">
        <div class="ui-g-12">
            <div class="ui-g">
                <div class="ui-g-12 ui-md-12 ui-lg-12">
                    <div class="ui-g-row">
                        <div class="ui-g-12">
                            <div class="ui-g-12 ui-md-12 ui-lg-12">
                                <div class="ui-g-1 ui-md-4 ui-lg-5">
                                </div>
                                <div class="ui-g-10 ui-md-4 ui-lg-2">
                                    <p class="copyright">Copyright 2016 Xamlware, Inc.</p>
                                    <p class="copyright2">All rights reserved</p>
                                </div>
                                <div class="ui-g-1 ui-md-4 ui-lg-5">
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
John Baird
  • 2,606
  • 1
  • 14
  • 33