Angular Materials Mat-Icon component
Tags: angular, angularmaterial, typescript, svg
Published: 2019-11-14 18:34:22

### Using local SVG images.
### Blog post #001 
#### Duncan Faulkner - October 2019 

When developing an Angular application with Angular Material, there comes a point when we need to add icons on our components, or buttons etc... Angular Material has the Mat-Icon component for doing this. This component works with web fonts like Font-Awesome for instance, simply by adding the name of the image required and an image is displayed. 

*For example:* 

> `<mat-icon>home</mat-icon>`

**Note:** *Requires Angular application and Angular Material is installed/configured and a reference to a web font like Font-Awesome library is all set up*

But what about if you have custom icons that are not part of a web font? And would like to makes changes to the icon, for example: change the colour on hover or on a specific condition at run time?

In a recent project I had a bespoke set of SVG icons, the Angular web application was to be installed on a server that didn't have access to the internet, so the images had to be local. I wanted to use the Mat-Icon component out of the box _(in an early version of the project I had a custom icon component)_. I still wanted to be able to change the colours of the icons at various stages throughout the application based on certain conditions as well as on hover. This post covers how I achieved that.

### SVG Icons
There are a number of different ways to register an icon with the Mat-Icon component, this post discusses  `addSvgIcon`, the others are `addSvgIconInNamespace, addSvgIconLiteral or addSvgIconLiteralInNamespace` and are all methods of `MatIconRegistry` and I might cover these in more detail in a future post.

## Setting up an Angular project
**Note** *In this post, I'm not going to step through the creation of an Angular application as there are so many already online. Plus I tend to create Nx workspaces for all my Angular projects as I prefer this project layout. I plan to do a blog post on this very soon, for the moment though see [Getting started with Narwhal's Nx Workspaces](https://nx.dev/angular/getting-started/what-is-nx)*.

In the newly created Angular project, create a `shared directory` and add a new file named `material.module.ts`. I like to separate Angular Material imports into their own module, I also create a separate module for other 3rd party components, just makes it easier to import later, especially using the Nx workspace layout and feature folders.

In the material.module.ts 

```typescript
// Material Module example.
// All other Angular Material component imports here
// but the important ones are...
import {MatIconModule, MatIconRegistry} from '@angular/material/icon';

@NgModule({
	declarations: [],
	imports: [
	// Other material imports removed for brevity,
	MatIconModule],
	exports: [
	// Other material exports removed for brevity,
	MatIconModule, MatIconRegistry
	],
	entryComponents: [],
	providers: [MatIconRegistry]
})
export  class  MaterialModule  {}
```
MatIconModule is the module for the component, and the MatIconRegistry is a service to register and display icons. Add a reference to the  `material.module.ts` in the `app.module.ts`. Don't forget to export it as well, otherwise it won't be available and Angular will not know anything about the Angular Material components. 

```typescript 
// Include the material module in app.module.ts
import  {  BrowserModule  }  from  '@angular/platform-browser';
import  {  NgModule  }  from  '@angular/core';
import  {  AppComponent  }  from  './app.component';
import  {  BrowserAnimationsModule  }  from  '@angular/platform-browser/animations';
import  {  MaterialModule  }  from  './shared/material.module';

@NgModule({
declarations:  [AppComponent],
imports:  [BrowserModule,  BrowserAnimationsModule,  MaterialModule],
providers:  [],
bootstrap:  [AppComponent]
})
export  class  AppModule  {}
```
Now we have the Angular Material components set up and configured we need to register the icons, before we can use them. For the moment we are just going to add these to the `app.component.ts` to get up and running, we'll look at a better method later on.

```typescript
// First Example
@Component({
	selector: 'app-root',
	templateUrl: './app.component.html',
	styleUrls: ['./app.component.scss']
})
export class AppComponent {
	constructor (
		private matIconRegistry: MatIconRegistery
	){
	this.matIconRegistry.addSvgIcon('home','assets/img/icon/home.svg');
	this.matIconRegistry.addSvgIcon('add','assets/img/icon/add.svg');
	}
	// or we could do this, and chain the addsvgIcon methods.
	// we'll use this method going forward in this post
	// {
	// 		this.matIconRegistry.addSvgIcon('home','assets/img/icon/home.svg')
	// 		.addSvgIcon('add','assets/img/icon/add.svg');
	// }
}
```
Add this to the `app.component.html` page, we'll discuss this in more detail in a bit.
```html
<!-- First HTML example -->
<div>
	<mat-icon svgIcon="home"></mat-icon>
	<mat-icon svgIcon="add"></mat-icon>
</div>
```

At this point we are not going to see much in the browser as we have an issue with the image URL. If you open the browsers console section, you will see the following error:

> `Error: unsafe value used in a resource URL context`. 

So what does this error mean? A brief explanation from the Angular Material website says:

> To prevent an Cross Site Scripting (XSS), SVG URL's and HTML strings passed to MatIconRegistry must be marked as trusted by the Angular's DomSanitizer service. Icons are fetched via XmlHttpRequest and must have their URL's on the same domain as the containing page or configured to allow cross-domain access.

So lets add the DomSanitizer and fix this issue.

```typescript
// Second Example - with the DomSanitizer
@Component({
	selector: 'app-root',
	templateUrl: './app.component.html',
	styleUrls: ['./app.component.scss']
})
export class AppComponent {
	constructor (
		private domSanitizer: DomSanitizer, 
		private matIconRegistry: MatIconRegistery
	){
	this.matIconRegistry
	.addSvgIcon('home',this.domSanitizer.bypassSecurityTrustResourceUrl('assets/img/icon/home.svg')
	.addSvgIcon('add',this.domSanitizer.bypassSecurityTrustResourceUrl('assets/img/icon/add.svg')
	// add other icons here....;
	}
}
```
The call to `bypassSecurityTrustResourceUrl` takes a URL as a parameter and sanitizes it so that an attacker cannot inject a 'JavaScript:` URL for example, see official documentation on [DomSanitizer](https://angular.io/api/platform-browser/DomSanitizer#description). 
Now that we have this in place we should now see two icons in the browser.

If we have a lot of icons to add this means lots of typing and lots of repetitive code, so lets refactor this some more. Start by removing all of this code (including the constructor) from the app.component.ts. This shouldn't really be in the app.component.ts file. Lets create another new module in the shared directory and call it `icon.module.ts` and then add the following:

```typescript
// Third Example - icon module
import  {  NgModule  }  from  '@angular/core';
import  {  DomSanitizer,  SafeResourceUrl  }  from  '@angular/platform-browser';
import  {  MaterialModule  }  from  '../shared/material.module';
import  {  MatIconRegistry  }  from  '@angular/material/icon';
@NgModule({
declarations:  [],
imports:  [MaterialModule],
exports:  [],
providers:  []
})
export  class  IconModule  {
private  path:  string  =  '../../assets/images'; // change this 
constructor(
private  domSanitizer:  DomSanitizer,
public  matIconRegistry:  MatIconRegistry
)  {
this.matIconRegistry
.addSvgIcon('home',  this.setPath(`${this.path}/home.svg`))
.addSvgIcon('add',  this.setPath(`${this.path}/file-plus.svg`));
}
private  setPath(url:  string):  SafeResourceUrl  {
	return  this.domSanitizer.bypassSecurityTrustResourceUrl(url);
	}
}
```
Overall, that's not too bad, we are only writing out the domSanitizer code once in the `private` method but more importantly, all the code is out of the `app.component.ts` file and is now a self contained module. If there are a lot of icons to add then this file will get a bit long, but the typing has gotten shorter (well, a little shorter at least). You could change the `constructor`to iterate through a `.json` file of image names, the path wouldn't change and could be a `const`, which would only mean maintaining a `.json` file with any new images. I might look at doing that in a follow up post.

**Note:** Don't forget to add this new icon.module.ts to the app.module.ts otherwise it won't work.

## Using the Mat-Icon component
So how do we use the mat-icon component? As seen earlier in this post we add the following code to our app.components.html page.

```html
<!-- First HTML example -->
<div>
	<mat-icon svgIcon="home"></mat-icon>
	<mat-icon svgIcon="add"></mat-icon>
</div>
```
This is a very simple example is showing how to put a `home` and an `add` icon on a page/component. This is not too dissimilar to how we would use this component with web fonts, but we are now using the `svgIcon` input property. The value we give to this input is the first parameter used in our call to register the `.addSvgIcon('home', ...)` in this case `home`.

Now we have an icon in place, how do we change the colour of the icon when someone hovers over it for example. 

## Change the  icon colour

**A Home icon**.
Example icon copy this into a file with the SVG extension.
```svg
<svg version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" fill="#00FFFF" /></svg>
```
In the above XML, I've removed all the namespaces etc... The main part here is the `fill="#00FFFF"` this sets the colour for the image, in this case to AquaMarine. 

If the `fill="#00..."` property is not there and you want a different colour to the default black, then you can add it to the path as above, it's optional.
 
I usually add the `fill` property and set it to white and then change them in SCSS as and when required (but because this example had a white the background I used another colour). 

```html
!<-- Second HTML example -->
<div>
	!<-- other code omitted for brevity -->	
	<a mat-button>
		<mat-icon svgIcon="home" class="btn-icon"></mat-icon>
	</a>
</div>
```
Add a class to the mat-icon html tag, as above and then add the SCSS, for the class. So when a user hovers over the button the colour of the icon changes.

```scss
.btn-icon  {
	&:hover  {
		path {
			fill:  rgba(#00ffff,  1);
			}
		}
	}
```
**Note** One thing I did forget to add to the app.component.ts above, was the following line: 
`encapsulation:  ViewEncapsulation.None`, without this then the hover affect doesn't work. 

```typescript
@Component({
selector:  'mat-icon-demo-root',
templateUrl:  './app.component.html',
styleUrls:  ['./app.component.scss'],
encapsulation:  ViewEncapsulation.None,
})
export  class  AppComponent  {
	title  =  'mat-icon-demo';
}
```
Enjoy! 

There is a github repo for this blog post at:
[https://github.com/Anglebrackets-io/mat-icon-demo](https://github.com/Anglebrackets-io/mat-icon-demo)