scouting: Enable strict template checking

I kept making mistakes that the compiler wasn't catching. Only when
you run the actual app do you see the errors (in the console).

This patch makes Angular actually check the code inside the template.
This required fixing up some code. Most notably, I switched some
things around so they use data bindings more.

Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
Change-Id: I13c63598bd472e4c19dc27a4f8d99e1da1237c8c
diff --git a/scouting/www/app/app.ts b/scouting/www/app/app.ts
index 1a70b6f..645c224 100644
--- a/scouting/www/app/app.ts
+++ b/scouting/www/app/app.ts
@@ -1,5 +1,7 @@
 import {Component, ElementRef, ViewChild, isDevMode} from '@angular/core';
 
+import {CompLevel} from '@org_frc971/scouting/www/entry';
+
 type Tab =
   | 'MatchList'
   | 'Notes'
@@ -17,7 +19,7 @@
   teamNumber: string;
   matchNumber: number;
   setNumber: number;
-  compLevel: string;
+  compLevel: CompLevel;
 };
 
 @Component({
diff --git a/scouting/www/entry/entry.component.ts b/scouting/www/entry/entry.component.ts
index b1d84b7..d2c922e 100644
--- a/scouting/www/entry/entry.component.ts
+++ b/scouting/www/entry/entry.component.ts
@@ -41,7 +41,7 @@
 
 // TODO(phil): Deduplicate with match_list.component.ts.
 const COMP_LEVELS = ['qm', 'ef', 'qf', 'sf', 'f'] as const;
-type CompLevel = typeof COMP_LEVELS[number];
+export type CompLevel = typeof COMP_LEVELS[number];
 
 // TODO(phil): Deduplicate with match_list.component.ts.
 const COMP_LEVEL_LABELS: Record<CompLevel, string> = {
@@ -141,7 +141,12 @@
   errorMessage: string = '';
   autoPhase: boolean = true;
   mobilityCompleted: boolean = false;
+  // TODO(phil): Come up with a better name here.
   selectedValue = 0;
+  endGameAction: StageType = StageType.kMISSING;
+  noteIsTrapped: boolean = false;
+  endGameSpotlight: boolean = false;
+
   nextTeamNumber = '';
 
   preScouting: boolean = false;
diff --git a/scouting/www/entry/entry.ng.html b/scouting/www/entry/entry.ng.html
index 553fa1e..233d63a 100644
--- a/scouting/www/entry/entry.ng.html
+++ b/scouting/www/entry/entry.ng.html
@@ -91,7 +91,7 @@
           type="radio"
           name="radio-group"
           [value]="i"
-          (change)="selectedValue = $event.target.value"
+          [(ngModel)]="selectedValue"
         />
         {{ i }}
       </label>
@@ -311,21 +311,19 @@
       <div class="button_row">
         <label>
           <input
-            #park
             type="radio"
-            id="option1"
             name="endgameaction"
-            value="park"
+            [value]="StageType.kPARK"
+            [(ngModel)]="endGameAction"
           />
           Park
         </label>
         <label>
           <input
-            #onStage
             type="radio"
-            id="option2"
             name="endgameaction"
-            value="onStage"
+            [value]="StageType.kON_STAGE"
+            [(ngModel)]="endGameAction"
           />
           On Stage
         </label>
@@ -333,42 +331,32 @@
       <div class="button_row">
         <label>
           <input
-            #harmony
             type="radio"
-            id="option3"
             name="endgameaction"
-            value="harmony"
+            [value]="StageType.kHARMONY"
+            [(ngModel)]="endGameAction"
           />
           Harmony
         </label>
         <label>
           <input
-            #na
             type="radio"
-            id="option2"
             name="endgameaction"
-            value="na"
+            [value]="StageType.kMISSING"
+            [(ngModel)]="endGameAction"
           />
           N/A
         </label>
       </div>
       <label>
-        <input
-          #trapNote
-          type="checkbox"
-          id="trapnote"
-          name="trapnote"
-          value="trapNote"
-        />
+        <input type="checkbox" name="trapnote" [(ngModel)]="noteIsTrapped" />
         Trap Note
       </label>
       <label>
         <input
-          #spotlight
           type="checkbox"
-          id="spotlight"
           name="spotlight"
-          value="spotlight"
+          [(ngModel)]="endGameSpotlight"
         />
         Spotlight
       </label>
@@ -394,7 +382,7 @@
       <button
         *ngIf="!autoPhase"
         class="btn btn-info"
-        (click)="changeSectionTo('Review and Submit');  addPenalties(); addAction({type: 'endMatchAction', stageType: (park.checked ? StageType.kPARK : onStage.checked ? StageType.kON_STAGE : harmony.checked ? StageType.kHARMONY : StageType.kMISSING), trapNote: trapNote.checked, spotlight: spotlight.checked});"
+        (click)="changeSectionTo('Review and Submit');  addPenalties(); addAction({type: 'endMatchAction', stageType: endGameAction, trapNote: noteIsTrapped, spotlight: endGameSpotlight});"
       >
         End Match
       </button>
@@ -430,7 +418,7 @@
       </button>
       <button
         class="btn btn-info"
-        (click)="changeSectionTo('Review and Submit');  addPenalties(); addAction({type: 'endMatchAction', stageType: (park.checked ? StageType.kPARK : onStage.checked ? StageType.kON_STAGE : harmony.checked ? StageType.kHARMONY : StageType.kMISSING), trapNote: trapNote.checked, spotlight: spotlight.checked});"
+        (click)="changeSectionTo('Review and Submit');  addPenalties(); addAction({type: 'endMatchAction', stageType: endGameAction, trapNote: noteIsTrapped, spotlight: endGameSpotlight});"
       >
         End Match
       </button>
@@ -439,35 +427,30 @@
   <div *ngSwitchCase="'Review and Submit'" id="Review" class="container-fluid">
     <div class="row">
       <ul id="review_data">
-        <li
-          *ngFor="let action of actionList"
-          [ngValue]="action"
-          style="display: flex"
-        >
+        <li *ngFor="let action of actionList" style="display: flex">
           <div [ngSwitch]="action.type" style="padding: 0px">
             <span *ngSwitchCase="'startMatchAction'">
-              Started match at position {{action.position}}
+              Started match at position {{$any(action).position}}
             </span>
             <span *ngSwitchCase="'pickupNoteAction'">Picked up Note</span>
             <span *ngSwitchCase="'placeNoteAction'">
-              Placed at {{stringifyScoreType(action.scoreType)}}
+              Placed at {{stringifyScoreType($any(action).scoreType)}}
             </span>
             <span *ngSwitchCase="'endAutoPhase'">Ended auto phase</span>
             <span *ngSwitchCase="'endMatchAction'">
-              Ended Match; stageType: {{(action.stageType === 0 ? "kON_STAGE" :
-              action.stageType === 1 ? "kPARK" : action.stageType === 2 ?
-              "kHARMONY" : "kMISSING")}}, trapNote: {{action.trapNote}},
-              spotlight: {{action.spotlight}}
+              Ended Match; stageType:
+              {{stringifyStageType($any(action).stageType)}}, trapNote:
+              {{$any(action).trapNote}}, spotlight: {{$any(action).spotlight}}
             </span>
             <span *ngSwitchCase="'robotDeathAction'">
-              Robot dead: {{action.robotDead}}
+              Robot dead: {{$any(action).robotDead}}
             </span>
             <span *ngSwitchCase="'mobilityAction'">
-              Mobility: {{action.mobility}}
+              Mobility: {{$any(action).mobility}}
             </span>
             <span *ngSwitchDefault>{{action.type}}</span>
             <span *ngSwitchCase="'penaltyAction'">
-              Penalties: {{action.penalties}}
+              Penalties: {{$any(action).penalties}}
             </span>
           </div>
         </li>
diff --git a/scouting/www/match_list/match_list.component.ts b/scouting/www/match_list/match_list.component.ts
index 35165d5..08120b3 100644
--- a/scouting/www/match_list/match_list.component.ts
+++ b/scouting/www/match_list/match_list.component.ts
@@ -9,11 +9,15 @@
 
 import {MatchListRequestor} from '@org_frc971/scouting/www/rpc';
 
+// TODO(phil): Deduplicate with entry.component.ts.
+const COMP_LEVELS = ['qm', 'ef', 'qf', 'sf', 'f'] as const;
+export type CompLevel = typeof COMP_LEVELS[number];
+
 type TeamInMatch = {
   teamNumber: string;
   matchNumber: number;
   setNumber: number;
-  compLevel: string;
+  compLevel: CompLevel;
 };
 
 @Component({
@@ -23,6 +27,7 @@
 })
 export class MatchListComponent implements OnInit {
   @Output() selectedTeamEvent = new EventEmitter<TeamInMatch>();
+
   progressMessage: string = '';
   errorMessage: string = '';
   matchList: Match[] = [];
@@ -30,6 +35,14 @@
 
   constructor(private readonly matchListRequestor: MatchListRequestor) {}
 
+  // Validates that the specified string is a proper comp level.
+  validateCompLevel(compLevel: string): CompLevel {
+    if (COMP_LEVELS.indexOf(compLevel as any) !== -1) {
+      return compLevel as CompLevel;
+    }
+    throw new Error(`Could not parse "${compLevel}" as a valid comp level.`);
+  }
+
   // Returns true if the match is fully scouted. Returns false otherwise.
   matchIsFullyScouted(match: Match): boolean {
     const scouted = match.dataScouted();
diff --git a/scouting/www/match_list/match_list.ng.html b/scouting/www/match_list/match_list.ng.html
index 0ebbe4c..fe26d09 100644
--- a/scouting/www/match_list/match_list.ng.html
+++ b/scouting/www/match_list/match_list.ng.html
@@ -22,7 +22,7 @@
             teamNumber: team.teamNumber,
             matchNumber: match.matchNumber(),
             setNumber: match.setNumber(),
-            compLevel: match.compLevel()
+            compLevel: validateCompLevel(match.compLevel()),
             })"
         class="match-item"
         [ngClass]="team.color"
diff --git a/tsconfig.json b/tsconfig.json
index bd23965..3999b5c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,5 +17,10 @@
   },
   "bazelOptions": {
     "workspaceName": "971-Robot-Code"
+  },
+  "angularCompilerOptions": {
+    "strictInjectionParameters": true,
+    "strictInputAccessModifiers": true,
+    "strictTemplates": true
   }
 }