View on GitHub

BuildingModernWebApplicationsWorkshop

Lab 6 - Creating Angular components

During this lab you will create components with the Angular CLI to display high scores and add new scores using the Angular Angular Reactive Components. You will learn how to navigate to these components using Angular Routing.

Goals for this lab:

Creating your first component and use it as your homepage

1. Create a high scores component with the Angular CLI

The following command will create a component. Which is going to be used as your homepage:

ng generate component high-scores --module=app

# --module=app selects the AppModule to auto import this generated component

# You can also write the command short-handed:
# ng g c high-scores --module=app

The script created a component folder ./src/app/high-scores containing 4 files:

  1. \high-scores.component.ts

    The typescript file connects with the .html and scss files. This file will also contain all the logic for the component.

  2. \high-scores.component.spec.ts

    With the spec class you are able to unit test your component.

  3. \high-scores.component.html

    This will show your content.

  4. \high-scores.component.scss

    The scss file styles the component.

The Angular CLI command also added your component to the AppModule. You can find this file in ./src/app/app.module.ts

@NgModule({
  declarations: [
    AppComponent,
    // Automatically added component
    HighScoresComponent
    // #end
  ],
  imports: [
    /* ... */
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

2. Set component as homepage in Angular Routing Module

Make the following changes to the AppRoutingModule which is automatically generated by the boiletplate Angular application ./src/app/app-routing.module.ts

const routes: Routes = [
  // Add this route
  {
    path: "",
    component: HighScoresComponent
  }
  // #end
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

Remember to fix the import for the new HighScoresComponent.

The HighScoresComponent component is now the homepage of your Angular application. Run the application to verify that this is correct and fix any errors that may occur.

npm start

You should now see the contents of the component: “high-scores works!”

Creating a component to display a list of high scores

For this chapter you will also use the high-scores component you created in the previous chapter.

1. Create a sub-component

Create a sub-component inside the ./src/app/high-scores component directory using this command:

ng generate component high-scores/high-scores-list --module=app

The script created files inside the ./src/app/high-scores/high-scores-list folder for the high-scores-list component.

2. Create a model to hold high scores

Create a model interface to hold high scores

ng generate interface shared/models/high-score

# short-hand command:
# ng g i shared/models/high-score

The above script generated the following file ./src/app/shared/models/high-score.ts. It is in a shared folder, so it can easily be used throughout the whole application when the application grows larger.

Add the following content to the high-score.ts file:

export interface HighScore {
  nickname: string;
  game: string;
  points: number;
}

Interface versus class

An interface is used because you are only interested in the properties (signature) of a high score. Later you will see that these properties directly map onto the results from the API.

3. Input for high scores list component

The next step is to have the high scores list component receive input to be displayed.

Replace the following TypeScript snippet in ./src/app/high-scores/high-scores-list/high-scores-list.component.ts:

@Component({
  selector: "app-high-scores-list",
  templateUrl: "./high-scores-list.component.html",
  styleUrls: ["./high-scores-list.component.scss"]
})
export class HighScoresListComponent implements OnInit {
  // This property will contain data passed from another component
  @Input()
  highScores: HighScore[] = [];

  // This property makes you able to publish events to the component using this component
  @Output()
  onHighScoreSelected: EventEmitter<HighScore> = new EventEmitter();

  constructor() {}

  ngOnInit() {}

  onHighScoreClicked(highScore: HighScore): void {
    this.onHighScoreSelected.emit(highScore);
  }
}

Again, fix the imports after you have replaced the code.

@Input is used to have other components pass data inside components. @Output makes you able to publish events to the component using this component

4. Display high scores list using Material components

To display the high scores you will make some changes in the file containing the HTML of the component. Open the file located at ./src/app/high-scores/high-scores-list/high-scores-list.component.html

It will currently show an empty list when running the application. In the next chapter you will provide this component with actual high scores. For now, you are going to add some placeholders that will hold the actual data when available.

<mat-list>
  <mat-list-item matRipple *ngFor="let highScore of highScores" (click)="onHighScoreClicked(highScore)">
    <mat-icon matListIcon>dvr</mat-icon>
    <h3 matLine>: </h3>
    <p matLine></p>
  </mat-list-item>
</mat-list>

Connecting high scores and high scores list components

1. Initialize a list of high scores inside the HighScoresComponent

Create a list of high scores in the following file ./src/app/high-scores/high-scores.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: "app-high-scores",
  templateUrl: "./high-scores.component.html",
  styleUrls: ["./high-scores.component.scss"]
})
export class HighScoresComponent implements OnInit {
  public highScores: HighScore[];

  constructor() {}

  ngOnInit() {
    this.highScores = [
      {
        nickname: "LX360",
        game: "Pac-Man",
        points: 96070
      },
      {
        nickname: "LeekGeek",
        game: "Space Invaders",
        points: 7028
      }
    ];
  }

  onHighScoreSelected(highScore: HighScore) {
    // Log the selected highscore to the developer console.
    console.log(`High score selected`, highScore);
  }
}

2. Pass high scores to high-scores-list component

Next, pass the array of high scores to the app-high-scores-list component. Replace the content in the file ./src/app/high-scores/high-scores.component.html with:

<app-high-scores-list [highScores]="highScores" (onHighScoreSelected)="onHighScoreSelected($event)"></app-high-scores-list>

3. Run the application

You should now see the a list of high scores when navigating to the homepage.

npm start

Suggestion

Run the application and validate whether you see the high score being logged to the console when you click on it.

Creating an add score form component using Angular Reactive Components

In this chapter you are going to create a new add score component, navigate to it and display a form using the Angular Reactive Components

1. Create a new add score page

Create a new component, just like you did with the high scores.

ng generate component add-score --module=app

# --module=app selects the AppModule to auto import this generated component

The following component should have been generated: ./src/app/add-score/*

2. Add navigation to the add score page

@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule {}


Create a link to the `add-score` page by adding a `routerLink` to the high-scores-list component `./src/app/high-scores/high-scores-list/high-scores-list.component.html`

```html
<mat-list>
  <!-- ... -->
</mat-list>

<!-- Added link -->
<a mat-button color="primary" [routerLink]="['/add-score']">Submit new Score</a>
<!-- #end -->

You can optionally also create a routerlink in the material toolbar in the file ./src/app/app.component.html by adding a new attribute [routerLink] in the existing <a> tag.

<a mat-icon-button color="white" [routerLink]="['/add-score']">

3. Create a form with Material Input components

Create a new form with Material components in the new add score component ./src/app/add-score.component.html

<form class="add-score-form">
  <mat-form-field class="input-nickname">
    <input matInput formControlName="nickname" required placeholder="Nickname" />
  </mat-form-field>

  <mat-form-field class="input-game">
    <mat-label>Games</mat-label>
    <mat-select formControlName="game">
      <mat-option value="Pacman">Pac-Man</mat-option>
      <mat-option value="Space Invaders">Space Invaders</mat-option>
      <mat-option value="Donkey Kong">Donkey Kong</mat-option>
    </mat-select>
  </mat-form-field>

  <mat-form-field>
    <input matInput formControlName="points" type="number" placeholder="Points" value="0" />
  </mat-form-field>

  <button class="form-button" (click)="submitScore()" mat-raised-button color="primary">
    Submit Score
  </button>
</form>

In this form you see the following:

Run the application and navigate to the form by clicking on the + sign or choosing the Submit new score link.

Let’s apply some style to the component for better spacing of the form elements. Add the following SCSS to ./src/app/add-score/add-score.component.scss:

.add-score-form {
  margin: 16px;
  max-width: 600px;

  .input-nickname {
    width: 100%;
  }

  .input-game {
    margin-right: 20px;
  }

  .form-button {
    display: block;
  }
}

As you can see above the css classes are nested, which means only classes within .add-score-form are affected, for example: .add-score-form .input-nickname in casual CSS.

Check the new look of the form.

4. Use Reactive Forms to add scores

Add the ReactiveFormsModule to the AppModule in file ./src/app/app.module.ts

@NgModule({
  declarations: [
    /* ... */
  ],
  imports: [
    /* ... */
    /* Add this module */
    ReactiveFormsModule
    /* #end */
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Create the form using Angular FormBuilder in file ./src/app/add-score/add-score.component.ts

@Component({
  selector: "app-add-score",
  templateUrl: "./add-score.component.html",
  styleUrls: ["./add-score.component.scss"]
})
export class AddScoreComponent implements OnInit {
  addScoreForm: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.addScoreForm = this.fb.group({
      nickname: ["", Validators.required],
      game: [""],
      points: [0]
    });
  }

  submitScore(): void {
    console.log(this.addScoreForm.value);
  }
}

Also make the following changes to the HTML in file ./src/app/add-score/add-score.component.html to attach the addScoreForm. The properties in the created addScoreForm map to the formControlName in the HTML file.

<!-- add [formGroup]="addScoreForm" to the <form> -->
<form class="add-score-form" [formGroup]="addScoreForm">
<!-- #end -->
  <!-- ... -->

  <!-- Show status of the addScoreForm -->
  <p>
    Form Status: 
  </p>
  <!-- #end -->
</form>

Suggestion:

Run the application and press the button. You should now see the values of the form logged to your developer console. Press F12 in Google Chrome to view the logs. You should also see the form becoming invalid and valid when changing the Nickname field, because it has the required validator on it.

Pro tip:

Go to https://angular.io/guide/reactive-forms to learn more about Angular Reactive Forms and validating forms.

Wrap up

In this lab you have created components and navigated to them using the Angular Router. You used Material components to enhance your application and Angular Reactive Forms to get data from your form. In the next lab you will connect the components to the .NET Core API created in the earlier Labs.

Continue with Lab 7 - Consuming a REST Web API from Angular.