[results feed] Add angular router to results feed

This enables the app to serve multiple pages, with different URLs,
from the same angular app. The try results page for Gerrit CLs is
just a placeholder, and will be implemented in a follow-up CL.

Change-Id: I9c1332eee971d7be4cf16c432fbc45941d20d11f
Reviewed-on: https://dart-review.googlesource.com/c/dart_ci/+/120700
Reviewed-by: Alexander Thomas <athom@google.com>
diff --git a/results_feed/lib/src/app_component.dart b/results_feed/lib/src/app_component.dart
index e16ca7d..f60048c 100644
--- a/results_feed/lib/src/app_component.dart
+++ b/results_feed/lib/src/app_component.dart
@@ -14,6 +14,7 @@
 import 'package:angular_components/material_dialog/material_dialog.dart';
 import 'package:angular_components/material_icon/material_icon.dart';
 import 'package:angular_components/material_toggle/material_toggle.dart';
+import 'package:angular_router/angular_router.dart';
 import 'package:dart_results_feed/src/filter_component.dart';
 
 import 'commit_component.dart';
@@ -38,7 +39,6 @@
       ModalComponent
     ],
     providers: [
-      ClassProvider(FirestoreService),
       ClassProvider(FilterService),
       ClassProvider(BuildService),
       overlayBindings
@@ -48,7 +48,7 @@
       'package:angular_components/app_layout/layout.scss.css',
       'app_component.css'
     ])
-class AppComponent implements OnInit {
+class AppComponent implements OnInit, CanReuse {
   String title = 'Results Feed (Angular Dart)';
 
   Map<IntRange, ChangeGroup> changeGroups = SplayTreeMap(reverse);
@@ -83,6 +83,12 @@
     IntersectionObserver(infiniteScrollCallback).observe(infiniteScroll);
   }
 
+  /// We do not want to create a new AppComponent object each time the
+  /// route changes, which includes changes to the fragment.
+  /// It is always acceptable to use the same AppComponent.
+  @override
+  Future<bool> canReuse(_, __) async => true;
+
   void infiniteScrollCallback(
       List entries, IntersectionObserver observer) async {
     infiniteScrollVisibleRatio = entries[0].intersectionRatio;
diff --git a/results_feed/lib/src/filter_service.dart b/results_feed/lib/src/filter_service.dart
index 160c877..d0d0f51 100644
--- a/results_feed/lib/src/filter_service.dart
+++ b/results_feed/lib/src/filter_service.dart
@@ -45,11 +45,7 @@
       ].join('&');
 
   void updateUrl() {
-    final newFragment = fragment();
-    Uri old = Uri.parse(window.location.href);
-    if (old.fragment != newFragment) {
-      window.location.replace(old.replace(fragment: newFragment).toString());
-    }
+    window.location.hash = fragment();
   }
 
   factory Filter.fromUrl() {
diff --git a/results_feed/lib/src/route_paths.dart b/results_feed/lib/src/route_paths.dart
new file mode 100644
index 0000000..b30947b
--- /dev/null
+++ b/results_feed/lib/src/route_paths.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:angular_router/angular_router.dart';
+
+final feedPath = RoutePath(path: '/feed');
+
+final clPath = RoutePath(path: '/cl/:cl/:patch');
+
+final rootPath = RoutePath(path: '/', useAsDefault: true);
diff --git a/results_feed/lib/src/routing_wrapper_component.dart b/results_feed/lib/src/routing_wrapper_component.dart
new file mode 100644
index 0000000..2bb18e6
--- /dev/null
+++ b/results_feed/lib/src/routing_wrapper_component.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:angular/angular.dart';
+import 'package:angular_router/angular_router.dart';
+
+import 'route_paths.dart';
+import 'app_component.template.dart';
+import 'try_results_component.template.dart';
+import 'firestore_service.dart';
+
+@Component(
+    selector: 'routing-wrapper',
+    directives: [routerDirectives],
+    providers: [ClassProvider(FirestoreService)],
+    template: '''
+      <router-outlet [routes]="routes"></router-outlet>
+    ''')
+class RoutingWrapperComponent {
+  final List<RouteDefinition> routes = [
+    RouteDefinition(routePath: feedPath, component: AppComponentNgFactory),
+    RouteDefinition(routePath: clPath, component: TryResultsComponentNgFactory),
+    RouteDefinition(
+        routePath: rootPath,
+        component: AppComponentNgFactory,
+        useAsDefault: true),
+  ];
+}
diff --git a/results_feed/lib/src/try_results_component.dart b/results_feed/lib/src/try_results_component.dart
new file mode 100644
index 0000000..1dcc529
--- /dev/null
+++ b/results_feed/lib/src/try_results_component.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:angular/angular.dart';
+import 'package:angular_router/angular_router.dart';
+
+import 'firestore_service.dart';
+import 'route_paths.dart';
+
+@Component(
+    selector: 'try-results',
+    directives: [coreDirectives, routerDirectives],
+    template: '''
+    <h1>Placeholder for try results page</h1>
+    change(CL) number: {{change}}<br>
+    patchset number: {{patch}}<br>
+    Link to results feed page:
+    <a [routerLink]="feedLink">Results feed</a>
+   ''')
+class TryResultsComponent implements OnActivate {
+  FirestoreService firestoreService;
+
+  TryResultsComponent(this.firestoreService);
+
+  get feedLink => feedPath.toUrl();
+
+  int patch;
+  int change;
+
+  @override
+  void onActivate(_, RouterState current) {
+    final changeParam = current.parameters['cl'];
+    change = changeParam == null ? null : int.parse(changeParam);
+    final patchParam = current.parameters['patch'];
+    patch = patchParam == null ? null : int.parse(patchParam);
+  }
+}
diff --git a/results_feed/pubspec.lock b/results_feed/pubspec.lock
index 33755b1..1db68f7 100644
--- a/results_feed/pubspec.lock
+++ b/results_feed/pubspec.lock
@@ -50,6 +50,13 @@
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.1.4"
+  angular_router:
+    dependency: "direct main"
+    description:
+      name: angular_router
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.0-alpha+24"
   angular_test:
     dependency: "direct dev"
     description:
@@ -77,7 +84,7 @@
       name: async
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.3.0"
+    version: "2.4.0"
   bazel_worker:
     dependency: transitive
     description:
@@ -119,14 +126,14 @@
       name: build_modules
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.5.0"
+    version: "2.6.2"
   build_resolvers:
     dependency: transitive
     description:
       name: build_resolvers
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.0.8"
+    version: "1.1.1"
   build_runner:
     dependency: "direct dev"
     description:
@@ -154,7 +161,7 @@
       name: build_web_compilers
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.4.1"
+    version: "2.6.2"
   built_collection:
     dependency: transitive
     description:
@@ -294,7 +301,7 @@
       name: html
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.14.0+2"
+    version: "0.14.0+3"
   http:
     dependency: "direct main"
     description:
@@ -469,7 +476,7 @@
       name: sass
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.22.12"
+    version: "1.23.0"
   sass_builder:
     dependency: "direct dev"
     description:
@@ -525,7 +532,7 @@
       name: source_gen
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.9.4+4"
+    version: "0.9.4+5"
   source_map_stack_trace:
     dependency: transitive
     description:
@@ -588,7 +595,7 @@
       name: test
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.6.11"
+    version: "1.8.0"
   test_api:
     dependency: transitive
     description:
@@ -602,7 +609,7 @@
       name: test_core
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.2.9+2"
+    version: "0.2.10"
   timing:
     dependency: transitive
     description:
@@ -616,7 +623,7 @@
       name: tuple
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.0.2"
+    version: "1.0.3"
   typed_data:
     dependency: transitive
     description:
@@ -630,7 +637,7 @@
       name: vm_service
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.0.0"
+    version: "2.1.1"
   watcher:
     dependency: transitive
     description:
@@ -653,4 +660,4 @@
     source: hosted
     version: "2.2.0"
 sdks:
-  dart: ">=2.5.0-dev.1.0 <2.7.0"
+  dart: ">=2.5.0 <2.7.0"
diff --git a/results_feed/pubspec.yaml b/results_feed/pubspec.yaml
index fe20dbb..e740fc3 100644
--- a/results_feed/pubspec.yaml
+++ b/results_feed/pubspec.yaml
@@ -9,6 +9,7 @@
   angular: ^6.0.0-alpha
   angular_components: '>=0.13.0'
   angular_forms: ^2.0.0-alpha
+  angular_router: ^2.0.0-alpha+22
   firebase: ^5.0.0
   googleapis: '>=0.53.0'
   googleapis_auth: ^0.2.7
diff --git a/results_feed/web/index.html b/results_feed/web/index.html
index b05b8f3..c0c6a1b 100644
--- a/results_feed/web/index.html
+++ b/results_feed/web/index.html
@@ -2,7 +2,7 @@
 <html>
 
 <head>
-  <base href="/" />
+  <base href="/">
   <script src="https://www.gstatic.com/firebasejs/5.9.2/firebase-app.js"></script>
   <script src="https://www.gstatic.com/firebasejs/5.9.2/firebase-auth.js"></script>
   <script src="https://www.gstatic.com/firebasejs/5.9.2/firebase-firestore.js"></script>
@@ -17,7 +17,7 @@
 </head>
 
 <body>
-  <my-app>Loading...</my-app>
+  <routing-wrapper>Loading...</routing-wrapper>
 </body>
 
 </html>
diff --git a/results_feed/web/main.dart b/results_feed/web/main.dart
index cd147cd..430968a 100644
--- a/results_feed/web/main.dart
+++ b/results_feed/web/main.dart
@@ -3,8 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:angular/angular.dart';
-import 'package:dart_results_feed/src/app_component.template.dart' as ng;
+import 'package:angular_router/angular_router.dart';
+import 'package:dart_results_feed/src/routing_wrapper_component.template.dart'
+    as ng;
+
+import 'main.template.dart' as self;
+
+@GenerateInjector(routerProviders)
+final InjectorFactory injector = self.injector$Injector;
 
 void main() {
-  runApp(ng.AppComponentNgFactory);
+  runApp(ng.RoutingWrapperComponentNgFactory, createInjector: injector);
 }