29.6. Events Can Call Functions

For the user input practice, we set the keyup and click events equal to true. This just checks to see if these events occur. When they do, the input is stored in newMovie, and the page refreshes.

To perform more complicated tasks in response to the user's actions, we can call a function when an event occurs. The syntax for this is:

(event) = "functionName(arguments...)"

Changing the movie list displayed on the web page requires us to modify the movies array in the movie-list.component.ts file. We will do this by creating an addMovie function and linking it to our event handlers.

29.6.1. Modify the HTML

Let's change our code in movie-list.component.html to call the function addMovie and pass the new movie title as the argument.

  1. On lines 7 and 8, replace true with the function call:

     1<div class='movies col-4'>
     2   <h3>Movies to Watch</h3>
     3   <ol>
     4      <li *ngFor ="let movie of movies">{{movie}}</li>
     5   </ol>
     6   <hr>
     7   <input #newMovie (keyup.enter)='addMovie(newMovie.value)' type='text' placeholder="Enter Movie Title Here"/>
     8   <button (click)='addMovie(newMovie.value)'>Add</button>
     9   <p>{{newMovie.value}}</p>
    10</div>
    

    Now when the user taps "Enter" or clicks the "Add" button after typing, the input newMovie.value gets sent to the function.

  2. Since our plan is to use a function to add the new movie to the array, we no longer need the title to appear below the input box. Remove <p>{{newMovie.value}}</p> from line 9.

29.6.2. Define the Function

Open movie-list.component.ts and examine the code:

 1import { Component, OnInit } from '@angular/core';
 2
 3@Component({
 4   selector: 'movie-list',
 5   templateUrl: './movie-list.component.html',
 6   styleUrls: ['./movie-list.component.css']
 7})
 8export class MovieListComponent implements OnInit {
 9   movies = ['Toy Story', 'The Shining', 'Sleepless in Seattle', 'The Martian'];
10
11   constructor() { }
12
13   ngOnInit() {
14   }
15}

The movies array stores the titles displayed on the web page, and we want to update this when the user supplies new information.

  1. Declare a function called addMovie that takes one parameter:

     1export class MovieListComponent implements OnInit {
     2   movies = ['Toy Story', 'The Shining', 'Sleepless in Seattle', 'The Martian'];
     3
     4   constructor() { }
     5
     6   ngOnInit() {
     7   }
     8
     9   addMovie (newTitle: string) {
    10
    11   }
    12}
    

    Notice that we have to declare the data type for the newTitle parameter.

  2. Now add code to push the new title to the movies array:

     1export class MovieListComponent implements OnInit {
     2   movies = ['Toy Story', 'The Shining', 'Sleepless in Seattle', 'The Martian'];
     3
     4   constructor() { }
     5
     6   ngOnInit() {
     7   }
     8
     9   addMovie (newTitle: string) {
    10      this.movies.push(newTitle);
    11   }
    12}
    

    The keyword this is required.

Note

It is a common practice to put constructor and functions like ngOnInit AFTER the variable declarations but BEFORE any custom functions.

Save the changes and then refresh the page. Enter a new title to verify that it appears in the movie list. Your page should look something like:

Updated movie list.

29.6.3. Tidying Up the Display

Notice that after adding a new movie to the list, the text remains in the input box. If we click "Add" multiple times in a row, we would see something like:

Same movie added multiple times.

Let's modify the code to try to prevent this from happening.

29.6.3.1. Clear the Input Box

  1. After the user submits a new title, we can clear the input box by setting its value to be the empty string (''). Open movie-list.component.html and modify the input statement as follows:

    <input #newMovie (keyup.enter)="addMovie(newMovie.value); newMovie.value = ''" type="text" placeholder="Enter Movie Title Here"/>
    

    When keyup.enter occurs, the code calls addMovie. Once control returns from the function, newMovie.value is set equal to '', which clears any text from the input box.

  2. Since the user can also click the "Add" button to submit a title, we need to modify the <button> element as well:

    <button (click)="addMovie(newMovie.value); newMovie.value = ''">Add</button>
    

    Now newMovie.value is set equal to '', when "Enter" or "Add" are used to submit data.

Try It

Refresh the page and verify that the input box gets cleared after each new title.

29.6.3.2. Check for Duplicates

Even though we clear the input box, there is nothing to prevent the user from entering the same movie multiple times. While some fans may want to watch a film twenty times in a row, let's have our code prevent repeats.

Recall that the includes method checks if an array contains a particular element. The method gives us several ways to check for a repeated title. One possibility is:

1addMovie (newTitle: string) {
2   if(!this.movies.includes(newTitle)){
3      this.movies.push(newTitle);
4   }
5}

If the movies array already contains newTitle, then the includes method returns true. The NOT operator (!) flips the result to false, and line 3 is skipped.

Try It

Refresh the page and verify that you cannot enter a duplicate title.

29.6.4. Bonus

To boost your skills, try these optional tasks to enhance your work:

  1. Modify addMovie to reject the empty string as a title.
  2. Use *ngIf to display an error message if the user does not enter a title or submits a title that is already on the list.
  3. Add CSS to change the color of the error message.

The example-solutions branch of the Angular repo shows completed code for the bonus tasks.

29.6.5. Check Your Understanding

Assume that we have an Angular project that presents users with a list of potential pets:

Potential pet list.

Question

Which of the following calls the addPet function when the user clicks on one of the potential pets:

  1. <li>{{pet}}</li>
  2. <li (click) = "true">{{pet}}</li>
  3. <li #addPet (click) = "true">{{pet}}</li>
  4. <li (click) = "addPet(pet)">{{pet}}</li>

Question

When the user moves the mouse over an animal, we want to store its name in the newFriend variable. Which of the following accomplishes this?

  1. <li (mouseover) = "pet.name">{{pet}}</li>
  2. <li #newFriend (mouseover) = "pet.name">{{pet}}</li>
  3. <li (mouseover) = "newFriend = pet.name">{{pet}}</li>
  4. <li (mouseover) = "newFriend">{{pet}}</li>