Angular Material - Part Two

If you worked with bootstrap you know that implementing tabs require some complex markup but in Angular Material we can easily add tabs to our application with only 2 elements.

Now let’s explore a few other Angular Material components. Here is part 1 of Angular Material.

Tooltips

Now let’s see how to add tooltips to your elements. So let’s start by importing the tooltip module.

Step 1

Adding the tooltip module in app.module.ts

  1. import { MatTooltipModule } from '@angular/material/tooltip';  

and here is our import array.

  1. imports: [  
  2.   BrowserModule,  
  3.   BrowserAnimationsModule,  
  4.   MatTooltipModule  
  5. ]  

And now we’ll use the import statement shortcut, instead of using fully qualified path we can access all our Modules in this import statement reference.

  1. import { MatTooltipModule } from '@angular/material';  

Step 2

Now let’s go to app.component.html and here we have a span and when we hover on this span:

  1. <span matTooltip="Here is my Tooltip">Some text</span>  

Now if you see in the browser and hover on the Some text we’ll see the hover.

Angular Material

And look we get this beautiful tooltip with nice animation. We can also position our tooltips. So how can we apply the position? Here is the guide for you, you can explore the different properties here

  1. <span   
  2.   matTooltipPosition="right"  
  3.   matTooltip="Here is my Tooltip">  
  4.   Some text  
  5. </span>  

And now if we hover over the text in the browser, we get tooltip on the right side of the text.

Angular Material

Here our tooltip appears on the right side. So it is very easy to apply the tooltip in our Angular elements.

Tabs

If you worked with bootstrap you know that implementing tabs requires some complex markups but in Angular Material we can easily add tabs to our application with only 2 elements.

Step 1

So let’s add the tab module.

  1. import { MatTooltipModule, MatTabsModule } from '@angular/material';  

And this is the import array to add the module.

  1. imports: [  
  2.   BrowserModule,  
  3.   BrowserAnimationsModule,  
  4.   MatTooltipModule,  
  5.   MatTabsModule  
  6. ]  

Step 2

Now let’s go back to our template. We put all our tabs inside a group

  1. <mat-tab-group>  
  2.   <mat-tab label="College">  
  3.     Content of College Tab  
  4.   </mat-tab>  
  5.   <mat-tab label="School">  
  6.     Content of School Tab  
  7.   </mat-tab>  
  8. </mat-tab-group>  

And if we watch the browser results.

Angular Material

And if you click on the tabs you’ll see the nice animation as well. We can see this tabs markup is very simple especially compared to the bootstrap markup for rendering tabs.

There is a little problem with these tabs. The content is so close to the tabs. So let’s add some padding to the text class in styles.css. And we already know that the styles we’re applying in styles.css don’t need to repeat in every component because it is our global style. So,

Angular Material

Now let’s style it in the styles.css

  1. /* You can add global styles to this file, and also import other style files */  
  2. @import "https://fonts.googleapis.com/icon?family=Material+Icons";  
  3. @import "~@angular/material/prebuilt-themes/indigo-pink.css";  
  4.   
  5. .mat-tab-body-content {  
  6.     padding-top: 20px;  
  7. }  

Now they are looking much cleaner in the browser.

Angular Material

Dialog

Step 1

So now let’s see how to add the dialog in the application. So first of all add the dialog module in app.module.ts

  1. import { MatTooltipModule, MatTabsModule, MatDialogModule,  
  2.   MatButtonModule } from '@angular/material';  
  3. Import array:  
  4.   imports: [  
  5.     BrowserModule,  
  6.     BrowserAnimationsModule,  
  7.     MatTooltipModule,  
  8.     MatTabsModule,  
  9.     MatDialogModule,  
  10.     MatButtonModule  
  11.   ]  

Step 2

Now let’s go to app template. And here we’ll add the button when the user clicks on this button, it will open the dialog. Apply the button variant directive mat-raised-button on the button.

  1. <button   
  2.   (click)="openDialog()"  
  3.   mat-raised-button>Dialog</button>  

Now let’s go to app.component.ts and here I’m going to add openDialog() method.

Step 3

So how can we open the dialog? It is very easy, first of all let’s imagine this dialog is for editing a course. Somewhere in our application we need to have a component for editing a course, we can display that component in the router outlet as a web page or we can display it inside a dialog. How do we display it? Doesn’t matter. But we need a component to start with. So we’ll generate a new component here.

PS C:\Users\Ami Jan\material-demo\material-demo> ng g c course

So now we have a new component. Now in order to display this component inside a dialog first we need to inject MatDialog service object. This is the service defined in MatDialogModule that we use for opening dialogs.

  1. import { Component } from '@angular/core';  
  2. import { MatDialog } from '@angular/material';  
  3. import { CourseComponent } from './course/course.component';  
  4.   
  5. @Component({  
  6.   selector: 'app-root',  
  7.   templateUrl: './app.component.html',  
  8.   styleUrls: ['./app.component.css']  
  9. })  
  10. export class AppComponent {  
  11.   constructor(private dialog: MatDialog) {}  
  12.   
  13.   openDialog() {  
  14.     // Target component in open()  
  15.     this.dialog.open(CourseComponent);  
  16.   }  
  17. }  

Step 4

Now let’s have a look in the browser, on button click we get the error in the console.

Angular Material

Look we got overlay, and it looks like there is a dialog on the left side of the screen but it looks like something is not working. And when we take a look at the console we get the error ‘No component factor found for CourseComponent’. We already know about compilation steps, when an Angular application starts. So we know that the Angular compiler kicks in and it's going to walk down the tree of our component and then it will compile each component in this tree.

Angular Material

But where does this tree come from? Well it starts from index.html and here we have app-root (root component) and then angular compiler is going to look at the template for app-root which is app.component.html. And here in app.component.html we haven’t any references to any other component in this application. In other words the only component in the component tree is our app component even though we have created coursecomponent but it is not in the component tree. So angular compiler can’t find it, it can’t compile it and that’s why we got that error.

Also in the deployment article we discussed what happens when Angular compiler compiles a component for each component, it generates the ngFactory file.

Angular Material

So that’s why we got this error. No component factory found for CourseComponent, and the main reason for this error is we’re adding the component dynamically in the DOM. It is not hard coded anywhere in our templates. To solve this problem we need to register this component as NgComponent. So in app.module.ts, inside the NgModule decorator we have some properties (declarations, imports, providers and so on). We can also add another property ‘entryComponents’ here that you’ve not seen yet. And in this property we’ll add the components that we added dynamically. So,

  1. @NgModule({  
  2.   declarations: [  
  3.     AppComponent,  
  4.     CourseComponent  
  5.   ],  
  6.   entryComponents: [  
  7.     CourseComponent  
  8.   ],  
  9.   imports: [  
  10.     BrowserModule,  
  11.     BrowserAnimationsModule,  
  12.     MatTooltipModule,  
  13.     MatTabsModule,  
  14.     MatDialogModule,  
  15.     MatButtonModule  
  16.   ],  
  17.   providers: [],  
  18.   bootstrap: [AppComponent]  
  19. })  
  20. export class AppModule {}  

Step 5

Now open the browser and the error is gone and when we click on the button we get a beautiful dialog.

Angular Material

Step 6

Now let’s add two buttons to close the dialog. So let’s go to course.component.html and here we add the couple of buttons.

  1. <p>  
  2.   course works!  
  3. </p>  
  4. <button mat-raised-button>Yes</button>  
  5. <button mat-raised-button>No</button>  

When the user clicks on any of these buttons, we want the dialog to close automatically. How do we do that? So here is another directive for this purpose. So if you look at the API tab of the dialog module, we can see MatDialogClose directive. Look at the selector

  1. button[mat-dialog-close] button[matDialogClose]  

We can apply this as an attribute to the button element.

  1. <p>  
  2.   course works!  
  3. </p>  
  4. <button mat-raised-button mat-dialog-close>Yes</button>  
  5. <button mat-raised-button>No</button>  

Now optionally we can set the value here with selector and this value will be the result of closing this dialog. So if we want to tell the component that opened this dialog what button the user clicked on, we can set the value.

  1. <p>  
  2.   course works!  
  3. </p>  
  4. <button mat-raised-button mat-dialog-close="yes">Yes</button>  
  5. <button mat-raised-button mat-dialog-close="no">No</button>  

Step 7

Now let’s go back to app.component.ts and read the results. Look the returntype of open()

Angular Material

The returntype of this method is an instance of MatDialogRef. Note that this class is generic and inside this angle brackets we have to type the generic argument. So here we’ll get the dialog reference to Course component. So let’s chain a few methods call there. Once we get the DialogRef we can call afterClosed() and the returntype of afterClosed() is Observable.

  1. export class AppComponent {  
  2.   constructor(private dialog: MatDialog) {}  
  3.   
  4.   openDialog() {  
  5.     this.dialog.open(CourseComponent).afterClosed();  
  6.   }  
  7. }  

With this observable we can subscribe to it and will be notified when the user closes our dialog box. So

  1. export class AppComponent {  
  2.   constructor(private dialog: MatDialog) {}  
  3.   
  4.   openDialog() {  
  5.     // Target component in open()  
  6.     this.dialog  
  7.       .open(CourseComponent)  
  8.       .afterClosed()  
  9.       .subscribe(result => console.log(result));  
  10.   }  
  11. }  

Step 8

Now back in the browser.

Angular Material

So to open the dialog we use MatDialog service, with which we simply call the open() and optionally if we want to be notified when the dialog is closed we can subscribe to the observable that is returned from the afterClosed()

Passing Data to Dialogs

Now we know how to open and close Dialog Boxes.

Step 1

Let’s see how to pass data to your dialogs. So here back in app.component.ts when opening CourseComponent let’s say we want to pass id of the course for any purpose. Passing the data is very easy, in open() we pass the 2nd argument to pass the data to CourseComponent.

  1. export class AppComponent {  
  2.   constructor(private dialog: MatDialog) {}  
  3.   
  4.   openDialog() {  
  5.     this.dialog  
  6.       .open(CourseComponent, {  
  7.         // set it with any data, number, string, any object to pass the dialog box  
  8.         data: { courseId: 1 }  
  9.       })  
  10.       .afterClosed()  
  11.       .subscribe(result => console.log(result));  
  12.   }  
  13. }  

This is how we pass the data to our dialogs.

Step 2

On the receiving side we should be able to read this data. So let’s go to course.component.ts, when the dialog service opens this component in the dialog it is going to pass the data in the constructor. So the constructor parameter should be any because we can pass anything.

  1. export class CourseComponent implements OnInit {  
  2.   constructor(data: any) {  
  3.     console.log('Data', data);  
  4.   }  
  5.   ngOnInit() {  
  6.   }  
  7. }  

Step 3

Now back in the browser.

Angular Material

Here we get this error ‘Can’t resolve all parameters for CourseComponent’. This is the error that you’ll see  from time to time and basically this has to do with Dependency Injection. Let me explain why. You know that when the application starts angular is going to compile each component in the component tree. And in this case, it is going to compile CourseComponent. So first it looks at the constructor and looks at all the parameters we defined here and is going to inject those parameters into this constructor, that’s Dependency Injection. Now if we had instead of this parameter of type any, if we had something like:

  1. constructor(service: CourseService) {  
  2.   console.log('Data', service);  
  3. }  

Would we get an error here? No. Let’s verify this, so let’s generate the service

PS C:\Users\Ami Jan\material-demo\material-demo> ng g s course

Now let’s go to app.module.ts and register this service as a provider. So,

  1. providers: [  
  2.   CourseService  
  3. ]  

Now back in CourseComponent.

  1. import { Component, OnInit } from '@angular/core';  
  2. import { CourseService } from '../course.service';  
  3.   
  4. @Component({  
  5.   selector: 'app-course',  
  6.   templateUrl: './course.component.html',  
  7.   styleUrls: ['./course.component.css']  
  8. })  
  9. export class CourseComponent implements OnInit {  
  10.   
  11.   constructor(service: CourseService) {  
  12.     console.log('Data');  
  13.   }  
  14.   ngOnInit() {  
  15.   }  
  16. }  

Back in the browser and the error is gone.

Angular Material

So if the parameter is of type CourseService we don’t get that error anymore. Let’s revert the code back to what we had earlier:

  1. constructor(data: any) {  
  2.   console.log('Data');  

The reason we got the error earlier is because Angular doesn’t know what to inject into this constructor because it can be anything. So to fix this problem we need to have a closer look at Dependency injection.

Step 4

Let’s go back to our app.module.ts, here in providers array we have 2 ways to register the dependency. One way is the simple way which we’re already using

  1. providers: [  
  2.   CourseService  
  3. ]  

We’ve also seen another way of using providers object so we pass the object with 2 properties.

  1. providers: [  
  2.   CourseService,  
  3.   { provide: CourseService, useClass: CourseService }  
  4. ]  

We’ve already used this approach in our Angular series. With this we’re basically telling Angular that whenever we have the parameter in the constructor of a class and the type of that parameter is CourseService (provide: CourseService), use this class CourseService (useClass: CourseService).

The first way of registering the dependency is basically a short way to the 2nd approach. So to solve the problem of any parameter, we need to register some kind of dependency here. So duplicate the line and make it for any,

  1. providers: [  
  2.   CourseService,  
  3.   { provide: CourseService, useClass: CourseService },  
  4.   { provide: any, useClass: CourseService }  
  5. ]  

But it doesn’t make sense, it's kind of weird. We’re telling Angular that whenever the type of the parameter of the constructor of the class is any, use CourseService. It is really weird. That’s why we got the compilation error on any. So how can we solve this? Before we discuss how to solve the problem, I want to introduce you to a new term.

In Angular what we have in provide property parameter is Injection token.

  1. { provide: CourseService }  

And  this injection token determines the type of parameter of the constructor of the class. And when dealing with non-class parameter, like when we have the parameter of type number or string or an interface, we need to create custom injection token because that type can’t be used as a token. We can’t have number, string or any in place of provide property.

  1. { provide: number }  

So we need to create custom injection token. So how can we do it?

Step 5

Let’s go back to CourseComponent and here we create the custom injection token for this parameter of constructor.

  1. import { Component, OnInit, InjectionToken } from '@angular/core';  
  2. import { CourseService } from '../course.service';  
  3.   
  4. // constant variable always in capital  
  5. export const DIALOG_DATA = new InjectionToken('DIALOG_DATA');  
  6.   
  7. @Component({  
  8.   selector: 'app-course',  
  9.   templateUrl: './course.component.html',  
  10.   styleUrls: ['./course.component.css']  
  11. })  

Now we have InjectionToken, the string parameter of InjectionToken is the name of the token.

Step 6

Now go back to app.module.ts and we use this custom token in our provider object. So back to app.module.ts

  1. providers: [  
  2.   CourseService,  
  3.   { provide: CourseService, useClass: CourseService },  
  4.   { provide: DIALOG_DATA, useClass:  }  
  5. ]  

Now what should we use for the class. Here we don’t want to deal with predefined class because we’re dealing with an object that we pass at runtime. So instead of useClass property we can use useValue and here we can pass an actual object.

  1. providers: [  
  2.   CourseService,  
  3.   { provide: CourseService, useClass: CourseService },  
  4.   { provide: DIALOG_DATA, useValue: {}}  
  5. ]  

So with this we’re telling Angular that whevever the parameter of the class is marked with this custom injection token that is DIALOG_DATA pass the empty object as the value of parameter during dependency injection.

Step 7

Now let’s go back to CourseComponent and use the injection token in the constructor.

  1. import { Component, OnInit, InjectionToken, Inject } from '@angular/core';  
  2. import { CourseService } from '../course.service';  
  3.   
  4. // constant variable always in capital  
  5. export const DIALOG_DATA = new InjectionToken('DIALOG_DATA');  
  6.   
  7. @Component({  
  8.   selector: 'app-course',  
  9.   templateUrl: './course.component.html',  
  10.   styleUrls: ['./course.component.css']  
  11. })  
  12. export class CourseComponent implements OnInit {  
  13.   
  14.   constructor(@Inject(DIALOG_DATA) data: any) {  
  15.     console.log('Data', data);  
  16.   }  
  17.   
  18.   ngOnInit() {  
  19.   }  
  20. }  

Now open the browser and see if these changes have solved our problem. And now the error is gone.

Angular Material

So we have this empty object on Open Dialog click button. This is the object we defined during dependency injection. But our purpose was to pass some data to our dialog. So earlier in our app.component.html when opening the dialog we pass the complex object with data

  1. openDialog() {  
  2.   this.dialog  
  3.     .open(CourseComponent, {  
  4.       data: { courseId: 1 }  
  5.     })  
  6.     .afterClosed()  
  7.     .subscribe(result => console.log(result));  
  8. }  

So instead of blank object we want to get this object here.

  1. data: { courseId: 1 }  

We have discussed everything, now you know what happens under the hood.

Step 8

At this point we don’t really have to define the custom injection token. This is actually defined in the dialog module in Angular Material. So let’s go to CourseComponent and remove the custom injection token and in the constructor we’re going to inject the injection token that is defined in the dialog module. So,

  1. import { Component, OnInit, InjectionToken, Inject } from '@angular/core';  
  2. import { CourseService } from '../course.service';  
  3. import { MAT_DIALOG_DATA } from '@angular/material';  
  4.   
  5. @Component({  
  6.   selector: 'app-course',  
  7.   templateUrl: './course.component.html',  
  8.   styleUrls: ['./course.component.css']  
  9. })  
  10. export class CourseComponent implements OnInit {  
  11.   
  12.   constructor(@Inject(MAT_DIALOG_DATA) data: any) {  
  13.     console.log('Data', data);  
  14.   }  
  15.   ngOnInit() {  
  16.   }  
  17. }  

And finally let’s go back to our app.module.ts and we don’t need to register DIALOG_DATA anymore

  1. providers: [  
  2.   CourseService,  
  3.   { provide: CourseService, useClass: CourseService }  
  4. ]  

Because when we import MatDialogModule in imports array, it automatically registers all the dependencies. So there in that module there is a provider object that associates the custom injection token with predefined object. And now test the application in the browser,

Angular Material

And this is what we get on Open Dialog button click.

Creating a Reusable Module

We have used a lot of Angular Material components so far. Now take a look at app.module.ts, you can see our import array is growing

  1. imports: [  
  2.   BrowserModule,  
  3.   BrowserAnimationsModule,  
  4.   FormsModule,  
  5.   MatCheckboxModule,  
  6.   MatRadioModule,  
  7.   MatSelectModule,  
  8.   MatInputModule,  
  9.   MatDatepickerModule,  
  10.   MatNativeDateModule,  
  11.   MatIconModule,  
  12.   MatButtonModule,  
  13.   MatChipsModule,  
  14.   MatProgressSpinnerModule,  
  15.   MatTooltipModule,  
  16.   MatTabsModule,  
  17.   MatDialogModule,  
  18.   MatButtonModule  
  19. ]  

It is getting really fat and as we use more angular material components, this array is getting bigger. So let’s see how to refactor the code and make it cleaner. But first of all let us discuss what exactly is wrong with this implementation?

In Software Engineering, we have the concept called Cohesion which means things that are related should be together and things that are not related should not be together. This principal helps us to organize our application code properly.

Angular Material

As an analogy think of a house, in the kitchen all the objects are highly related we have plates, glasses, pots and so on. We don’t have the bed in the kitchen.

Angular Material

Similarly in the bedroom we have the bed room furniture; we don’t have pots in the bed room.

Angular Material

So this is the cohesion principal in real life. Now in this app.module.ts implementation we’re missing the cohesion principal. Look at the imports array -- all the MatModules are highly related  and these are all about Angular Material components,

  • MatCheckboxModule,
  • MatRadioModule,
  • MatSelectModule,
  • MatInputModule,
  • MatDatepickerModule,
  • MatNativeDateModule,
  • MatIconModule,
  • MatButtonModule,
  • MatChipsModule,
  • MatProgressSpinnerModule,
  • MatTooltipModule,
  • MatTabsModule,
  • MatDialogModule,
  • MatButtonModule

In contrast we have FormsModule which is completely separate from Angular Material Modules. So to apply the cohesion principal here and organize this code in a more better way we need to put all the Angular Material modules inside a new module.

Step 1

Now let’s see how to create a new module

PS C:\Users\Ami Jan\material-demo\material-demo> ng g m mat-components

And this creates a new folder mat-components and in this folder we have mat-components.module.ts. In this implementation we don’t really need to put only this single file inside a folder and I wanna take this file out and put it under folder. So,

PS C:\Users\Ami Jan\material-demo\material-demo> mv src/app/mat-components/mat-components.module.ts

src/app

PS C:\Users\Ami Jan\material-demo\material-demo> rm src/app/mat-components

Step 2

Now let’s go to mat-components.module.ts

  1. import { NgModule } from '@angular/core';  
  2. import { CommonModule } from '@angular/common';  
  3.   
  4. @NgModule({  
  5.   imports: [  
  6.     CommonModule  
  7.   ],  
  8.   declarations: []  
  9. })  
  10. export class MatComponentsModule { }  

We can see we have a class called MatComponentsModule. This class is just like our app module  and is decorated with NgModule decorator function. So here we have imports and declarations exactly like in our app module. But now here we are not going to have any components, any directives, any pipes. So we don’t really need this declarations property, also because we’re not going to have any components, any template, any directives defined somewhere else. So we don’t really need imports array as well.

We have another property called exports which is an array. And what we add in this exports array will be exported out of this module. So this means we can move all the Angular Material Modules here in exports array and then when we import MatComponentsModule in app.module.ts class all those Angular Material Modules will automatically come with our new module. So,

  1. import { NgModule } from '@angular/core';  
  2. import { MatTooltipModule, MatTabsModule, MatDialogModule,  
  3.   MatButtonModule, MatCheckboxModule, MatRadioModule,  
  4.   MatSelectModule, MatInputModule, MatDatepickerModule,  
  5.   MatNativeDateModule, MatIconModule, MatChipsModule,  
  6.   MatProgressSpinnerModule} from '@angular/material';  
  7.   
  8. @NgModule({  
  9.   exports: [  
  10.     MatCheckboxModule,  
  11.     MatRadioModule,  
  12.     MatSelectModule,  
  13.     MatInputModule,  
  14.     MatDatepickerModule,  
  15.     MatNativeDateModule,  
  16.     MatIconModule,  
  17.     MatButtonModule,  
  18.     MatChipsModule,  
  19.     MatProgressSpinnerModule,  
  20.     MatTooltipModule,  
  21.     MatTabsModule,  
  22.     MatDialogModule,  
  23.     MatButtonModule  
  24.   ]  
  25. })  
  26. export class MatComponentsModule { }  

And our app.module.ts code is

  1. imports: [  
  2.   BrowserModule,  
  3.   BrowserAnimationsModule,  
  4.   FormsModule,  
  5.   MatComponentsModule  
  6. ]  

Now our app.module.ts is much cleaner and more maintainable.

Themes

In Angular material we have the concept of theme which is basically the collection of various color patterns. We have primary, secondary, warning colors.

Angular Material

Primary refers to a color that appear most frequently in our application, secondary color is used to give some accent of certain key elements in the UI. We often use secondary colors for buttons, progress bars, sliders, highlighting things and so on. And we use the warn palette to give warnings to the user.

Let’s discuss some examples,

Angular Material

In this design the primary color is blue and we have 2 different shades of primary color. The secondary or the accent color is yellow and we can see we have use this here for button and slider. So the secondary color allows certain elements like buttons and sliders to stand out.

Here is another example, on this page we have multiple tabs.

Angular Material

We can see 3 different shades of primary color. And here again we use yellow for the secondary color to highlight the currently selected tab. These palettes help us to keep a consistent look in our application. So if in the future we decide to change this blue to a different color we have to modify only 1 place and then everybody in our application all our components will automatically adapt the new color patterns. So this is the benefit of using a theme.

We have already used the pre-built theme in our application. But we can also create the custom theme that gives the unique character to our application. If you want to design the custom theme the first step is to use the color tool on material website. If we head over to this website tool,

Angular Material

Here we can immediately see the impact of colors in our design in User Interface tab. And honestly speaking this looks quite boring. And I’ve selected the colors.

Angular Material

Also we can navigate to different examples through this arrow. Of course this is for mobile apps but it gives you an idea what your theme looks like. One more important thing I wanted to pay attention here is the number on top of this palette.

Angular Material

So remember the number of shades of color that you’ve selected for your primary and secondary color because we’re going to implement the custom theme and there we need to reference these numbers to get this kind of colors we design in our UI. All these colors are predefined in material design guideline. These are not random numbers, they all have name and version.

Before explore custom theme, let’s take an idea of SASS.

SASS

If you have used CSS in the large complex projects you know that CSS quickly gets hard to maintain. This is because the CSS we used today lacks a lot of features and this is where CSS Preprocessor can help. With the help of CSS Preprocessor we use features that don’t exist in CSS yet, so we write CSS code of the future and then this preprocessor convert our code into valid CSS and browsers can understand today.

Angular Material

This is exactly how we use typescript to write javascript. So typescript has a lot of features that we don’t currently have in our javascript.

There are various kinds of CSS Preprocessors out there and each support a slightly different set of features. Angular CLI by default supports SASS which stands for Syntactically Awesome Style Sheet. So let’s see how to use SASS in our Angular applications.

Step 1

Let’s create a new file in the src folder and call it theme.scss. And we need to register this new file in Angular CLI configuration. So open angular.json and here in the styles array add the path to our new sass file.

  1. "styles": [  
  2.   "src/styles.css",  
  3.   "src/theme.scss"  
  4. ]  

Step 2

To see Sass in action, first open the app.component.html and add a couple of headings.

  1. <h1>Heading 1</h1>  
  2. <h2>Heading 2</h2>  

Let’s say we want both our headings to be blue.

Now open theme.scss.

  1. // Traditional approach  
  2. h1 { color: yellow; }  
  3. h2 { color: yellow; }  

The problem with this approach if in the future we decide blue to purple then we have to modify 2 different places. But there is a better way to write this.

  1. h1, h2 { color: yellow; }  

Now we need to change at just one place. This approach really works but is not easy to maintain, I’ve seen many developers spending a lot of their time to maintain the CSS code but most of their code overriding each other. This is where SASS shines.

With SASS we can define variables and give them the value and then reuse that variables in multiple places, exactly like we have variables in other programming languages.

  1. $color: blue;  
  2.   
  3. h1 {  
  4.     color: $color;  
  5. }  
  6.   
  7. h2 {  
  8.     color: $color;  
  9. }  

In the future, if we just change the value $color both heading elements will automatically be updated. Right now if we go into the browser and verify the CSS, it doesn’t show us headings with blue color.

Angular Material

Because changes to angular.json are not visible unless we recompile our application. So back in the terminal execute again,

PS C:\Users\Ami Jan\material-demo\material-demo> ng serve

And now look it is working successfully.

Angular Material

So this is how the magic happens, we give the Sass file to the Sass preprocessor and Sass will replace this color variable with an actual value. So this is how it generates the css.

Now let’s take a look at another feature, here we have an import statement and the beautiful thing about this is we can import another sass file

  1. @import "another.scss";  
  2.   
  3. $color: blue;  
  4.   
  5. h1 {  
  6.     color: $color;  
  7. }  
  8.   
  9. h2 {  
  10.     color: $color;  
  11. }  

With this implementation, Sass will import all the styles of another.scss into this file as if all of the styles are the part of one file.

Now let’s see another powerful feature called mixin. In old classical implementation of css.

  1. .box {  
  2.     border: 1px solid yellow;  
  3.     border-radius: 5px;  
  4.     padding: 10px;  
  5. }  
  6.   
  7. .modern-box {  
  8.     border: 1px solid yellow;  
  9.     border-radius: 5px;  
  10.     padding: 10px;  
  11.     // ..  
  12.     // Some more attributes  
  13.     //  
  14. }  

We need to write the code again and again. But here in Sass, we’ll use the feature mixin. A mixin is like a collection of multiple attributes. So,

  1. @mixin soft-border {  
  2.     border: 1px solid yellow;  
  3.     border-radius: 5px;  
  4.     padding: 10px;  
  5. }  
  6.   
  7. .box {  
  8.     @include soft-border();  
  9. }  
  10.   
  11. .modern-box {  
  12.     @include soft-border();  
  13.     // ..  
  14.     // Some more attributes  
  15.     //  
  16. }  

Now you might think we’re including the soft-border and calling like a function. It is because we can pass the arguments to this function. And of course this is the wonderful thing of Sass.

  1. @mixin soft-border($border-radius) {  
  2.     border: 1px solid yellow;  
  3.     border-radius: $border-radius;  
  4.     padding: 10px;  
  5. }  
  6.   
  7. .box {  
  8.     @include soft-border(5px);  
  9. }  
  10.   
  11. .modern-box {  
  12.     @include soft-border(10px);  
  13.     // ..  
  14.     // Some more attributes  
  15.     //  
  16. }  

See not only can we reuse the certain attributes but we also have the ability to make slight variation.

But this was the very basic introduction to Sass with Angular. And you should have to learn Sass on your own.

Custom Theme

So back to our theme.scss file and first we need to import the sass file that comes with Angular Material. This sass file defines all the color that you saw earlier in the color tool of material.io

  1. // Angular Material Theme  
  2. @import "~@angular/material/_theming";  

We imported Angular material theme, now we need to include course styles that are defined in angular material. These styles give our component, modern and consistent look and feel. For example, you have seen how our input fields look, how our dropdown list looks. All are defined in the mixin called mat-core. So we’ll use that mixin as well here.

  1. // Angular Material Theme  
  2. @import "~@angular/material/_theming";  
  3.   
  4. // include course styles defined in Angular Material  
  5. @include mat-core();  

Now we need to define the custom callers on top of these. So we need to define 3 variables or 3 color palettes (primary, accent, warn)

  1. // Angular Material Theme  
  2. @import "~@angular/material/_theming";  
  3.   
  4. // include course styles defined in Angular Material  
  5. @include mat-core();  
  6.   
  7. $app-primary: mat-palatte($mat-blue, 600);  
  8. $app-accent: mat-palatte($mat-yellow, 700);  
  9. $app-warn: mat-palatte($mat-red);  

To define the variable we prefix the variable name with $ sign. And we use the mat-palatte() to define the variable. If you want to get an idea what is mat-palette function, you can search it in _theming.scss. Here you’ll see its definition with essential and optional parameters definition. So we’re passing $mat-blue color to mat-palatte function with its 600 shades which we select in the material.io color tool. Every color of Angular Material is defined with $mat- prefix with color name in _theming.scss. And a theme is a combination of multiple color palettes.

Here we define another variable. And we have 2 functions for treating a theme (mat-light-theme() or mat-dark-theme()) and depending upon which theme you use the background and foreground colors are going to be different.

  1. $app-theme: mat-light-theme();  

You can find any function arguments by going under the hood (_theming.scss) and watch what parameters any function have. Now we have the theme and we need to use the mixin to add the theme. So,

  1. // Angular Material Theme  
  2. @import "~@angular/material/_theming";  
  3.   
  4. // include course styles defined in Angular Material  
  5. @include mat-core();  
  6.   
  7. $app-primary: mat-palette($mat-blue, 600);  
  8. $app-accent: mat-palette($mat-yellow, 700);  
  9. $app-warn: mat-palette($mat-red);  
  10.   
  11. $app-theme: mat-light-theme($app-primary, $app-accent, $app-warn);  
  12.   
  13. @include angular-material-theme($app-theme);  

Once again we don’t have to memorize all these patterns, we can simply go to angular material website and see this code on the documentation.

Now let’s verify the results and go to app.component.html and add some buttons here.

  1. <button color="primary" mat-raised-button>Button 1</button>  
  2. <button color="accent" mat-raised-button>Button 1</button>  
  3. <button color="warn" mat-raised-button>Button 3</button>  

And now let’s see this in action.

Angular Material

So we can see our button looks different. It is our light theme. Now let’s look at the dark theme.

  1. $app-theme: mat-dark-theme($app-primary, $app-accent, $app-warn);  

Now back in the browser you don’t see any changes because we need to apply the css class to the container of our application. This is where the background color is going to come into effect.

So let’s go to index.html and apply the class.

  1. <body class="mat-app-background">  
  2.   <app-root></app-root>  
  3. </body>  

And now back in the browser. So this is our dark theme with dark background.

Angular Material

Using Angular Materials Typography

So here in the template for app component, we have added a heading and a markup. With this markup we get something like this,

Angular Material

This font is Times New Roman. What if you want to change the font? Font size? Font weight? This is what Typography all about.

So let’s see how to work with typography in Angular Material. So head over to angular material typography. If you open the link you can see a bunch of css classes that we can apply to various elements. So we have got title, subheading, body, caption, button etc. In order for these to work first we need to import Roboto font that is used in Angular Material. Of course we can use a different font but this is the standard look and feel in angular material.

Here we have the link of Roboto font. So just copy the link and go to styles.css and import the font.

  1. @import "https://fonts.googleapis.com/icon?family=Material+Icons";  
  2. @import "https://fonts.googleapis.com/css?family=Roboto:300,400,500";  
  3. @import "~@angular/material/prebuilt-themes/indigo-pink.css";  
  4.   
  5. .mat-tab-body-content {  
  6.     padding-top: 20px;  
  7. }  

Now back in app.component.html we can apply those css classes we saw, on these elements. And all the classes prefixed with mat,

  1. <h1 class="mat-headline">Heading 1</h1>  
  2.   
  3. <p class="mat-body-1">  
  4.   Lorem ipsum dolor sit amet consectetur adipisicing elit. In, optio repellendus et, atque impedit rem quae ea sint enim quia molestias! Quam maxime praesentium quibusdam enim doloribus laudantium quasi sunt.  
  5. </p>  

And now if you open the browser, the font is changed and now it is Roboto font in heading and paragraph as well.

Angular Material

Now applying all these classes to individual elements is very tedious. So the better way is to apply at the global level. Let’s go to index.html

  1. <body class="mat-typography">  
  2.   <app-root></app-root>  
  3. </body>  

So with the help of this class we no longer need to apply the individual classes to each different html element, they become automatically applied on them. So remove the classes on app.component.html and make them plain.

Customizing Typography

Now let’s see how to customize the typography.

Step 1

So first we need to change the roboto font. So go to the fonts.google.com and search for open sans font and add it. So by adding it obviously we added the entire family of open sans. In your application you may want subset of the form instead of entire family and this is purely for the optimization but in our demo, it doesn’t really matter.

Simply copy this url.

https://fonts.googleapis.com/css?family=Open+Sans

Step 2

And now back in styles.css and replace the robot font with Open Sans font.

  1. @import "https://fonts.googleapis.com/icon?family=Material+Icons";  
  2. @import "https://fonts.googleapis.com/css?family=Open+Sans";  
  3. @import "~@angular/material/prebuilt-themes/indigo-pink.css";  
  4.   
  5. .mat-tab-body-content {  
  6.     padding-top: 20px;  
  7. }  

Step 3

Now we need to go to our custom theme and set this font as the new font

  1. $app-typography: mat-typography-config(  
  2.     $font-family: '"Open Sans", "Helvetica Neue", sans-serif'  
  3. );  
  4.   
  5. @include angular-material-typography($app-typography);  

Once again, if you have any confusion about these functions (mat-typography-config(), angular-material-typography()) you can search for them in the _theming.scss file and see what is happening there under the hood, what parameters they need and what is the pattern. 

Always it is a good practice to use 2 or 3 fonts because if the first font is not available on the machine then most of the time Helvetica Neue is there and sans-serif  is mostly present in Windows machines.

Step 4

Now back in the browser. And now the font has been changed to Open Sans.

Step 5

Now let’s change the heading. From (_theming.scss) we’re using this function here again and again to customize the typography.

  1. @function mat-typography-config(  
  2.   $font-family:   'Roboto, "Helvetica Neue", sans-serif',  
  3.   $display-4:     mat-typography-level(112px, 112px, 300),  
  4.   $display-3:     mat-typography-level(56px, 56px, 400),  
  5.   $display-2:     mat-typography-level(45px, 48px, 400),  
  6.   $display-1:     mat-typography-level(34px, 40px, 400),  
  7.   $headline:      mat-typography-level(24px, 32px, 400),  
  8.   $title:         mat-typography-level(20px, 32px, 500),  
  9.   $subheading-2:  mat-typography-level(16px, 28px, 400),  
  10.   $subheading-1:  mat-typography-level(15px, 24px, 400),  
  11.   $body-2:        mat-typography-level(14px, 24px, 500),  
  12.   $body-1:        mat-typography-level(14px, 20px, 400),  
  13.   $caption:       mat-typography-level(12px, 20px, 400),  
  14.   $button:        mat-typography-level(14px, 14px, 500),  
  15.   // Line-height must be unit-less fraction of the font-size.  
  16.   $input:         mat-typography-level(inherit, 1.125, 400)  
  17. )  

So now let’s change the heading and change its argument.

  1. mat-typography-level(fontSize, lineHeight, fontWeight) 

So,

  1. $app-typography: mat-typography-config(  
  2.     $font-family: '"Open Sans", "Helvetica Neue", sans-serif',  
  3.     $headline: mat-typography-level(35px, 32px, 700)  
  4. );  
  5.   
  6. @include angular-material-typography($app-typography);  

This is how we customize our heading. So we can see it is really very easy to customize the typography in Angular Material. Again it is not necessary to memorize everything, all the guidelines are already documented in the documentation.