guide layout with angular material - devonfw/devon4ng GitHub Wiki

Table of Contents

Angular Material Layout

The purpose of this guide is to get a basic understanding of creating layouts using Angular Material in a devon4ng application. We will create an application with a header containing some menu links and a sidenav with some navigation links.

Finished application
Figure 1. This is what the finished application will look like

Create a new angular application

We start with opening the devonfw IDE(right-click anywhere in your workspace and click "Open devonfw CMD shell here") and running the following command to start a project named devon4ng-mat-layout

  • ng new devon4ng-mat-layout --routing --style=scss. If you are using Nx, the command would be nx generate @nrwl/angular:app devon4ng-mat-layout --routing --style=scss in your Nx workspace. Click here to get started with using Nx.

We are providing the routing flag so that a routing module is generated, and we are also setting the style sheet format to SCSS with --style=scss.

Once the creation process is complete, open your newly created application in Visual Studio Code. Try running the empty application by running the following command in the integrated terminal:

  • ng serve. (If you are using Nx, you have to specify the project name along with the --project flag, so the command becomes ng serve --project=devon4ng-mat-layout)

Angular will spin up a server and you can check your application by visiting http://localhost:4200/ in your browser.

Blank application
Figure 2. Blank application

Adding Angular Material library to the project

Next we will add Angular Material to our application. In the integrated terminal, press Ctrl + C to terminate the running application and run the following command:

  • npm install --save @angular/material @angular/cdk @angular/animations

You can also use Yarn to install the dependencies if you prefer that:

  • yarn add @angular/material @angular/cdk @angular/animations

Once the dependencies are installed, we need to import the BrowserAnimationsModule in our AppModule for animations support.

Importing BrowserAnimationsModule in AppModule
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

@NgModule({
  ...
  imports: [BrowserAnimationsModule],
  ...
})
export class AppModule { }

Angular Material provides a host of components for designing our application. All the components are well structured into individual NgModules. For each component from the Angular Material library that we want to use, we have to import the respective NgModule.

We will be using the following components in our application:
import { MatIconModule, MatButtonModule, MatMenuModule, MatListModule, MatToolbarModule, MatSidenavModule } from '@angular/material';

@NgModule({
  ...
  imports: [
	...
    MatIconModule,
    MatButtonModule,
    MatMenuModule,
    MatListModule,
    MatToolbarModule,
    MatSidenavModule,
	...
	],
  ...
})
export class AppModule { }

A better approach is to import and then export all the required components in a shared module. But for the sake of simplicity, we are importing all the required components in the AppModule itself.

ℹ️
You can find a working copy of this application https://github.com/devonfw-sample/devon4ts-samples/tree/master/apps/angular-material-basic-layout[here]. The sample application is part of a Nx workspace, which means it is one of the many apps in a monorepo and capable of importing reusable code from a shared library. This guide describes the implementaion by assuming a stand-alone single-repo application, but the pages and layout described in this sample app are similar to the ones used in another sample app in the monorepo (https://github.com/devonfw-sample/devon4ts-samples/tree/master/apps/angular-material-theming[angular-material-theming]), which is why we have exported the required components from a shared library and reused them in both the apps. As a result, the code in our monorepo will be slightly different. It would still help you in following this guide.

Next, we include a theme in our application. Angular Material comes with four pre-defined themes: indigo-pink, deeppurple-amber, pink-bluegrey and purple-green. It is also possible to create our own custom theme, but that is beyond the scope of this guide. Including a theme is required to apply all of the core and theme styles to your application. We will include the indigo-pink theme in our application by importing the indigo-pink.css file in our src/styles.scss:

In src/styles.scss:
@import "~@angular/material/prebuilt-themes/indigo-pink.css";

To use Material Design Icons along with the mat-icon component, we will load the Material Icons library in our src/index.html file

In src/index.html:
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

Development

Now that we have all the Angular Material related dependencies set up in our project, we can start coding. Let’s begin by adding a suitable margin and font to the body element of our single page application. We will add it in the src/styles.scss file to apply it globally:

In src/styles.scss:
body {
  margin: 0;
  font-family: "Segoe UI", Roboto, sans-serif;
}

At this point, if we run our application, this is how it will look like:

Angular Material added to the application
Figure 3. Application with Angular Material set up

We will clear the app.component.html file and setup a header with a menu button and some navigational links. We will use mat-toolbar, mat-button, mat-menu, mat-icon and mat-icon-button for this:

app.component.html:
<mat-toolbar color="primary">
  <button mat-icon-button aria-label="menu">
    <mat-icon>menu</mat-icon>
  </button>
  <button mat-button [matMenuTriggerFor]="submenu">Menu 1</button>
  <button mat-button>Menu 2</button>
  <button mat-button>Menu 3</button>
  
  <mat-menu #submenu="matMenu">
    <button mat-menu-item>Sub-menu 1</button>
    <button mat-menu-item [matMenuTriggerFor]="submenu2">Sub-menu 2</button>
  </mat-menu>

  <mat-menu #submenu2="matMenu">
    <button mat-menu-item>Menu Item 1</button>
    <button mat-menu-item>Menu Item 2</button>
    <button mat-menu-item>Menu Item 3</button>
  </mat-menu>

</mat-toolbar>

The color attribute on the mat-toolbar element will give it the primary (indigo) color as defined by our theme. The color attribute works with most Angular Material components; the possible values are 'primary', 'accent' and 'warn'. The mat-toolbar is a suitable component to represent a header. It serves as a placeholder for elements we want in our header. Inside the mat-toolbar, we start with a button having mat-icon-button attribute, which itself contains a mat-icon element having the value menu. This will serve as a menu button which we can use to toggle the sidenav. We follow it with some sample buttons having the mat-button attribute. Notice the first button has a property matMenuTriggerFor bound to a local reference submenu. As the property name suggests, the click of this button will display the mat-menu element with the specified local reference as a drop-down menu. The rest of the code is self explanatory.

Header added to the application
Figure 4. This is how our application looks with the first menu button (Menu 1) clicked.

We want to keep the sidenav toggling menu button on the left and move the rest to the right to make it look better. To do this we add a class to the menu icon button:

app.component.html:
...
  <button mat-icon-button aria-label="menu" class="menu">
    <mat-icon>menu</mat-icon>
  </button>
...

And in the app.component.scss file, we add the following style:

app.component.scss:
.menu {
    margin-right: auto;
}

The mat-toolbar element already has it’s display property set to flex. Setting the menu icon button’s margin-right property to auto keeps itself on the left and pushes the other elements to the right.

Final look of the header
Figure 5. Final look of the header.

Next, we will create a sidenav. But before that lets create a couple of components to navigate between, the links of which we will add to the sidenav. We will use the ng generate component (or ng g c command for short) to create Home and Data components. (Append --project=devon4ng-mat-layout to the command in a Nx workspace). We nest them in the pages sub-directory since they represent our pages.

  • ng g c pages/home

  • ng g c pages/data;

Let us set up the routing such that when we visit http://localhost:4200/ root url we see the HomeComponent and when we visit http://localhost:4200/data url we see the DataComponent. We had opted for routing while creating the application, so we have the routing module app-routing.module.ts setup for us. In this file, we have the empty routes array where we set up our routes.

app-routing.module.ts:
import { HomeComponent } from './pages/home/home.component';
import { DataComponent } from './pages/data/data.component';

	const routes: Routes = [
	  { path: '', component: HomeComponent },
	  { path: 'data', component: DataComponent }
	];

We need to provide a hook where the components will be loaded when their respective URLs are loaded. We do that by using the router-outlet directive in the app.component.html.

app.component.html:
...
	</mat-toolbar>
	<router-outlet></router-outlet>

Now when we visit the defined URLs we see the appropriate components rendered on screen.

Lets change the contents of the components to have something better.

home.component.html:
<h2>Home Page</h2>
home.component.scss:
h2 {
    text-align: center;
    margin-top: 50px;
}
data.component.html:
<h2>Data Page</h2>
data.component.scss:
h2 {
    text-align: center;
    margin-top: 50px;
}

The pages look somewhat better now:

Home page
Figure 6. Home page
Data page
Figure 7. Data page

Let us finally create the sidenav. To implement the sidenav we need to use 3 Angular Material components: mat-sidenav-container, mat-sidenav and mat-sidenav-content. The mat-sidenav-container, as the name suggests, acts as a container for the sidenav and the associated content. So it is the parent element, and mat-sidenav and mat-sidenav-content are the children sibling elements. mat-sidenav represents the sidenav. We can put any content we want, though it is usually used to contain a list of navigational links. The mat-sidenav-content element is for containing the contents of our current page. Since we need the sidenav application-wide, we will put it in the app.component.html.

app.component.html:
...
</mat-toolbar>

<mat-sidenav-container>
  <mat-sidenav mode="over" [disableClose]="false" #sidenav>
    Sidenav
  </mat-sidenav>
  <mat-sidenav-content>
    <router-outlet></router-outlet>
  </mat-sidenav-content>
</mat-sidenav-container>

The mat-sidenav has a mode property, which accepts one of the 3 values: over, push and side. It decides the behavior of the sidenav. mat-sidenav also has a disableClose property which accents a boolean value. It toggles the behavior where we click on the backdrop or press the Esc key to close the sidenav. There are other properties which we can use to customize the appearance, behavior and position of the sidenav. You can find the properties documented online at https://material.angular.io/components/sidenav/api We moved the router-outlet directive inside the mat-sidenav-content where it will render the routed component. But if you check the running application in the browser, we don’t see the sidenav yet. That is because it is closed. We want to have the sidenav opened/closed at the click of the menu icon button on the left side of the header we implemented earlier. Notice we have set a local reference #sidenav on the mat-sidenav element. We can access this element and call its toggle() function to toggle open or close the sidenav.

app.component.html:
...
  <button mat-icon-button aria-label="menu" class="menu" (click)="sidenav.toggle()">
    <mat-icon>menu</mat-icon>
  </button>
...
Sidenav works
Figure 8. Sidenav is implemented

We can now open the sidenav by clicking the menu icon button. But it does not look right. The sidenav is only as wide as its content. Also the page does not stretch the entire viewport due to lack of content. Let’s add the following styles to make the page fill the viewport:

app.component.scss:
...
mat-sidenav-container {
    position: absolute;
    top: 64px;
    left: 0;
    right: 0;
    bottom: 0;
}

The sidenav width will be corrected when we add the navigational links to it. That is the only thing remaining to be done. Lets implement it now:

app.component.html:
...
  <mat-sidenav [disableClose]="false" mode="over" #sidenav>
	<mat-nav-list>
      <a
        id="home"
        mat-list-item
        [routerLink]="['./']"
        (click)="sidenav.close()"
        routerLinkActive="active"
        [routerLinkActiveOptions]="{exact: true}"
      >
        <mat-icon matListAvatar>home</mat-icon>
        <h3 matLine>Home</h3>
        <p matLine>sample home page</p>
      </a>
      <a
        id="sampleData"
        mat-list-item
        [routerLink]="['./data']"
        (click)="sidenav.close()"
        routerLinkActive="active"
      >
        <mat-icon matListAvatar>grid_on</mat-icon>
        <h3 matLine>Data</h3>
        <p matLine>sample data page</p>
      </a>
    </mat-nav-list>
  </mat-sidenav>
...

We use the mat-nav-list element to set a list of navigational links. We use the a tags with mat-list-item directive. We implement a click listener on each link to close the sidenav when it is clicked. The routerLink directive is used to provide the URLs to navigate to. The routerLinkActive directive is used to provide the class name which will be added to the link when it’s URL is visited. Here we name the class`active`. To style it, let' modify the app.component.scss file:

app.component.scss:
...
mat-sidenav-container {
...
	a.active {
        background: #8e8d8d;
        color: #fff;

        p {
            color: #4a4a4a;
        }
    }
}

Now we have a working application with a basic layout: a header with some menu and a sidenav with some navigational links.

Finished application
Figure 9. Finished application

Conclusion

The purpose of this guide was to provide a basic understanding of creating layouts with Angular Material. The Angular Material library has a huge collection of ready to use components which can be found at https://material.angular.io/components/categories It has provided documentation and example usage for each of its components. Going through the documentation will give a better understanding of using Angular Material components in our devon4ng applications.

⚠️ **GitHub.com Fallback** ⚠️