29.8. Studio: Angular, Part 2

At the end of the first mission planner studio, multiple components display data about the mission. Your job is to allow the user to update the mission plan by adding user interaction.

29.8.1. Getting Started

For this studio and the next, you will clone some starter code from GitHub.

  1. Fork the Angular Mission Planner repository.

  2. In the terminal, navigate out of the project folder you used for part 1 and into your root angular_practice directory.

  3. Clone your fork to your computer.

    Warning

    Initializing a new Angular project inside of another one creates version control complications that are best avoided.

    Before running the git clone command in the terminal, make sure you are NOT inside a current Angular project!

  4. Use git status to verify that you are on branch studio-2. If not, use git checkout to switch to it.

  5. Run npm install to download dependencies.

  6. Run ng serve to build and serve the project.

29.8.2. Review the Starter Code

The starter code for this studio is similar to the solution for the first mission planner studio, but with a few notable changes.

29.8.2.1. Editable Mission Name

The mission name can now be edited by clicking on the text, changing the text in the input box, and then updated by clicking save or pressing the enter key. Review the code in src/app/header/header.component.html and src/app/header/header.component.ts to see how this feature was implemented.

Animated gif of mission name being clicked, edited, and then saved.

Example of mission name being edited.

29.8.2.2. Crew Array of Objects

Open src/app/crew/crew.component.ts in VSCode. Notice on line 10 that a crew array is defined. This array of objects will be used to display the crew. Each crew member has a name and firstMission property. If firstMission is true, it means this is the first mission for that person.

 1import { Component, OnInit } from '@angular/core';
 2
 3@Component({
 4   selector: 'app-crew',
 5   templateUrl: './crew.component.html',
 6   styleUrls: ['./crew.component.css']
 7})
 8export class CrewComponent implements OnInit {
 9
10   crew: object[] = [
11      {name: "Eileen Collins", firstMission: false},
12      {name: "Mae Jemison", firstMission: false},
13      {name: "Ellen Ochoa", firstMission: true}
14   ];
15
16   constructor() { }
17
18   ngOnInit() {
19   }
20
21}

29.8.3. Requirements

Note

All of these features only temporarily alter the data. If you refresh the page, the original data will reappear.

29.8.3.1. Edit Rocket Name

The rocket name should be clickable and editable like the mission name. Alter src/app/header/header.component.html and src/app/header/header.component.ts to allow the user to edit the rocket name.

29.8.3.2. Use *ngFor to Display Crew

Replace the static list of <li> tags in src/app/crew/crew.component.html with an *ngFor that loops over the crew array.

Add this code to src/app/crew/crew.component.html.

1<li *ngFor="let member of crew">{{member.name}}</li>

29.8.3.3. Display 1st Mission Status

If a crew member's firstMission property is true, then display the text "- 1st" next to their name.

Example of first mission status appearing next to crew member name.

Example of first mission status being shown.

Add this code right after the member name in src/app/crew/crew.component.html.

1<span *ngIf="member.firstMission">- 1st</span>

29.8.3.4. Add Crew Members

Allow crew members to be added to the list. To create a new crew member, two pieces of information are required:

  1. crew member's name
  2. the first mission status

We will use an input box and a checkbox to collect the data.

Animated gif of crew member being added to list after add button is clicked.

Example of crew member being added.

Add this code to the bottom of src/app/crew/crew.component.html.

1<input #name type="text"/>
2<label>First mission<input #firstMission type="checkbox"/></label>
3<button (click)="add(name.value, firstMission.checked)">Add</button>

Line 1 creates an input that declares the local variable name. Line 2 defines a checkbox that declares the firstMission variable. Line 3 creates a button that, when clicked, sends the new name and checkbox value to the add function. This function adds the new crew member to the roster!

In the src/app/crew/crew.component.ts file, include this code for the add function:

1add(memberName: string, isFirst: boolean) {
2  this.crew.push({name: memberName, firstMission: isFirst});
3}

29.8.3.5. Remove Crew Members

Allow removing of crew members by adding a button next to each person in the crew list. When the remove button is clicked, the remove function in the crew component will be called, which deletes that person from the crew array.

Animated gif of crew member disappearing from the list after the remove button for that item is clicked.

Example of crew member being removed.

Add line 4 to file src/app/crew/crew.component.html. Be sure to put it before the closing </li>, so that the button appears next to each item in the crew list.

1<li *ngFor="let member of crew">
2   {{member.name}}
3   <span *ngIf="member.firstMission">- 1st</span>
4   <button (click)="remove(member)">remove</button>
5</li>

Add the remove function shown below to the crew component in the src/app/crew/crew.component.ts file.

1remove(member: object) {
2  let index = this.crew.indexOf(member);
3  this.crew.splice(index, 1);
4}

29.8.3.6. Edit Crew Members

Finally we are going to allow the user to edit crew members who have already been added.

  1. If the crew member name is clicked, then their name should be replaced with a text input and a save button.

  2. When save is clicked, the input and save button are replaced by the text-only version of the name.

  3. Only one crew member can be edited at a time.

    Animated gif of crew member name being clicked, edited, and then saved.

    Example of crew member name being edited.

We need to add a click event to the member name.

  1. Put {{member.name}} inside of a <span> that has a (click) handler.

  2. Make the <li> in src/app/crew/crew.component.html look like the code below.

    1<li *ngFor="let member of crew">
    2   <span (click)="edit(member)" class="editable-text">{{member.name}}</span>
    3   <span *ngIf="member.firstMission">- 1st</span>
    4   <button (click)="remove(member)">remove</button>
    5</li>
    

We need a way of knowing which crew is being edited.

  1. Add this property to the crew component in file src/app/crew/crew.component.ts. The property memberBeingEdited represents the crew member who is currently being edited.

    memberBeingEdited: object = null;
    
  2. Next add a edit function to the crew component file src/app/crew/crew.component.ts. This function will set a memberBeingEdited variable to be equal to the crew member who was clicked.

    edit(member: object) {
       this.memberBeingEdited = member;
    }
    

Now we need to add an *ngIf that will show the two versions of the member, the display state or the edit state.

  1. In the edit state, an input box with a save button will appear, but for now the input and save won't have any functionality. Make your src/app/crew/crew.component.html file look like the below code.

     1<h3>Crew</h3>
     2<ul>
     3   <li *ngFor="let member of crew">
     4
     5      <span *ngIf="memberBeingEdited !== member; else elseBlock">
     6         <!-- display state of member -->
     7         <span (click)="edit(member)" class="editable-text">{{member.name}}</span>
     8         <span *ngIf="member.firstMission">
     9            - 1st
    10         </span>
    11         <button (click)="remove(member)">remove</button>
    12      </span>
    13
    14      <ng-template #elseBlock>
    15         <!-- edit state of member -->
    16         <input />
    17         <button>save</button>
    18      </ng-template>
    19
    20   </li>
    21</ul>
    22<input #name type="text"/>
    23<label>First mission<input #firstMission type="checkbox"/></label>
    24<button (click)="add(name.value, firstMission.checked)">Add</button>
    

Finally, we are going to make the edit state update the member name when save is clicked.

  1. Update the <input> and <button> tags to look like:

    1<ng-template #elseBlock>
    2   <!-- edit state of member -->
    3   <input #updatedName (keyup.enter)="save(updatedName.value, member)" value="{{member.name}}"/>
    4   <button (click)="save(updatedName.value, member)">save</button>
    5</ng-template>
    

The last step is to add the save function to the crew component. This function will be called when the <button> is clicked or when the enter key is pressed and the <input> has focus.

  1. Add the below save function to the crew component.

    1save(name: string, member: object) {
    2member['name'] = name;
    3this.memberBeingEdited = null;
    4}
    

29.8.4. Bonus Missions

Before starting on any of these bonus features, be sure to commit and push your work.

  1. Don't allow duplicate names to be added to the crew.
  2. Allow user to add equipment.
  3. Allow the user to edit equipment.
  4. Allow the user to remove equipment.
  5. Allow user to add experiments.
  6. Allow the user to edit experiments.
  7. Allow the user to remove experiments.

29.8.5. Sanity Check

Complete code for this studio (without the bonus content) can be found in the studio-2-solution branch of the repository.