In case of complex projects it is often expected to provide different layouts adapted to the needs of users – the logged-in user requires different UI elements than the guest. We have to customise the appearance of applications time and again taking into account many various aspects. Angular allows you to organise the presentation layer to easily handle multiple layouts.
Our application will provide two separate layouts – one served on a landing page view, the other used on a dashboard view. In this post we will display the default contents, just to present how to serve different components in isolated layouts.
To see the version with a horizontal navigation and a footer for the landing page and a vertical navigation for a dashboard, check out the Enhance the presentation layer of your multi-layout Angular app post, where all views are complemented with the appropriate html code and Bootstrap classes. The finished project is available in the following GitHub repository: little-pinecone/angular-multi-layout-scaffolding.
I try to keep the project up to date. Visit the releases list to keep track of the current versions of frameworks and libraries used. |
$ ng --version Angular CLI: 6.0.8 Node: 8.11.3 OS: linux x64 Angular: 6.1.1
scss
preprocessor and Bootstrap styles. To learn how to create such a project, visit the How to create a new Angular project post. If you want to change the preprocessor, read the How to change a CSS preprocessor in an Angular project post.The layout
folder will contain all components used to assemble the guest and authorised layouts and a component that is shared by both of them – PageContent
. The actual content of two views – the landing page and the dashboard – is going to be stored in the pages
folder. We are also going to create a routing module
, so the layout served depends on the url.
You won’t find any authorisation logic in this example.
The final project src/
directory tree:
Angular CLI will create all parent folders for generated components – just remember to give the full path for a component.
To avoid code repetition and facilitate handling pages, we will create a component accessible for both layouts – the PageContent
component:
$ ng generate component layout/page-content
Replace the default content of the page-content.component.html
with the following code:
<!-- src/app/layout/page-content/page-content.component.html --> <router-outlet></router-outlet>
The work done in this section is contained in the commit 9d92b44a7c2b620e0d5391b7cd89ecf47cb88b58.
Create the component for the top navigation:
$ ng generate component layout/guest/guest-top-nav
Create the component for the footer:
$ ng generate component layout/guest/guest-footer
Bind the navigation, page content and footer within the guest layout:
$ ng generate component layout/guest/guest-layout
Replace the content of the guest-layout.component.html
with the following code:
<!-- src/app/layout/guest/guest-layout/guest-layout.component.html --> <div> <app-guest-top-nav></app-guest-top-nav> <app-page-content></app-page-content> <app-guest-footer></app-guest-footer> </div>
Generate the component for our landing page:
$ ng generate component pages/landing-page
The following steps are well covered in the official Angular tutorial.
Let’s create a routing module in the main directory of the project:
$ ng generate module app-routing --flat --module=app
Angular CLI will create required files in the app/
folder and register the new module in the imports array of the AppModule
.
In the app-routing.module.spec.ts
file we can delete @NgModule.declarations array and CommonModule references:
// src/app/app-routing.module.spec.ts … import { CommonModule } from '@angular/common'; //delete this line … @NgModule({ imports: [ CommonModule //delete this line ], declarations: [] //delete this line }) …
From the @angular/router
library import RouterModule and Routes:
// src/app/app-routing.module.spec.ts … import { RouterModule, Routes} from '@angular/router'; …
Add @NgModule.exports array with RouterModule
in it:
// src/app/app-routing.module.spec.ts … @NgModule({ imports: [], exports: [ RouterModule ] }) …
We will need two separate sections for routes, but for now we can handle only the guest layout. Import GuestLayoutComponent
and LandingPageComponent
and add the following route to the routes
array:
// src/app/app-routing.module.spec.ts import { NgModule } from '@angular/core'; import { RouterModule, Routes} from '@angular/router'; import { GuestLayoutComponent } from './layout/guest/guest-layout/guest-layout.component'; import { LandingPageComponent } from './pages/landing-page/landing-page.component'; const routes: Routes = [ { path: '', component: GuestLayoutComponent, children: [ { path: '', component: LandingPageComponent, pathMatch: 'full'}, ] }, ]; …
Add RouterModule.forRoot(routes)
to the @NgModule.imports array:
// src/app/app-routing.module.spec.ts … @NgModule({ imports: [ RouterModule.forRoot(routes) ], exports: [ RouterModule ] }) export class AppRoutingModule { }
We want to change the content displayed on http://localhost:4200/
– from the default Angular page to our landing page. Replace the content of app.component.html
with the following code:
<!-- src/app/app.component.html --> <router-outlet></router-outlet>
Check out the landing page of your app on http://localhost:4200/
:
The work done in this section is contained in the commit a5cb113c477777dd266457341313e070999d930e.
Make sure that the CLI is tracking changes, refresh the browser window with Ctrl+F5
or restart the Angular server if you can’t see the effect.
If you run the following command:
$ ng test
you will see that the default test set from app.component.spec.ts
fails. Let’s investigate the errors:
<router-outlet>
is not recognised
AppComponent should create the app Failed: Template parse errors: 'router-outlet' is not a known element
Importing RouterTestingModule to the page-content.component.spec.ts
and app.component.spec.ts
files provides the Router mock:
// src/app/layout/page-content/page-content.component.spec.ts import { RouterTestingModule } from '@angular/router/testing'; // add this line … beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ PageContentComponent ], imports: [ RouterTestingModule ], // add this line }) .compileComponents(); }));
GuestLayoutComponent should create Failed: Template parse errors: 'app-guest-top-nav' is not a known element 'app-page-content' is not a known element 'app-guest-footer' is not a known element
The guest-layout template have nested components – they should have their own tests so we can exclude them from testing in guest-layout
. Import Component, create mocks and add the mocked components to the declarations
section:
// src/app/layout/guest/guest-layout/guest-layout.component.spec.ts … import { Component } from '@angular/core'; @Component({selector: 'app-guest-top-nav', template: ''}) class GuestTopNavComponent {} @Component({selector: 'app-page-content', template: ''}) class PageContentComponent { } @Component({selector: 'app-guest-footer', template: ''}) class GuestFooterComponent {} … beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ GuestLayoutComponent, GuestTopNavComponent, PageContentComponent, GuestFooterComponent ] }) .compileComponents(); })); …
You can apply NO_ERRORS_SCHEMA instead, but it will hide other errors – missing components and attributes that you overlooked or misspelled.
AppComponent should render title in a h1 tag Failed: Cannot read property 'textContent' of null
We altered the html that is tested- adjusting the content expected by tests fixes the problem. Furthermore, we can remove all tests from the app.component.spec.ts
file – except the one testing the creation of the app:
// src/app/app.component.spec.ts … it('should create the app', async(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); })); });
AppComponent
, GuestLayoutComponent
and PageContentComponent
don’t have any logic or html elements to test – we only need to verify whether they were created. You can read more about testing components in the official guide.
The work done in this section is contained in the commit c4e7e2d570c6fed56a5db249325710f5b620074d.
If the errors remain, try terminating the testing process and run the $ ng test command again.
Create the component for the side navigation:
$ ng generate component layout/authorised/authorised-side-nav
Bind the navigation and page content within the layout for and authorised user:
$ ng generate component layout/authorised/authorised-layout
Replace the content of the authorised-layout.component.html
with the following code:
<!-- src/app/layout/authorised/authorised-layout/authorised-layout.component.html --> <div> <app-authorised-side-nav></app-authorised-side-nav> <app-page-content></app-page-content> </div>
Generate a component for the dashboard page:
$ ng generate component pages/dashboard
Import AuthorisedLayoutComponent
and DashboardComponent
and add the following route to the routes array:
// src/app/app-routing.module.ts … import { AuthorisedLayoutComponent } from './layout/authorised/authorised-layout/authorised-layout.component'; import { DashboardComponent } from './pages/dashboard/dashboard.component'; const routes: Routes = [ … { path: '', component: AuthorisedLayoutComponent, children: [ { path: 'dashboard', component: DashboardComponent }, ] }, ]; …
Check out the authorised layout on http://localhost:4200/dashboard
:
The work done in this section is contained in the commit 7fd037e2ea0ed74f176dd7a1edee6b3740560b80.
If you run the following command:
$ ng test
you will see that the tests from authorised-layout.component.spec.ts
fail. Again, we encounter the error caused by the nested components:
AuthorisedLayoutComponent should create Failed: Template parse errors: 'app-authorised-side-nav' is not a known element 'app-page-content' is not a known element
The authorised-layout template have nested components – they should have their own tests so we can exclude them from testing in authorised-layout
. Import Component, create mocks and add the mocked components to the declarations
section:
// src/app/layout/authorised/authorised-layout/authorised-layout.component.spec.ts … import { Component } from '@angular/core'; @Component({selector: 'app-authorised-side-nav', template: ''}) class AuthorisedSideNavComponent {} @Component({selector: 'app-page-content', template: ''}) class PageContentComponent { } … beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ AuthorisedLayoutComponent, AuthorisedSideNavComponent, PageContentComponent ] }) .compileComponents(); })); …
The work done in this section is contained in the commit 24cba57ee1b58b8c1abb6110212c6f78d71fd021.
If the errors remain, try terminating the process and run the $ ng test command again.
Add html content and styles to the landing page, dashboard and both layouts components. Check out the Enhance the presentation layer of your multi-layout Angular app post or visit the project repository to see how to style the pages:
Photo by Toa Heftiba on StockSnap
Spring Security allows us to use role-based control to restrict access to API resources. However,…
A custom annotation in Spring Boot tests is an easy and flexible way to provide…
Delegating user management to Keycloak allows us to better focus on meeting the business needs…
Swagger offers various methods to authorize requests to our Keycloak secured API. I'll show you…
Configuring our Spring Boot API to use Keycloak as an authentication and authorization server can…
Keycloak provides simple integration with Spring applications. As a result, we can easily configure our…
View Comments
Hi Marta,
you saved my time :)
thanks a lot
thanks for your tutorial I have two different layout user layout and dashboard both have different files and build in CSS can you explain how to integrate both of them incorrect way. thanks in advance.
very cool.
thanks :)
I followed your tutorial and Want to achieve the same thing. Please check this question I asked on stackoverflow, maybe you can help.
https://stackoverflow.com/q/65365743/10587542