Photo Gallery Part 3 - UI Design
Coding Tutorial - Building our User Interface of our Photo Gallery app. Leveraging Angular and Material Design to build our app.

Welcome back to part 3. This time we are going to work on creating the UI for our photo gallery. If you are not caught up, make sure you check out part 1 and part 2.
House Keeping
Changing the Angular Build Directory
Angular will automatically put your finished app in a dist folder after you build it. This is normally fine, but since our Node server is going to look inside the public folder for the frontend, we need to change Angular's default build directory. We do this by editing the .angular-cli.json file. Change the value for outDir from './dist' to '../public'.
Custom npm scripts
Next we are going to create a customer npm script. Normally you would use the 'ng serve' command to start a development server for Angular. Since we want our backend and frontend to work together, we are going to use the 'ng build -w' command instead. This will build our angular frontend, and rebuild it after we make changes to it.
Inside package.json, your scripts section should not look like .
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js",
"ng-start": "cd angular-src && ng build -w",
"test": "echo \"Error: no test specified\" && exit 1"
},
Material Design Modules
Let's start our design journey with adding the needed modules we will need from the material design.
Lets open our material.module.ts file, and make the following changes to it.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
MatTableModule,
MatInputModule,
MatDialogModule,
MatToolbarModule,
MatCardModule,
MatIconModule,
MatButtonModule,
MatSidenavModule,
MatListModule
} from '@angular/material';
@NgModule({
imports: [
MatTableModule,
MatInputModule,
MatDialogModule,
MatToolbarModule,
MatCardModule,
MatIconModule,
MatButtonModule,
MatSidenavModule,
MatListModule
],
exports: [
MatTableModule,
MatInputModule,
MatDialogModule,
MatToolbarModule,
MatCardModule,
MatIconModule,
MatButtonModule,
MatSidenavModule,
MatListModule
]
})
export class MaterialModule {}
Don't worry about all of these for now, we will highlight each component as we use them in our code.
Components
Now we are going to create all of the Angular components we are going to be coding. You could create them one by one, but instead we will create them all at the same time. I like keeping our folder structure tidy, so we are going to place all of our components inside a components folder under angular-src/src/app/. I am using the short-form of the command, the full command is ng generate component
ng g c components/home
ng g c components/navbar
ng g c components/navbar/sidenav
ng g c components/gallery
ng g c components/add-photo
ng g c components/add-album
Application Routing
Inside the app.component.html file, replace the contents with our sidenav
<app-sidenav></app-sidenav>
Next, lets create the routing for our application. For right now, it is going to be very basic.
In angular-src/src/app/app-routing.module.ts, add the following to the routes variable at the top.
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'gallery/:id', component: GalleryComponent }
];
This tells our application that our default route(/) will go to our Home component, and that we have gallery route which will go to our Gallery component, and provide the gallery's ID.
Sidenav and Navbar
You will commonly see that you add the navbar and router outlet to your app.component.html file. However, in this case we are going to add our sidebar as the root element in our app.component.html and then add all of our content inside there. Let's get started
App Component
Inside app.component.html, replace the content with
<app-sidenav></app-sidenav>
Sidenav
Next we are going to create the sidenav, which is where we will put our navbar and router outlet.
sidenav.component.html
<mat-sidenav-container>
<mat-sidenav #sidenav mode="side" [opened]="state" [fixedInViewport]="true">
<mat-nav-list *ngIf="albumsObservable | async; let albums; else loading">
<button mat-raised-button color="primary" (click)="addAlbumClick()"><mat-icon>add</mat-icon> Add Album</button>
<hr>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content style="background-color: #fff;">
<app-navbar></app-navbar>
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>
<ng-template #loading>Loading ...</ng-template>
Navbar
Lastly, we are going to create the navbar. For now, we will keep it simple with just a Home button and a way of opening the sidenav.
navbar.component.html
<mat-toolbar color="primary">
<mat-toolbar-row>
<button mat-icon-button (click)="toggleSideNavClick()"><mat-icon>menu</mat-icon></button>
<span>Photo Gallery</span>
<button mat-button [routerLinkActive]="['active']" [routerLinkActiveOptions]="{extended: true}" [routerLink]="['']"><mat-icon>home</mat-icon> Home</button>
</mat-toolbar-row>
We need to add some logic to our typescript, which will allow the sidenav to open and close.
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
albumName = '';
constructor(private router: Router) { }
ngOnInit() {
}
toggleSideNavClick() {
this.navService.toggleSidebar();
}
}
You can see that we have created the toggleSideNavClick function, but it is making a call to some service which we haven't created yet. Although we could have done all of this code from within our components, I thought it would be fun to create a service to do it for us.
Navbar Service
We'll start by creating the service with the Angular CLI
ng g service services/navbar
In our service, we are going to create a behaviorsubject and use it as an observable. This allows us to change the state of the sienav and have our components reflect the changes in the browser.
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class NavbarService {
private state = false;
private sidebarOpen = new BehaviorSubject<boolean>(false);
sidebarState = this.sidebarOpen.asObservable();
constructor() { }
toggleSidebar() {
if (this.state) {
this.sidebarOpen.next(false);
} else {
this.sidebarOpen.next(true);
}
this.state = !this.state;
}
private openSidebar() {
this.state = true;
this.sidebarOpen.next(true);
}
private closeSidebar() {
this.state = false;
this.sidebarOpen.next(false);
}
}
Don't forget to add our new service to the provider section app.module.ts.
Now, every time we click on the menu icon, it will change the state of our service to either true or false.
If you look at in your browser, you will see that the sidebar still doesn't open. This is because we have to tell our sidebar to look in our service to get it's state.
Open sidenav.component.ts and add the following
import { NavbarService } from './../../../services/navbar.service';
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { MatSidenav } from '@angular/material/sidenav';
@Component({
selector: 'app-sidenav',
templateUrl: './sidenav.component.html',
styleUrls: ['./sidenav.component.css']
})
export class SidenavComponent implements OnInit {
@ViewChild('sidenav') sidenav: MatSidenav;
state: boolean;
constructor(public router: Router, private navbarService: NavbarService) { }
ngOnInit() {
this.navbarService.sidebarState.subscribe(res => this.state = res);
}
}
By adding this, we are looking up the state of the sidebar inside our service and assigning it to our state variable. If you check the sidenav HTML, you will see the [open]="state", which is how it knows to change from open to close. Since we are using an observable, the sidenav will open/close when it receives an update from our service.
Testing
To run our app, we will want to open 2 terminals. Navigate to your project's folder and execute the following commands
Start the NodeJS Server (terminal 1)
npm run dev
Start Angular (terminal 2)
npm run ng-start
Once both have started, open your web browser and go to localhost:3000 to see what our app looks like.
One thing you will notice is that when you open the sidenav, you will see 'Loading...'. This is fine for the time being. The sidenav is going to by dynamic and will show the list of photo albums we have. Next time, we will add the needed services to load this data.
Concussion
I think that is enough for this time. We have create the base of how our app will look. Using Angular Material components, we were able to create a sidenav and navbar to our application. We also created a service and used a behaviorsubject and observables to move the state of the sidebar between our sidebar and navbar component.
Next time we will focus on creating our services and making the needed API calls to our server.
As always, all of the code can be found on GitHub
Make sure you subscribe, so you get first access to part 4 of our Photo Album.