Websites with Angular and Less CSS tutorials
6. Creating the navigation component
av_timer1 minute read

So someone finally sent you the code?

  1. Topics of discussion
  2. Adding Angular Material, Bootstrap and jQuery to our app
  3. The navigation component

1. Topics of discussion

In this tutorial, we will be creating our first component, the navigation component, and we will also add some more plugins to our app. So, let's have some fun.

2. Adding Angular Material, Bootstrap and jQuery to our app

Angular Material is a very very useful plugin that allows you to create a navigation component pretty easily. It also has a number of other useful components which you can use if needed.

In order to install Angular Material, you need to run the following command:

npm install --save @angular/material@7.3.7 @angular/cdk@7.3.7

The --save portion of the command is used to save your newly added package to your dependency list in package.json. Supposedly starting from version 5 of npm they are automatically added even if you don't put --save there. In order to check your npm version, just run:

CODE: npm --version

Now that we have Angular Material installed, let's install Bootstrap as well:

npm install --save bootstrap

And finally, jQuery:

npm install --save jquery

Bootstrap is a powerful CSS framework that will allow us to easily organize elements in our pages. jQuery is a Javascript framework which is required in order to run the Javascript stuff that Bootstrap offers.

However, there is an extra step required in order to make sure we can use it in our application. We need to modify the angular.json file as follows. In the build section located at the beginning of the file, you have an options attribute, which has a bunch of attributes of its own. You need to modify the styles and scripts attributes by adding some files in there. It looks something like this:

"options": {
  "outputPath": "dist/recipe-site",
  "index": "src/index.html",
  "main": "src/main.ts",
  "polyfills": "src/polyfills.ts",
  "tsConfig": "src/tsconfig.app.json",
  "assets": [
    "src/favicon.ico",
    "src/assets"
  ],
  "styles": [
    "src/styles.less",
    "node_modules/bootstrap/dist/css/bootstrap.min.css"
  ],
  "scripts": [
    "node_modules/jquery/dist/jquery.min.js",
    "node_modules/bootstrap/dist/js/bootstrap.min.js"
  ],
  "es5BrowserSupport": true
}

So what did we just do? We basically told Angular that, whenever it builds the application, when it comes to styles, it should also include the bootstrap.min.css file located at the given path and when it comes to scripts, it should include the two Javascript files mentioned.

In other words, the generated style.css file after the build will also contain the contents from the bootstrap.min.css file and the generated scripts.js file will contain the contents from the 2 js files.

It is very important to add the jquery script BEFORE the bootstrap script. Otherwise, you'll receive an error as the bootstrap script expects jquery to already be added in your application.

3. The navigation component

Now that we have everything we need, let's start coding. The first thing we need to do is create a new folder in the app folder. So, right click on the app folder, select New Folder and name it components. Once that is done, go to the recipe-site folder where your app resides, go to the folder address bar, type cmd and press Enter.

Now that the command prompt is open, type the following command:

ng generate module components/Navigation

This line of code will create a new module called NavigationModule inside the components folder. You will notice that a folder called navigation has appeared inside the components folder. How did it know to do that? Well, by default, the Angular CLI adds the src/app part of the path to any module, component or service you might generate. It's basically the default path for it.

I've decided to keep our app organized into separate modules rather than all in one module because it is a better way to do it, performance wise, since in large scale apps, the main module can reference only the modules it actually needs rather than having to import them all.

Now that we have our module created, let's create our component:

ng generate component components/navigation/AppNavigation --module=Navigation --export

This line of code will generate a component called AppNavigationComponent inside the navigation folder. It will be a part of the NavigationModule and the --export allows as to automatically add it to the modules' exports array.

You'll have noticed the a folder called app-navigation has appeared inside the navigation folder. The Angular CLI looks at the name you have given to the component (in our case, AppNavigation) and creates a folder based on that name. Whenevr it encounters an uppercase letter, it considers it a new word and everything before it becomes a different word. The folder name is comprised of all these words, each separated by a - sign. And inside this folder, we have the 4 files associated to the component.

If you open the app-navigation.component.ts, you will notice that the class name is AppNavigationComponent. The Angular CLI automatically adds certain words after the name you give it, based on what you are attempting to create:

  • it adds Module if you are creating a module
  • it adds Component if you are creating a component
  • it adds Service if you are creating a service

Now, let's add the necessary html code to our template:

<mat-sidenav-container class="sidenav-container">
  <mat-sidenav #drawer class="sidenav" fixedInViewport="true"
    attr.role="'navigation'"
    mode="'over'"
    [opened]="isOpen">
    <mat-toolbar>
      <a class="home-button" routerLink="/index">
        <img src="assets/img/path_to_my_logo.png" />
      </a>
    </mat-toolbar>
    <mat-nav-list class="navigation-list">
      <a class="navigation-item" mat-list-item routerLink="/url_here" routerLinkActive="active">
        <mat-icon class="material-icons">library_books</mat-icon><span class="navigation-item-label">Link Name</span>
      </a>
    </mat-nav-list>
  </mat-sidenav>
  <mat-sidenav-content class="sidenav-content">
    <mat-toolbar class="sidenav-content-toolbar" color="primary">
      <button mat-icon-button class="menu-button" type="button" aria-label="Toggle sidenav" (click)="drawer.toggle()">
        <i class="material-icons">menu</i>
      </button>
      <span class="filler"></span>
    </mat-toolbar>
    <router-outlet></router-outlet>
  </mat-sidenav-content>
</mat-sidenav-container>

Quite a lot of stuff there right? Let's see what we have there. Basically, all the elements of our navigation component reside inside the mat-sidenav-container element. We also add a class to that element, which we will get to when we style it. The next step is to actually create the mat-sidenav element, which is the element that contains the navigation routes in it. As you can see, we define some attributes for that element.

The first thing you notice is the #drawer attribute. Whenever we do this on an element, we make it visible to the component. In other words, we can access it in a component by using the same name found after the # sign. It will become clear when we get to the Typescript code. Next up we have the fixedInViewPort which I believe gives the sidenav a fixed position if set to true. Ideally you want it set to true so that the sidenav always appears in the same place.

Next up we have the [attr.role] attribute, which is set to 'navigation' since that is the role of our sidenav. Aftwerwards we have the mode attribute, which is set to over, which means that when we open the sidenav, it opens over other elements of your page.

Finally, we have the [opened] attribute. You may have noticed that we have used brackets this time around. There is a subtle difference. When you don't use brackets, Angular understands that what you place after the = sign is the actual value for that attribute. However, when you use brackets like in this case, Angular looks at the value after the = sign and then checks the .ts file for a variable with the name given as value to the attribute. Once it finds the variable, it reads the value from it and that value is then attributed to the attribute.

These techniques have some very specific names in programming. They are called pass by value and pass by reference. The first case is pretty simple, you're passing the actual value to the attribute. In the second case, you're passing a reference to a variable which is then translated by Angular to an actual value.

We also have a mat-toolbar element which holds our logo inside it. In order for that image to be accessible, it needs to be added in the build phase. For that, you need to create an img folder inside the assets folder and place the image inside the img folder. Angular is already configured to copy the assets folder via this section in angular.json:

"assets": [
  "src/favicon.ico",
  "src/assets"
]

Next up is the mat-nav-list, which is the place where we will define our a tags. An a tag is called an anchor tag and it is the tag used to create links to other places in your website. We'll edit these links as we add components. For now, let's focus on some certain attributes and tags:

  • mat-list-item - it's an attribute you have to add when using Angular Material to create your sidenav so that it knows how to position elements inside the navigation list
  • routerLink - the url to which you are redirected when you click that link
  • routerLinkActive - the class which is added to that element when the current route is the one associated to the element
  • mat-icon - an element used to place an icon next to the text of the link; we use Angular Material icons, hence why the element has to have the material-icons class and inside the element we put a special code; Angular then knows what icon you are referring to; the material-icons class alongside an icon code can be used with other html tags as well, as you'll see below; more details can be found here

Finally, we have the mat-sidenav-content element which is used to define the area in which we display the content associated to the route we are currently viewing. The first thing we define in it is a button that toggles the sidenav and afterwards we abb the router-outlet element, which is pivotal if you are using Angular routing. The router-outlet element will be replaced with the component associated to the route your are currently viewing. Basically, that where Angular knows to render the html for that component.

Now that we have the html part done, let's move on to the Typescript code for the component logic:

import { Component, OnInit, ViewChild } from '@angular/core';

import { MatSidenav } from '@angular/material';



@Component({

  selector: 'app-navigation',

  templateUrl: './app-navigation.component.html',

  styleUrls: ['./app-navigation.component.less']

})

export class AppNavigationComponent implements OnInit {



  isOpen: boolean;

  @ViewChild('drawer') drawer: MatSidenav;

 

  constructor() { }



  ngOnInit() {

    this.isOpen = false;

  }

}

By default, when creating a component, Angular adds the app- prefix to it. This can be changed from the angular.json file, but for now, I've changed it for this component manually so that the selector would not be app-app-navigation. Instead, I've modified it to app-navigation.

As you can see, we have accessed the drawer element defined earlier via the @ViewChild() annotation. And since we want it to toggle a sidenav, we have assigned it the MatSidenav type. In theory you could have given it any other type, but it would have caused errors.

Other than that there is not much going on. We declare the isOpen variable and initialize it as false in the ngOnInit() method. That method requires implementation since our component implements the OnInit interface.

Finally, let's add some style to our page:

@main-color: rgb(6, 38, 57);
@secondary-color: rgb(211, 84, 77);
@main-color-light: lighten(@main-color, 80%);

@bootstrap-sm: ~"(max-width: 767px)";
@text-color: #fff;

.sidenav-container {
  height: 100%;

  .sidenav {
    width: 300px;
    background-color: #fff;

    box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12);

    .home-button {
      width: 100%;
      padding-top: 60px;
      margin-bottom: 30px;
      display: inline-block;

      .app-logo {
        width: 120px;
        margin: 0 auto;
        display: block;
      }
    }

    .mat-toolbar {
    background: inherit;
    }

    .navigation-list {
    padding-top: 30px;

    .navigation-item {
        &.active {
          background: @main-color-light;
          color: @main-color;
        }
        .mat-icon {
          margin-right: 5px;
        }
        .navigation-item-label {
          padding-top: 2px;
        }
      }

      .navigation-divider {
        color: @main-color-light;
        border-color: @main-color-light;
      }
    }
  }

  .sidenav-content {
    .sidenav-content-toolbar {
      background: @main-color;
      color: @text-color;
      .menu-button {
        color: @text-color;
        background: @main-color;
        &:focus {
          outline: none;
        }
      }
    }

    .filler {
      flex: 1 1 auto;
    }
  }
 
  .mat-toolbar.mat-primary {
    position: sticky;
    top: 0;
    z-index: 1;
  }
}

The first thing we do in this file is create a bunch of variables that we get to reuse later. We alos use the lighten function which takes a color and makes it lighter by a given percentage value.

We also define a variable used to create a media query later on. Media queries are used to apply different CSS rules based on a certain condition. And while bootstrap has since changed its width limits starting with version 4, I still resort to these values out of confort.

We than start to define some styles. As you can see, we start off with a .sidenav-container class and then define rules for other classes inside of it. The . before the class name is very important. Mostly because without it, you would not be defining a CSS class. Rather, your styles would get applied to the sidenav-container HTML element, which doesn't exist.

For those of you familiar with regular CSS, your might be wondering how you can define classes like this and still have it work. Well, Less isn't your regular CSS language. However, once it gets compiled, it gets compiled to regular CSS and the rules are as follows:

  • whenever you define a CSS class inside another CSS class, the corresponding regular CSS definition would be .class1 .class2 ... and so on; the styles for class1 would get applied to any html tag that has the class while the styles for class2 would get applied to any element that have that class set on them and are also children of elements with class1; in other words, if your html element has class2 on it, but is not defined inside another html element with class1 on it, then the styles for class2 will not be applied to it
  • whenever you use the & operator followed by a class name, the corresponding regular CSS would be .class1.class2; in other words, in order for the styles present in class2 to be applied, the element on which class2 is applied must also have class1 on it; an example in our case are the anchor tags, which have the routerLinkActive attribute on them; like I've already mentioned, the value for that attribute is a CSS class name that is added to the element alongside other existing classes; that means that our anchord tags would have both the navigation-item and active classes on them when we are on their respective route urls; hence why we use that way of writing CSS styles in our file
  • whenever you use the & operator followed by a state such as hoverfocusactive etc. then the styles defined in that block of code will only get applied if your html element is in one of those states; these styles are usually useful for buttons and anchor tags

In our case, we use that final bullet point to remove the outline from the menu button when we click on it (which would make the button be in a focus state). Otherwise, a weird blue outline would appear on the button.

Since this is not a CSS course, I won't go into too much detail about these concepts, but here are a few guidelines:

  • when defining the style for an element, you need to view it as a box
  • each of these boxes has a border, a content area, a margin and a padding
  • the border may or may not be visible, but it refers to the area that defines where the content of the box starts and ends; you have 4 borders: top, right, bottom, left
  • the content refers to what is found inside the box; for p elements it is usually text, for img elements it's the image and so on;
  • the margin refers to the distance between two different boxes
  • the padding refers to the difference between the border and where the content starts

This is a bird's eye view of what is known as the CSS box model, if you want to further your knowledge on this. I will work on separate courses for all these technologies as well.

Before we build our application, there are four more thing to be done. First, modify your navigation.module.ts file as follows:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatSidenavModule, MatIconModule, MatToolbarModule, MatListModule, MatButtonModule } from '@angular/material';
import { MatMenuModule} from '@angular/material/menu';
import { AppNavigationComponent } from './app-navigation/app-navigation.component';

@NgModule({
  declarations: [AppNavigationComponent],
  imports: [
    BrowserAnimationsModule,
    CommonModule,
    MatToolbarModule,
    MatButtonModule,
    MatSidenavModule,
    MatIconModule,
    MatMenuModule,
    MatListModule,
    RouterModule
  ],
  exports: [AppNavigationComponent]
})
export class NavigationModule { }

Remember when I said that sometimes you need to import other modules for your app to work? This is one of those times.

Next up, let's modify our app.component.html file as follows:

<app-navigation></app-navigation>
<router-outlet></router-outlet>

What we did here is basically add our navigation element in the page. Speaking of which, since this component is in another module, the 3rd step is to import said module in our app.module.ts file:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { NavigationModule } from './components/navigation/navigation.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    NavigationModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

And finally, we need to reference a stylesheet for Angular Material in our index.html page:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>RecipeSite</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
  <app-root></app-root>
</body>
</html>

And since no tutorial is ever done without a P.S., add this line to the beginning of your styles.less file. It will help solve an annoying issue reported by Chrome regarding the theme for Angular Material:

@import '@angular/material/prebuilt-themes/deeppurple-amber.css';

Right, that should cover everything. Run an ng build followed by ng serve --open to see the changes. In the next tutorials, we will be adding more components and routes to our pages. See you then.

COMMENTS
No comments yet...