Merge "scouting: Enable strict template checking"
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
   }
 }