Small simplification to dependency injection.

Rather than caching a value inside of the DependencyProvider, instead
provide the provider function itself to the DependencySubscriber.

Release-Notes: skip
Change-Id: I803167ae316474364856e07f3348d842e1cc9c9b
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
index 76265fb..597c7c0 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.ts
@@ -31,15 +31,13 @@
 import {EditViewState} from '../../../models/views/edit';
 import {ChangeViewState} from '../../../models/views/change';
 import {PatchRangeParams} from '../../../utils/url-util';
-import {DependencyRequestEvent} from '../../../models/dependency';
+import {testResolver} from '../../../test/common-test-setup';
 
 suite('gr-router tests', () => {
   let router: GrRouter;
 
   setup(() => {
-    document.dispatchEvent(
-      new DependencyRequestEvent(routerToken, x => (router = x))
-    );
+    router = testResolver(routerToken);
   });
 
   test('getHashFromCanonicalPath', () => {
diff --git a/polygerrit-ui/app/models/dependency.ts b/polygerrit-ui/app/models/dependency.ts
index 5499db2..3b5081a 100644
--- a/polygerrit-ui/app/models/dependency.ts
+++ b/polygerrit-ui/app/models/dependency.ts
@@ -47,14 +47,15 @@
  * ---
  *
  * Ancestor components will inject the dependencies that a child component
- * requires by providing factories for those values.
+ * requires by providing providers for those values.
  *
  *
  * To provide a dependency, a component needs to specify the following prior
  * to finishing its connectedCallback:
  *
  * ```
- *   provide(this, fooToken, () => new FooImpl())
+ *   const fooImpl = new FooImpl();
+ *   provide(this, fooToken, () => fooImpl);
  * ```
  * Dependencies are injected as factories in case the construction of them
  * depends on other dependencies further up the component chain.  For instance,
@@ -63,7 +64,8 @@
  *
  * ```
  *   const barRef = resolve(this, barToken);
- *   provide(this, fooToken, () => new FooImpl(barRef()));
+ *   const fooImpl = new FooImpl(barRef());
+ *   provide(this, fooToken, () => fooImpl);
  * ```
  *
  * Lifetime guarantees
@@ -188,7 +190,7 @@
  */
 export interface DependencyRequest<T> {
   readonly dependency: DependencyToken<T>;
-  readonly callback: Callback<T>;
+  readonly callback: Callback<Provider<T>>;
 }
 
 declare global {
@@ -218,7 +220,7 @@
 {
   public constructor(
     public readonly dependency: DependencyToken<T>,
-    public readonly callback: Callback<T>
+    public readonly callback: Callback<Provider<T>>
   ) {
     super('request-dependency', {bubbles: true, composed: true});
   }
@@ -238,12 +240,20 @@
   }
 }
 
+function makeDependencyError<T>(
+  host: HTMLElement,
+  dependency: DependencyToken<T>
+): DependencyError<T> {
+  const dep = dependency.description;
+  const tag = host.tagName;
+  const msg = `Could not resolve dependency '${dep}' in '${tag}'`;
+  return new DependencyError(dependency, msg);
+}
+
 class DependencySubscriber<T>
   implements ReactiveController, ResolvedDependency<T>
 {
-  private value?: T;
-
-  private resolved = false;
+  private provider?: Provider<T>;
 
   constructor(
     private readonly host: ReactiveControllerHost & HTMLElement,
@@ -251,34 +261,26 @@
   ) {}
 
   get() {
-    this.checkResolved();
-    return this.value!;
+    if (!this.provider) {
+      throw makeDependencyError(this.host, this.dependency);
+    }
+    return this.provider();
   }
 
   hostConnected() {
-    this.value = undefined;
-    this.resolved = false;
+    this.provider = undefined;
     this.host.dispatchEvent(
-      new DependencyRequestEvent(this.dependency, (value: T) => {
-        this.resolved = true;
-        this.value = value;
+      new DependencyRequestEvent(this.dependency, (provider: Provider<T>) => {
+        this.provider = provider;
       })
     );
-    this.checkResolved();
-  }
-
-  checkResolved() {
-    if (this.resolved) return;
-    const dep = this.dependency.description;
-    const tag = this.host.tagName;
-    const msg = `Could not resolve dependency '${dep}' in '${tag}'`;
-    throw new DependencyError(this.dependency, msg);
+    if (!this.provider) {
+      throw makeDependencyError(this.host, this.dependency);
+    }
   }
 }
 
 class DependencyProvider<T> implements ReactiveController {
-  private value?: T;
-
   constructor(
     private readonly host: ReactiveControllerHost & HTMLElement,
     private readonly dependency: DependencyToken<T>,
@@ -286,20 +288,17 @@
   ) {}
 
   hostConnected() {
-    // Delay construction in case the provider has its own dependencies.
-    this.value = this.provider();
     this.host.addEventListener('request-dependency', this.fullfill);
   }
 
   hostDisconnected() {
     this.host.removeEventListener('request-dependency', this.fullfill);
-    this.value = undefined;
   }
 
   private readonly fullfill = (ev: DependencyRequestEvent<unknown>) => {
     if (ev.dependency !== this.dependency) return;
     ev.stopPropagation();
     ev.preventDefault();
-    ev.callback(this.value!);
+    ev.callback(this.provider);
   };
 }
diff --git a/polygerrit-ui/app/test/common-test-setup.ts b/polygerrit-ui/app/test/common-test-setup.ts
index 4b8357d..06e551a 100644
--- a/polygerrit-ui/app/test/common-test-setup.ts
+++ b/polygerrit-ui/app/test/common-test-setup.ts
@@ -94,7 +94,7 @@
 }
 
 function resolveDependency(evt: DependencyRequestEvent<unknown>) {
-  evt.callback(testResolver(evt.dependency));
+  evt.callback(() => testResolver(evt.dependency));
 }
 
 setup(() => {