Angular
Posted By Sebastian

Angular 6 – MEAN Stack Crash Course – Part 4: Completing The User Interface


Subscribe On YouTube

DemoCode

Part 1: Front-end Project Setup And Routing
Part 2: Implementing The Back-end
Part 3: Connecting Front-end To Back-end
Part 4: Completing The User Interface

This is the final part of the MEAN Stack Crash Course series. In this part we’re going to complete the implementation of the Angular 6 front-end application which is part of the MEAN stack sample.

In the first part of this series you’ve learned how to create the Angular 6 front-end application, set up the basic project structure by creating components and configuring the client-side routing.

In the second part we’ve covered the back-end project of the MEAN stack application. We’ve implemented a Node.js / Express server which is connected to a MongoDB database instance. We’ve implemented server end points accepting HTTP request for creating, updating, deleting, and retrieving issue data.

In part three we’ve been implementing an Angular service class in our front-end project which is used to connected to the back-end. Implementing an Angular service for accessing the back-end helps us to keep our code DRY by encapsulating the logic which is needed for accessing the REST back-end in one single class.

Let’s again take a quick look at the MEAN stack overview:

Including Material Design Components

We’ve already started to use Material Design components from the Angular Material library in the first part of this series (like the MatToolbar component). In this part we’re going to continue using Material Design components to build the user interface of the sample application. In order to make all needed Material Design components available the following imports needs to be added in app.module.ts:

import { MatToolbarModule, 
         MatFormFieldModule, 
         MatInputModule, 
         MatOptionModule, 
         MatSelectModule, 
         MatIconModule, 
         MatButtonModule, 
         MatCardModule, 
         MatTableModule, 
         MatDividerModule, 
         MatSnackBarModule } from '@angular/material';

Having added the needed imports we need to make sure to add the modules to the array which is assigned to the imports property of the @NgModule decorator as well:

@NgModule({
  declarations: [
    AppComponent,
    CreateComponent,
    EditComponent,
    ListComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    RouterModule.forRoot(routes),
    HttpClientModule,
    MatToolbarModule,
    MatFormFieldModule,
    MatInputModule,
    MatOptionModule,
    MatSelectModule,
    MatIconModule,
    MatButtonModule,
    MatCardModule,
    MatTableModule,
    MatDividerModule,
    MatSnackBarModule
  ],
  providers: [IssueService],
  bootstrap: [AppComponent]
})

Creating A Data Model

Because we’re dealing with Issue data in all our components we’re introducing the Issue interface in issue.model.ts:

export interface Issue {
    id: String;
    title: String;
    responsible: String;
    description: String;
    severity: String;
    status: String;
}

Implementing ListComponent

Now, let’s start completing the component implementation in our MEAN stack sample application. First, let’s take a look at ListComponent.

Implementing The Component Class

In list.component.ts we need to add the following code:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { MatTableDataSource } from '@angular/material';

import { Issue } from '../../issue.model';
import { IssueService } from '../../issue.service';

@Component({
  selector: 'app-list',
  templateUrl: './list.component.html',
  styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {

  issues: Issue[];
  displayedColumns = ['title', 'responsible', 'severity', 'status', 'actions'];

  constructor(private issueService: IssueService, private router: Router) { }

  ngOnInit() {
    this.fetchIssues();
  }

  fetchIssues() {
    this.issueService
    .getIssues()
    .subscribe((data: Issue[]) => {
      this.issues = data;
      console.log('Data requested ... ');
      console.log(this.issues);
    });
  }

  editIssue(id) {
    this.router.navigate([`/edit/${id}`]);
  }

  deleteIssue(id) {
    this.issueService.deleteIssue(id).subscribe(() => {
      this.fetchIssues();
    });
  }
}

Here we’re making sure that Issue and IssueService are imported and that IssueService is injected into the component by adding a corresponding constructor parameter. Three methods are added to the class:

  • fetchIssue: This method is used to retrieve the complete list of issues to display. The retrieval is done by calling the IssueService method getIssue and subscribing to the Observable which is returned. The array of issues is stored in class member issues.
  • editIssue: The editIssue method is used as the event handler method for the click event of the edit link which is included in the output. In this case the navigate method of the Router service is used to navigate to the edit route. Furthermore editIssue is expecting to get the ID of the issue to edit as a parameter in order to hand it over to the edit route.
  • deleteIssue: The deleteIssue event handler method is connected to the click event of the delete link. The ID of the current issue is passed into the method as a parameter and used to call the deleteIssue service method to delete the issue from the database. Once the issue has been deleted (we’re subscribing to the Observable which is returned from the call of the service method) we’re calling fetchIssue again to make sure that the list of issue is retrieved again from the back-end.

Implementing The Component Template

Next let’s take a look at the corresponding template implementation in list.component.html. The following code needs to be inserted:

<div>
  <br>
  <mat-card>
    <button mat-raised-button color="primary" routerLink="/create">Create New Issue</button>
    <br><br>
    <mat-divider></mat-divider>
    <br>
    <table mat-table [dataSource]="issues">
      <ng-container matColumnDef="title">
        <th mat-header-cell *matHeaderCellDef> Title </th>
        <td mat-cell *matCellDef="let element"> {{element.title}} </td>
      </ng-container>

      <ng-container matColumnDef="responsible">
        <th mat-header-cell *matHeaderCellDef> Responsible </th>
        <td mat-cell *matCellDef="let element"> {{element.responsible}} </td>
      </ng-container>

      <ng-container matColumnDef="severity">
        <th mat-header-cell *matHeaderCellDef> Severity </th>
        <td mat-cell *matCellDef="let element"> {{element.severity}} </td>
      </ng-container>

      <ng-container matColumnDef="status">
        <th mat-header-cell *matHeaderCellDef> Status </th>
        <td mat-cell *matCellDef="let element"> {{element.status}} </td>
      </ng-container>

      <ng-container matColumnDef="actions">
          <th mat-header-cell *matHeaderCellDef class="mat-column-right"> Actions </th>
          <td mat-cell *matCellDef="let element" class="mat-column-right">
            <button mat-button color="primary" (click)="editIssue(element._id)">Edit</button>
            <button mat-button color="warn" (click)="deleteIssue(element._id)">Delete</button>
          </td>
        </ng-container>

      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
    </table>
  </mat-card>
</div>

Here we’re making use of several Material Design components. E.g. the MatTable component is used to display the retrieved issues in a table output. Therefore we’re using the matColumnDef, matCellDef, matRowDef, matHeaderRowDef and matHeaderCellDef to define the table structure.

In the actions columns we’re making sure to include two MatButton components. The click event of the first button is connected to the editIssue event handler method and the click event of the second button is connected to the deleteIssue event handler method.

The output of the component should now similar to what you can see in the following screenshot:

Implementing CreateComponent

Next let’s take a look at the code which needs to be used for CreateComponent.

Implementing The Component Class

In create.component.ts the following code needs to be inserted:

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Router } from '@angular/router';

import { IssueService } from '../../issue.service';

@Component({
  selector: 'app-create',
  templateUrl: './create.component.html',
  styleUrls: ['./create.component.css']
})
export class CreateComponent implements OnInit {

  createForm: FormGroup;

  constructor(private issueService: IssueService, private fb: FormBuilder, private router: Router) {
    this.createForm = this.fb.group({
      title: ['', Validators.required],
      responsible: '',
      description: '',
      severity: ''
    });
  }

  addIssue(title, responsible, description, severity) {
    this.issueService.addIssue(title, responsible, description, severity).subscribe(() => {
      this.router.navigate(['/list']);
    });
  }

  ngOnInit() {
  }

}

Here we’re using Angular’s FormBuilder service to create a a form which can then be used to enter issue data. FormBuilder, FormGroup and Validators is imported from the @angular/forms library.

To make sure that you can use this import statement in create.component.ts you also need to make sure to add ReactiveFormsModule in app.module.ts. Add the following import statement on top of the file:

import { ReactiveFormsModule } from '@angular/forms';

And make sure to add ReactiveFormsModule to the imports array as well.

We’re using CreateComponent’s constructor to use the FormBuilder service to create a form group by using method group and passing in a form configuration object.

Furthermore the addIssue event handler method is implemented which is called when the user clicks on the Save button. In this case the addIssue method from IssueService is used to save the new issue in the database.

Implementing The Component Template

The template code of CreateComponent needs to be inserted in create.component.html. The code can be seen in the following listing:

<div>
  <br>
  <mat-card>
    <section class="mat-typography">
      <h3>Create A New Issue</h3>
    </section>
    <mat-divider></mat-divider>
    <br>
    <form [formGroup]="createForm" class="create-form">
      <mat-form-field class="field-full-width">
        <input matInput placeholder="Issue Title" formControlName="title" #title>

      </mat-form-field>
      <mat-form-field class="field-full-width">
        <input matInput placeholder="Responsible" formControlName="responsible" #responsible>
      </mat-form-field>
      <mat-form-field class="field-full-width">
        <textarea matInput placeholder="Description" formControlName="description" #description></textarea>
      </mat-form-field>
      <mat-form-field>
          <mat-select placeholder="Severity" formControlName="severity" #severity>
            <mat-option value="Low">Low</mat-option>
            <mat-option value="Medium">Medium</mat-option>
            <mat-option value="High">High</mat-option>
          </mat-select>
      </mat-form-field>
      <mat-divider></mat-divider>
      <br><br>
      <button mat-raised-button color="accent" routerLink="/list">Back</button>&nbsp;
      <button type="submit" (click)="addIssue(title.value, responsible.value, description.value, severity.value)" [disabled]="createForm.pristine || createForm.invalid" mat-raised-button color="primary">Save</button>
    </form>
  </mat-card>
</div>

The createForm FormGroup is attached to a <form> element and the corresponding form input controls are added as well. For each input control we need to make sure to

  • add the formControlName attribute and assign the name of the FormGroup element as a string
  • add a template variable

The click event handler of the submit button is bound to the addIssue event handler method. The current values of the input controls are passed into the addIssue call by using the previously assigned template variables of those control elements.

The output of CreateComponent should look like what you can see in the following screenshot:

Implementing EditComponent

Finally, let’s complete the implementation of EditComponent.

Implementing The Component Class

The code which needs to be added in edit.component.ts to complete the implementation of the EditComponent class can be seen in the following:

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

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

import { IssueService } from '../../issue.service';
import { Issue } from '../../issue.model';

@Component({
  selector: 'app-edit',
  templateUrl: './edit.component.html',
  styleUrls: ['./edit.component.css']
})
export class EditComponent implements OnInit {

  id: String;
  issue: any = {};
  updateForm: FormGroup;

  constructor(private issueService: IssueService, private router: Router, private route: ActivatedRoute, private snackBar: MatSnackBar, private fb: FormBuilder) { 
    this.createForm();
  }

  ngOnInit() {
    this.route.params.subscribe(params => {
      this.id = params.id;
      this.issueService.getIssueById(this.id).subscribe(res => {
        this.issue = res;
        this.updateForm.get('title').setValue(this.issue.title);
        this.updateForm.get('responsible').setValue(this.issue.responsible);
        this.updateForm.get('description').setValue(this.issue.description);
        this.updateForm.get('severity').setValue(this.issue.severity);
        this.updateForm.get('status').setValue(this.issue.status);
      });
    });
  }

  createForm() {
    this.updateForm = this.fb.group({
      title: ['', Validators.required ],
      responsible: '',
      description: '',
      severity: '',
      status: ''
    });
  }

  updateIssue(title, responsible, description, severity, status) {
    this.issueService.updateIssue(this.id, title, responsible, description, severity, status).subscribe(() => {
      this.snackBar.open('Issue updated successfully', 'OK', {
        duration: 3000,
      });
    });
  }

}

Again a form is needed and Angular’s FormBuilder service is used to create a new FormGroup. The code which is needed to create the FormGroup is encapsulated in method createForm. This method is then called within the class constructor to make sure that the Form is initiated when the component is created.

Because we want to use this form for editing an existing issue entry we need to set the input field values accordingly. This is done in the ngOnInit lifecycle method. Inside ngOnInit we’re using the IssueService method getIssueById to retrieve the issue which has been passed into the component by using route parameters. One the issue record is available we’re setting updateForm control values to the corresponding issue values.

Furthermore the updateIssue event handler method is added. Every time the user clicks on the Save button of the form this method is invoked. Inside this method we’re calling the updateIssue method of IssueService. If the issue has been updated successfully we’re using the MatSnackBar service to output a confirmation message.

Implementing The Component Template

Last but not least the corresponding template code to be inserted into edit.component.html:

<div>
    <br>
    <mat-card>
      <section class="mat-typography">
          <h3>Update Issue</h3>
      </section>
      <mat-divider></mat-divider>
      <br>
      <form [formGroup]="updateForm" class="edit-form">
        <mat-form-field class="field-full-width">
          <input placeholder="Title" matInput formControlName="title" #title>
        </mat-form-field>
        <mat-form-field class="field-full-width">
          <input placeholder="Responsible" matInput formControlName="responsible" #responsible>
        </mat-form-field>
        <mat-form-field class="field-full-width">
          <textarea placeholder="Description" matInput formControlName="description" #description></textarea>
        </mat-form-field>
        <mat-form-field  class="field-full-width">
            <mat-select placeholder="Severity" formControlName="severity" #severity>
              <mat-option value="Low">Low</mat-option>
              <mat-option value="Medium">Medium</mat-option>
              <mat-option value="High">High</mat-option>
            </mat-select>
        </mat-form-field>
        <mat-form-field class="field-full-width">
            <mat-select placeholder="Status" formControlName="status" #status>
              <mat-option value="Open">Open</mat-option>
              <mat-option value="In Progress">In Progress</mat-option>
              <mat-option value="Done">Done</mat-option>
            </mat-select>
        </mat-form-field>
        <mat-divider></mat-divider>
        <br><br>
        <button mat-raised-button color="accent" routerLink="/list">Back</button>&nbsp;
        <button type="submit" (click)="updateIssue(title.value, responsible.value, description.value, severity.value, status.value)" [disabled]="updateForm.pristine || updateForm.invalid" mat-raised-button color="primary">Save</button>
      </form>
    </mat-card>
</div>

The output should now comply to what you can see in the following screenshot:

Conclusion

In this final part of the Angular 6 – The MEAN Stack Crash Course series we’ve completed the front-end application by fully implementing the previously created components in our Angular 6 project.

By the end of this series you should now have a practical and profound understanding of the MEAN stack and it’s building blocks. Throughout this four part series you’ve learned how to build an Angular 6 CRUD application from scratch with MongoDB, Express, Node.js, and Material Design UI.

ONLINE COURSE: Angular & NodeJS - The MEAN Stack Guide

Check out the great online course Angular & NodeJS – The MEAN Stack Guide with thousands of students already enrolled:

Angular & NodeJS – The MEAN Stack Guide

  • Learn how to connect your Angular Frontend to a NodeJS & Express & MongoDB Backend by building a real Application
  • Connect any Angular Frontend with a NodeJS Backend

  • Use ExpressJS as a NodeJS Framework
  • Improve any Angular (+ NodeJS) application by adding Error Handling
  • Understand how Angular works and how it interacts with Backends

  • Use MongoDB with Mongoose to interact with Data on the Backend

Go To Course

ONLINE COURSE: Angular - The Complete Guide

Check out the great Angular – The Complete Guide with thousands of students already enrolled:

Angular – The Complete Guide

  • This course covers Angular 6
  • Develop modern, complex, responsive and scalable web applications with Angular
  • Use their gained, deep understanding of the Angular  fundamentals to quickly establish themselves as frontend developers
  • Fully understand the architecture behind an Angular application and how to use it
  • Create single-page applications with on of the most modern JavaScript frameworks out there

Go To Course


Using and writing about best practices and latest technologies in web design & development is my passion.