Adding Angular Material to Angular app
We built Tour of Heroes tutorial here, but our styling doesn’t look the best. One of the benefits of adding style library to our project is that we have a consistent, well tested styling in our app.
There are many choices for Angular, like Bootstrap, Angular Material, KendoUI. I will add Angular Material, since I like the Google’s material design and this library is quite complete (currently in beta stage).
Getting started
- install NodeJS
npm install -g @angular/cli
git clone https://github.com/jamzi/Angular2Workshop.git
cd Angular2Workshop
npm install
ng serve
This will spin up our Tour of Heroes app, which will be our base for styling. If you open localhost:4200, you should see the app running.
Adding Angular Material
npm install --save @angular/material
npm install --save @angular/animations
Now, once Angular Material and animations are installed, let’s import it under app.module.ts imports.
//app.module.ts
import { MaterialModule } from '@angular/material';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';// other imports
@NgModule({
imports: [
MaterialModule,
BrowserAnimationsModule
],
...
})
export class AppModule { }
The default font for Material design is Roboto, so we can add it to the body element for our app in the styles.css file.
//styles.css
body {
font-family: Roboto, 'Helvetica Neue', sans-serif;
}
We won’t need our component styles anymore, so you can go to next files and delete all styling: app.component.css, dashboard.component.css, hero-detail.component.css, hero-search.component.css and heroes.component.css.
We will be using Material icons and prebuilt Material theme, so put the following link elements to index.html.
//index.html
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="../node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css" rel="stylesheet">
Styling the toolbar + sidenav navigation
First, let’s create a md-sidenav-container for our navigation and make it fullscreen with the attribute. Inside md-sidenav-container create new md-sidenav and assign local variable #start to it.
Next, create new md-nav-list which will hold our two navigation links. Last, add a button with a click event to close the md-sidenav.
//app.component.html
<md-sidenav-container fullscreen>
<md-sidenav #start>
<md-nav-list>
<a md-list-item routerLink="/dashboard">Dashboard</a>
<a md-list-item routerLink="/heroes">Heroes</a>
</md-nav-list>
<hr>
<button md-button (click)="start.close()">CLOSE</button>
</md-sidenav>
<md-toolbar color="primary">
<button md-icon-button (click)="start.open()">
<md-icon>menu</md-icon>
</button> {{ title }}
</md-toolbar>
<div class="main-content">
<router-outlet></router-outlet>
</div>
</md-sidenav-container>
Our title will be placed in the md-toolbar, so create this element with primary color attribute. Inside, put a hamburger icon button with click event to open the sidenav. After the button, display the title.
After the md-toolbar, let’s add new div with a class of main-content (for styling) and put router-outlet inside. This is where we will display our components, when we navigate to them. Our app component styling is done, it already looks a lot nicer.
The main content (where our views will display), should have a little padding, so we add it to the app.component.css.
//app.component.css
.main-content {
padding: 32px;
}
Styling the dashboard
First, let’s style our dashboard, since it is a starting point in our application. We will put top 4 heroes in md-cards and style them a little bit.
//dashboard.component.html
<h3>Top Heroes</h3>
<md-card class="dashboard-card" *ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]">
<h4>{{ hero.name }}</h4>
</md-card>
<app-hero-search></app-hero-search>//dashboard.component.css
.dashboard-card {
width: 300px;
display: inline-block;
margin-right: 15px;
}
Styling the search component
We use search component right on the dashboard, so let’s use some Material Design magic on this component too. We will use md-input for our search input and we need to put it in the md-input-container. To display our search results, we will be using md-nav-list and iterate through md-list-items.
//hero-search.component.html
<h3>Hero Search</h3>
<md-input-container>
<input mdInput #searchBox placeholder="Search for hero name" (keyup)="search(searchBox.value)">
</md-input-container>
<md-nav-list>
<a md-list-item *ngFor="let hero of heroes | async" (click)="gotoDetail(hero)">
{{hero.name}}
</a>
</md-nav-list>
Styling the heroes
Heroes component contains an input for adding a new hero and a list of existing heroes. The input will be similar to the search component one, so md-input inside md-input-container and we will also add a md-raised-button for the add action. We wrap the input into new md-card.
//heroes.component.html
<h2>My Heroes</h2>
<md-card>
<md-input-container>
<input mdInput #heroName placeholder="Hero name">
</md-input-container>
<button md-raised-button (click)="add(heroName.value); heroName.value=''">Add</button>
</md-card>
Under the input, we will add a new md-nav-list of existing heroes. We iterate over heroes and we put hero into md-list-item. Let’s display hero name in md-line span element.
//heroes.component.html
...
<md-nav-list>
<a md-list-item *ngFor="let hero of heroes" (click)="onSelect(hero)">
<span md-line>{{ hero.name }}</span>
<button md-icon-button (click)="delete(hero)">
<md-icon>delete</md-icon>
</button>
</a>
</md-nav-list>
When we click md-line, we will open new md-dialog. Let’s declare a new dialogRef, which is a type of MdDialogRef. We will declare new SelectedHeroDialog component in the same file.
We also need to inject MdDialog in the constructor and modify the onSelect method to open the dialog and pass the hero into SelectedHeroDialog component.
//heroes.component.ts
import { MdDialog, MdDialogRef } from '@angular/material';dialogRef: MdDialogRef<SelectedHeroDialog>;constructor(
private heroService: HeroService,
private router: Router,
private dialog: MdDialog) { }onSelect(hero: Hero): void {
this.dialogRef = this.dialog.open(SelectedHeroDialog);
this.dialogRef.componentInstance.selectedHero = hero;
}
Now, let’s create our SelectedHeroDialog component (under HeroesComponent). We will use inline template where we display our selectedHero and use pipe to transform it to uppercase. We will also add two md-raised-button elements. One will navigate us to details of this hero and second one will clse the dialog box. We use backtics in our template to use multi-line strings (template literals).
//heroes.component.ts
...
@Component({
selector: 'selected-hero-dialog',
template: `
<h2>
{{selectedHero.name | uppercase}} is my hero
</h2>
<button md-raised-button color="primary" (click)="gotoDetail()">View Details</button>
<button md-raised-button (click)="dialogRef.close()">Close dialog</button>`
})
export class SelectedHeroDialog {
selectedHero: any;constructor(
public dialogRef: MdDialogRef<SelectedHeroDialog>,
private router: Router) { }gotoDetail(): void {
this.dialogRef.close();
this.router.navigate(['/detail', this.selectedHero.id]);
}
}
In typescript file, we need to declare selectedHero and inject MdDialogRef and Router. We also wrote a gotoDetail method which navigates us to hero-detail component of selected hero.
We need to declare our new SelectedHeroDialog component in app.module.ts under declarations and entry components.
//app.module.ts
import { SelectedHeroDialog } from './heroes/heroes.component';declarations: [
...
SelectedHeroDialog
],
entryComponents: [
SelectedHeroDialog
]
Styling the hero details
We will be modify existing template in hero-detail.component.html. Let’s display hero id and name in md-input, the id will have a disabled state. We will also modify button and make them md-raised-button and save button will have primary theme color.
//hero-detail.component.html
<div *ngIf="hero">
<md-input-container>
<input mdInput placeholder="Id" [(ngModel)]="hero.id" disabled>
</md-input-container>
<md-input-container>
<input mdInput placeholder="Name" [(ngModel)]="hero.name">
</md-input-container>
</div>
<div>
<button md-raised-button color="primary" (click)="save()">Save</button>
<button md-raised-button (click)="goBack()">Back</button>
</div>