Assignment #6: Orbit Report

Warning

Please do not attempt this assignment until AFTER your first lesson on Angular. It can be tempting to dive right in, but Angular is a broad topic, and you want to wait to start until you have time to go over the requirements in class.

There are thousands of satellites orbiting the earth. You are tasked with creating a searchable, sortable table of satellites. For the purposes of this assignment, a satellite will be defined as any object purposefully placed into orbit.

Your table will have the following features:

  1. Satellites: Each row in the table contains data on one satellite.
  2. Search form: Filters the results based on matches to the entered text. Pressing enter or clicking the button triggers the search.
  3. Sortable columns: The Name and Type column headers can be clicked, which will sort the table using that property.
  4. Counts: Displays the total number of satellites in the table, as well as the count for each type of satellite.

Your completed assignment should look something like this:

Screenshot of orbit report table.

You can also view gifs of the finished project in the Orbit Report Demo section at the bottom of this page.

Setup

  1. Use ng new to create a new Angular project named orbit-report. Prompt answers: No Routing, Use CSS.

  2. Create a new repository in your GitHub account named orbit-report. Copy the URL for this empty repo.

  3. In the terminal, navigate into your local orbit-report directory and link the Angular project to the GitHub repo:

    $ git remote add origin <remote-url>
    
  4. Finally, commit and push your Angular assignment to GitHub.

Requirements

As you accomplish each task, be sure to commit and push your changes before moving on to the next item.

1) Define and Create Satellites

In JavaScript, TypeScript, and Angular projects, you can create classes to represent entities in the project. For this project you need to create a class named Satellite to represent, you guessed it, a satellite. The Satellite class needs to define the properties needed to accurately represent a satellite.

  1. In terminal go to the orbit-report folder.

  2. Create a class with command $ ng generate class Satellite.

  3. Notice that the new file orbit-report/src/app/satellite.ts was created.

  4. Add these properties to the Satellite class in satellite.ts:

    name: string;
    orbitType: string;
    type: string;
    operational: boolean;
    launchDate: string;
    
  5. Add a constructor to Satellite class.

    1. Constructor signature should be:

      constructor(name: string, type: string, launchDate: string, orbitType: string, operational: boolean)
      
    2. You need to assign the class properties in the constructor.

Now we need to use the Satellite class to create an initial array of Satellite objects.

  1. Define an array named sourceList in app.component.ts.

    1. sourceList: Satellite[];
    2. For this to compile, you must add import { Satellite } from './satellite'; to the top of the file.
  2. In the constructor in app.component.ts set sourceList to be an array of Satellite objects.

    constructor() {
       this.sourceList = [
          new Satellite("SiriusXM", "Communication", "2009-03-21", "LOW", true),
          new Satellite("Cat Scanner", "Imaging", "2012-01-05", "LOW", true),
          new Satellite("Weber Grill", "Space Debris", "1996-03-25", "HIGH", false),
          new Satellite("GPS 938", "Positioning", "2001-11-01", "HIGH", true),
          new Satellite("ISS", "Space Station", "1998-11-20", "LOW", true),
       ];
    }
    
  3. In the terminal run ng serve.

  4. View the app in your browser. At this point you should see the default Angular starter page. If you don't, check the build output and browser console for any errors.

2) Create Orbit List Component

Now that you have an array of Satellite objects, you need to display them. To do that, create a new component named orbit-list.

  1. Generate the orbit-list component in the app folder. If you need a reminder of how to do this, review the Adding a New Component section.
  2. Replace the contents of app.component.html with just one line of code, <app-orbit-list></app-orbit-list>.
  3. View the app in your browser. You should see: orbit-list works!

3) Pass in Satellites to Orbit List Component

The orbit-list component's job is to show a list of satellites. Remember, you declared an array of Satellite objects in app.component.ts named sourceList. In order to pass that array into the orbit-list, you need to learn a new Angular feature named input properties. Here, the term "input" refers to data being sent into the component. Angular input properties are NOT related to HTML input elements.

Currently, app.component.html uses the orbit-list component like so:

<app-orbit-list></app-orbit-list>

To pass the sourceList array into the orbit-list component, you need to learn new syntax.

  1. Modify <app-orbit-list></app-orbit-list> in app.component.html as follows:

    <app-orbit-list [satellites]="sourceList"></app-orbit-list>
    
    1. [satellites] declares a new satellites property on the orbit-list component.
    2. ="sourceList" sets the value of the satellites property to be the sourceList array.
  2. View the app in your browser. You should NOT see the message orbit-list worked! Why?

  3. Open developer tools in your browser and look at the JavaScript console.

    You should see an error message telling you that the orbit-list component does NOT have a satellites property. Note that only the relevant message text has been included below.

    Error: Template parse errors:
    Can't bind to 'satellites' since it isn't a known property of 'app-orbit-list'.
    1. If 'app-orbit-list' is an Angular component and it has 'satellites' input, then verify that it is part of this module.
    

To solve this issue, you need to declare in orbit-list.component.ts that the component has an input property named satellites.

  1. Add the code below just before the constructor in orbit-list.component.ts.

    @Input() satellites: Satellite[];
    

    The @Input() is special Angular syntax that declares that satellites is a property that will be passed into the component via <app-orbit-list [satellites]="sourceList"></app-orbit-list>.

  2. Update the import statements in orbit-list.component.ts to access the Input and Satellite classes.

    1
    2
    import { Component, OnInit, Input } from '@angular/core';
    import { Satellite } from '../satellite';
    
  3. View the app in your browser. You should see orbit-list works! but no satellites yet. That is the next step.

4) Display Table of Satellites

Now that orbit-list has a satellites property that is an array of Satellite objects, you can use that array to build an HTML table with each row being a different satellite.

  1. In orbit-list.component.html use *ngFor to loop over the satellites array. The HTML table you build should look like the following.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <h3>Orbit Report</h3>
    <table>
       <tr class="header-row">
          <th class="sortable">Name</th>
          <th class="sortable">Type</th>
          <th>Operational</th>
          <th>Orbit Type</th>
          <th>Launch Date</th>
       </tr>
       <!-- TODO: put <tr *ngFor=""></tr> here -->
    </table>
    

Next, you need to include CSS that will make your table and application look a little nicer.

  1. Copy the entire contents of the example orbit-list-component.css and put it into your orbit-list-component.css. Leave the sortable class alone, but change the table and warning styles to colors and sizes you find pleasing.

  2. Copy the entire contents of the example app.component.css and put it into your app.component.css.

  3. Copy the <style> tag from the example index.html and paste it into the <head> of your index.html.

    1. Only add in the <style>, do NOT remove any other HTML from your index.html.
  4. View the app in your browser. Your table should look something like:

    Screen shot of browser showing http://localhost:4200 with a table of four satellites.

    Example of application at this point.

5) Fetch Satellite Data

So far, you have used an array declared in app.component.ts as the source of data to display. Your next task is to switch to using a fetch that retrieves json data from a server.

  1. Replace your constructor in AppComponent with the code below.

  2. Implement the three features mentioned in the TODO: comments.

    constructor() {
       this.sourceList = [];
       let satellitesUrl = 'https://handlers.education.launchcode.org/static/satellites.json';
    
       window.fetch(satellitesUrl).then(function(response) {
          response.json().then(function(data) {
    
             let fetchedSatellites = data.satellites;
             // TODO: loop over satellites
             // TODO: create a Satellite object using new Satellite(fetchedSatellites[i].name, fetchedSatellites[i].type, fetchedSatellites[i].launchDate, fetchedSatellites[i].orbitType, fetchedSatellites[i].operational);
             // TODO: add the new Satellite object to sourceList using: this.sourceList.push(satellite);
    
          }.bind(this));
       }.bind(this));
    
    }
    

Note

Explaining the .bind(this) syntax is beyond the scope of this book, but the statements are crucial for this.sourceList.push(satellite) to work properly.

  1. View the app in your browser. Your table should look similar to:

    Screen shot of browser showing http://localhost:4200 with a table of 9 satellites.

    Example of application after switching to fetched data.

6) Highlight Space Debris

You need to make it easier to spot dangerous space debris in the list. Add an Angular attribute directive to accomplish this.

  1. Add a shouldShowWarning method to the Satellite class.

    1. shouldShowWarning returns a boolean and has no parameters.
    2. shouldShowWarning returns true if the satellite type is 'Space Debris', and it returns false otherwise. Note that this check should be case-insensitive.
  2. Use shouldShowWarning to add the warning CSS class to the <td> containing the satellite's type.

    1. For guidance refer to the section on changing styles with attribute directives.
    Screen shot of browser showing http://localhost:4200 with a table of 9 satellites, with Space Debris cell having a red background.

    Example of warning style adding a red background to Space Debris type.

Note

If you prefer, modify the table HTML to make the entire row the warning color.

7) Sorting

Sorting is a useful feature for any table. When a user clicks the "Name" heading, sort the table by the name property. Also, if the user clicks the "Type" heading, then sort the table by the type property.

  1. Add an Angular click handler that calls sort('name') to the Name <th> element. Note that the sorting feature will NOT work until you have completed step 3.

  2. Add an Angular click handler that calls sort('type') to the Type <th> element.

  3. Add a sort method to the OrbitListComponent class. Remember that by convention, the method should come after the constructor and ngOnInit.

    1. The sorting method has been provided below.
    2. To see an example of the sort working, see the Orbit Report Demo below.
    sort(column: string): void {
       // array.sort modifies the array, sorting the items based on the given compare function
       this.satellites.sort(function(a: Satellite, b: Satellite): number {
          if(a[column] < b[column]) {
             return -1;
          } else if (a[column] > b[column]) {
             return 1;
          }
          return 0;
       });
    }
    

Note

The provided sort method contains a new usage of the array.sort method. Previously in the book you used array.sort without passing it a function, see sort function examples. This usage of array.sort uses a compare function, which allows you to control how the objects in the array are sorted. A compare function is needed to sort the array of Satellite objects, because JavaScript does not know how to sort objects, JavaScript needs you to tell it which Satellite object should go before another Satellite object. For more details about the compare function see MDN description of sort using a compare function.

8) Searching

You are doing great! Next you will add a search feature.

  1. Add this HTML <div class="search-form"></div> in app.component.html.

  2. Add an <input> element inside the <div>.

  3. Add an Angular (keyup.enter) handler to the <input> tag that calls search(searchTerm.value). searchTerm is the local variable defined in <input> to store the data entered by the user.

  4. Add a <button> element inside the <div>.

  5. Add an Angular (click) handler to the <button> that also calls search(searchTerm.value).

  6. Add a search method to the AppComponent class. The code for this method is provided below.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    search(searchTerm: string): void {
       let matchingSatellites: Satellite[] = [];
       searchTerm = searchTerm.toLowerCase();
       for(let i=0; i < this.sourceList.length; i++) {
          let name = this.sourceList[i].name.toLowerCase();
          if (name.indexOf(searchTerm) >= 0) {
             matchingSatellites.push(this.sourceList[i]);
          }
       }
       // assign this.displayList to be the the array of matching satellites
       // this will cause Angular to re-make the table, but now only containing matches
       this.displayList = matchingSatellites;
    }
    

Notice the usage of a new variable named displayList. displayList should contain the Satellite objects that the user wants to see. Previously ALL the satellites were displayed, because there was not a search feature. Now the user can perform a search, which means they want to see ONLY the matching results. The sourceList variable contains ALL the Satellite objects. If you removed the Satellite objects from sourceList that didn't match the search term, then the user could never see them again. Instead when the user performs a search, displayList will be populated with only the matching Satellite objects in sourceList. Matching is defined as satellite.name containing the search term.

  1. Add the displayList: Satellite[]; property to the AppComponent class and set displayList = [] in the constructor.

  2. Pass in the displayList to the orbit-list-component.

    <app-orbit-list [satellites]="displayList"></app-orbit-list>
    
  3. View the app in your browser. Why is the table empty when the app loads? What is the value of displayList when the app first loads?

  4. Set displayList to be a copy of sourceList when the app loads.

    1. Add this code after sourceList has been populated by the fetched data in the constructor.

            // make a copy of the sourceList to be shown to the user
            this.displayList = this.sourceList.slice(0);
         }.bind(this));
      }.bind(this));
      
  5. For an example of search working, see Orbit Report Demo.

Bonus Missions

A) Zebra Stripes

Alternate the color for every other row in the table. Choose whichever pair of colors you prefer, but the highlighting for space debris should still be distinct.

Alternating row colors.

B) Counting Satellites

Create a new component that shows the total number of satellites currently displayed in the table. Also, the component should show the number of each type of satellite.

  1. Create an orbit-counts component.

  2. Add styles to orbit-counts.component.css to make your count table complement the list of satellites, or use the CSS provided in this sample file.

  3. Add the orbit-counts component to app.component.html.

  4. Pass in displayList via [satellites]="displayList".

  5. Use the given HTML as a template. Remember to replace the hard-coded counts.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <h3>Satellite Counts:</h3>
    <div class="counts">
       <div>Total: <span>9</span></div>
       <div>Space Debris: <span>1</span></div>
       <div>Communication: <span>2</span></div>
       <div>Probe: <span>2</span></div>
       <div>Positioning: <span>1</span></div>
       <div>Space Station: <span>2</span></div>
       <div>Telescope: <span>1</span></div>
    </div>
    
  6. The rest of the steps are left for you to figure out! Your completed component should look similar to:

    Example of six satellite counts being displayed.

    Example of the seven different satellite counts being displayed.

C) Update the Search Feature

Modify the search feature to find matches using the orbitType and type properties.

If you completed the counting satellites bonus, use an *ngFor to loop over an array of the different types, instead of explicitly writing a <tr> for each satellite type.

Note

You may have already completed this mission, depending on how you accomplished counting the satellites.

Submitting Your Work

In Canvas, open the Orbit Report assignment and click the "Submit" button. An input box will appear.

Copy the URL for your Github repository and paste it into the box, then click "Submit" again.

Orbit Report Demo

Once you complete all of the tasks outlined above, your project should behave something like this:

Sorting the Table

Sorting the table gif.

Searching the Table

Searching the table gif.

Counting Satellites Bonus

Satellite counts component.