Building  Large-Scale Web Applications with Angular
上QQ阅读APP看书,第一时间看更新

Component with inputs

Navigate to the workour-runner folder and generate a boilerplate exercise description component:

ng generate component exercise-description -is

To the generated exercise-description.component.ts file, add the highlighted code:

import { Component, OnInit, Input } from '@angular/core';
... export class ExerciseDescriptionComponent { @Input() description: string; @Input() steps: string; }

The @Input decorator signifies that the component property is available for data binding. Before we dig into the @Input decorator, let's complete the view and integrate it with WorkoutRunnerComponent.

Copy the view definition for exercise description, exercise-description.component.html, from the Git branch checkpoint2.3, in the workout-runner/exercise-description folder. Look at the highlighted HTML for the exercise description:

<div class="card-body">
<div class="card-text">{{description}}</div>
</div> ... <div class="card-text">
{{steps}}
</div>

The preceding interpolation references the input properties of ExerciseDescriptionComponent: description and steps.

The component definition is complete. Now, we just need to reference ExerciseDescriptionComponent in WorkoutRunnerComponent and provide values for description and steps for the ExerciseDescriptionComponent view to render correctly.

Open workout-runner.component.html and update the HTML fragments as highlighted in the following code. Add a new div called description-panel before the exercise-pane div and adjust some styles on the exercise-pane div, as follows:

<div class="row">
    <div id="description-panel" class="col-sm-3">
<abe-exercise-description
[description]="currentExercise.exercise.description"

[steps]="currentExercise.exercise.procedure"></abe-exercise-description>
</div>
<div id="exercise-pane" class="col-sm-6"> ...

If the app is running, the description panel should show up on the left with the relevant exercise details.

WorkoutRunnerComponent was able to use ExerciseDescriptionComponent because it has been declared on WorkoutRunnerModule (see the  workout-runner.module.ts declaration property). The Angular CLI component generator does this work for us.

Look back at the abe-exercise-description declaration in the preceding view. We are referring to the description and steps properties in the same manner as we did with the HTML element properties earlier in the chapter (<img [src]='expression' ...). Simple, intuitive, and very elegant!

The Angular data binding infrastructure makes sure that whenever the currentExercise.exercise.description and currentExercise.exercise.procedure properties on WorkoutRunnerComponent change, the bound properties on ExerciseDescriptionComponent, description, and steps are also updated.

The @Input decoration can take a property alias as a parameter, which means the following: consider a property declaration such as: @Input("myAwesomeProperty") myProperty:string. It can be referenced in the view as follows: <my-component [myAwesomeProperty]="expression"....

The power of the Angular binding infrastructure allows us to use any component property as a bindable property by attaching the @Input decorator (and @Output too) to it. We are not limited to basic data types such as string, number, and boolean; there can be complex objects too, which we will see next as we add the video player:

The  @Input decorator can be applied to complex objects too.

Generate a new component in the workout-runner directory for the video player:

ng generate component video-player -is

Update the generated boilerplate code by copying implementation from video-player.component.ts and video-player.component.html available in the Git branch checkpoint2.3 in the trainer/src/components/workout-runner/video-player folder (GitHub location: http://bit.ly/ng6be-2-3-video-player).

Let's look at the implementation for the video player. Open video-player.component.ts and check out the VideoPlayerComponent class:

export class VideoPlayerComponent implements OnInit, OnChanges { 
  private youtubeUrlPrefix = '//www.youtube.com/embed/'; 
 
  @Input() videos: Array<string>; 
  safeVideoUrls: Array<SafeResourceUrl>; 
 
  constructor(private sanitizer: DomSanitizationService) { } 
 
  ngOnChanges() { 
    this.safeVideoUrls = this.videos ? 
        this.videos 
            .map(v => this.sanitizer.bypassSecurityTrustResourceUrl(this.youtubeUrlPrefix + v)) 
    : this.videos; 
  } 
} 

The videos input property here takes an array of strings (YouTube video codes). While we take the videos array as input, we do not use this array directly in video player view; instead, we transform the input array into a new array of safeVideoUrls and bind it. This can be confirmed by looking at the view implementation:

<div *ngFor="let video of safeVideoUrls"> 
   <iframe width="198" height="132" [src]="video" frameborder="0" allowfullscreen></iframe> 
</div> 

The view also uses a new Angular directive called ngFor to bind to the safeVideoUrls array. The ngFor directive belongs to a class of directives called structural directives. The directive's job is to take an HTML fragment and regenerate it based on the number of elements in the bound collection.

If you are confused about how the ngFor directive works with safeVideoUrls, and why we need to generate safeVideoUrls instead of using the videos input array, wait for a while as we are shortly going to address these queries. But, let's first complete the integration of VideoPlayerComponent with WorkoutRunnerComponent to see the final outcome.

Update the WorkoutRunnerComponent view by adding the component declaration after the exercise-pane div:

<div id="video-panel" class="col-sm-3">
<abe-video-player [videos]="currentExercise.exercise.videos"></abe-video-player>
</div>

The VideoPlayerComponent's videos property binds to the exercise's videos collection.

Start/refresh the app and the video thumbnails should show up on the right.

If you are having a problem with running the code, look at the Git branch checkpoint2.3 for a working version of what we have done thus far. You can also download the snapshot of checkpoint2.3 (a ZIP file) from http://bit.ly/ng6be-checkpoint-2-3. Refer to the README.md file in the trainer folder when setting up the snapshot for the first time.

Now, it's time to go back and look at the parts of the VideoPlayerComponent implementation. We specifically need to understand:

  • How the ngFor directive works
  • Why there is a need to transform the input videos array into safeVideoUrls
  • The significance of the Angular component life cycle event OnChanges (used in the video player)

To start with, it's time to formally introduce ngFor and the class of directives it belongs to: structural directives.