Creating the book page component

Let's create a page for managing book collections.

This page will display all book collections and will allow us to manage them. Moreover, the controller of the page will be aware of the fact that the BookService exists and will use it when modifications need to be made to books/collections. Because of that added responsibility, it should be considered a smart component.

From here on, you'll start noticing more important changes in how we are going to construct the view. Rather than putting all of the elements and logic in the same place, we will instead decompose our view in multiple Angular components, with each responsible for a smaller part.

Still from the book module's folder, let's go ahead and create the page using the CLI:

ng g component pages/book-page

The output should look as follows:

CREATE src/app/book/pages/book-page/book-page.component.html (28 bytes) 
CREATE src/app/book/pages/book-page/book-page.component.spec.ts (643 bytes) 
CREATE src/app/book/pages/book-page/book-page.component.ts (281 bytes) 
CREATE src/app/book/pages/book-page/book-page.component.scss (0 bytes) 
UPDATE src/app/book/book.module.ts (280 bytes) 

As you can see, the CLI has generated the HTML template, the controller, an SASS (with the .scss extension) file for the styles and a file for the unit tests. The CLI has also already registered the page in declarations of the module file.

Now, let's adapt the generated controller (that is, book-page.component.ts) in order to inject BookService:

  1. Add the following import at the top: 
    import {BookService} from '../../services/book.service';.
  2. Add the following parameter to the constructor: private bookService: BookService.

Angular will take care of resolving and injecting this dependency for us.

When a variable is used only within the controller, it's a best practice to declare it as private in order to be sure it cannot be modified from the outside.

Next, we need to declare the bookCollections map that we'll use in the template: public bookCollections: Map<string, MediaCollection<Book>>;.

Of course, you'll also need to add the corresponding imports:

  • import {MediaCollection} from '../../../shared/entities/media-collection.entity';
  • import {Book} from '../../entities/book.entity';
This time, we have declared our variable as public because we'll need to access it from the template of our component.

Another thing that we need to add and instantiate ourselves is FormControl. We will use it in our template to back the form input field used for the creation of a book collection.

In order to add it, we need to do the following:

  1. Add the following import: import { FormControl } from '@angular/forms';
  2. Declare the following class field: public formControl: FormControl;

By using FormControl (which is a part of reactive forms), we'll be able to fully configure the field and its validation rules programmatically. This approach should be preferred in general, as it is more powerful than defining the validation rules through the templates. Also, as we saw in the previous chapter, reactive forms work synchronously, which is far more predictable.

We need to configure our form control during the initialization life cycle phase of Angular. To do so, let's implement the OnInit interface:

  1. Import the interface: import { OnInit } from '@angular/core';
  2. Implement it: ...​ BookPageComponent implements OnInit

Now, implement the ngOnInit method as follows:

ngOnInit() { 
  this.formControl = new FormControl('', Validators.required); 
  this.bookService.reloadBookCollections(); 
  this.bookCollections = this.bookService.bookCollections; 
} 

You'll also need to import the Validators class from the @angular/forms module.

Take a closer look at how we create and configure FormControl. As you can see, we define the default value as '' and we use Validators.required to mark the input as required.

Next, we are going to create a method that will be called when a new book collection needs to be created. We will bind this method to the submit action of the book collection creation form:

createBookCollection(): void { 
  if (this.formControl.valid) { 
    this.bookService.createBookCollection(this.formControl.value); 
    this.formControl.reset(); 
  } 
} 

In this method, the first thing that we do is check the validity of our form control. If it is valid, then we call the createBookCollection method of our book service with the current value of formControl as a parameter. Finally, we reset our formControl instance to have a blank input again.

Next, we'll add some additional methods using the delegate design pattern. For these, we don't need to add specific checks:

removeBookCollection(identifier: string): void { 
  this.bookService.removeBookCollection(identifier); 
} 
 
createBook(book: Book, collectionIdentifier: string): void { 
  this.bookService.createBook(collectionIdentifier, book); 
} 
 
removeBook(collectionIdentifier: string, bookIdentifier: string) { 
  this.bookService.removeBook(collectionIdentifier, bookIdentifier); 
} 
 
reloadBookCollections(): void { 
  this.bookService.reloadBookCollections(); 
} 

Now that our controller is ready, it is time to implement the template of our page. We'll simply reuse the code from our first implementation of MediaMan and adapt it to take advantage of Angular's templating features.

Open index.html, located in the following folder of the code samples: Chapter04/mediaman-v1. Then, copy the HTML code from line 12 (that is, this line: <h2>Book collections</h2>) to line 33 (included) into the book-page.component.html template, replacing what is already there.

Because we will manage and display book collections differently, you can safely remove the following code from the template:

<div id="bookCollections" class="containerGroup"> 
</div> 

The next step that we need to take is to adapt the template to bind its elements to our controller.

First of all, add the following binding to the input field of the newBookCollection form: [formControl]="formControl". This will tie the input to FormControl.

Now, we want to display warnings when required (for example, when the input has been touched and if no value has been defined).

To do this, let's add the following code right underneath the Name input of the newBookCollection form:

<div *ngIf="formControl.invalid && (formControl.dirty || formControl.touched)" class="alert alert-danger"> 
  <span *ngIf="formControl.hasError('required')"> 
    Name is required. 
  </span> 
</div> 

Notice that we can easily adapt our view based on the form control state.

We need to make a few last changes to this first form:

  1. Change the onclick DOM event binding to an Angular click binding: (click).
  2. Associate that binding with the createBookCollection() method of our controller (no need for mediaManController this time).
  3. Change the button's type to submit. This will allow us to send the form using the Enter key easily.

In the second form (that is, the one used to reload the book collections), we need to make the following modifications:

  1. Adapt the input's click event binding to an Angular binding (the same as earlier).
  2. Also, change the button's type to submit.

You can find the completed file at the following location in this book's assets: Chapter08/mediaman-v2/src/app/book/book-page.component.html.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.66.13