('attribute tokens', () => {
    it('should be able to provide an attribute token', () => {
      const TOKEN = new InjectionToken<string>('Some token');
      function factory(token: string): string {
        return token + ' with factory';
      }
      @Component({
        selector: 'my-comp',
        template: '...',
        providers: [
          {
            provide: TOKEN,
            deps: [[new Attribute('token')]],
            useFactory: factory,
          },
        ],
        standalone: false,
      })
      class MyComp {
        constructor(@Inject(TOKEN) readonly token: string) {}
      }

      @Component({
        template: `<my-comp token='token'></my-comp>`,
        standalone: false,
      })
      class WrapperComp {
        @ViewChild(MyComp) myComp!: MyComp;
      }

      TestBed.configureTestingModule({declarations: [MyComp, WrapperComp]});

      const fixture = TestBed.createComponent(WrapperComp);
      fixture.detectChanges();
      expect(fixture.componentInstance.myComp.token).toBe('token with factory');
    });
  });

  describe('inject()', () => {
    it('should work in a directive constructor', () => {
      const TOKEN = new InjectionToken<string>('TOKEN');

      @Component({
        standalone: true,
        selector: 'test-cmp',
        template: '{{value}}',
        providers: [{provide: TOKEN, useValue: 'injected value'}],
      })
      class TestCmp {
        value: string;
        constructor() {
          this.value = inject(TOKEN);
        }
      }

      const fixture = TestBed.createComponent(TestCmp);
      fixture.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('injected value');
    });

    it('should work in a service constructor when the service is provided on a directive', () => {
      const TOKEN = new InjectionToken<string>('TOKEN');

      @Injectable()
      class Service {
        value: string;
        constructor() {
          this.value = inject(TOKEN);
        }
      }

      @Component({
        standalone: true,
        selector: 'test-cmp',
        template: '{{service.value}}',
        providers: [Service, {provide: TOKEN, useValue: 'injected value'}],
      })
      class TestCmp {
        constructor(readonly service: Service) {}
      }

      const fixture = TestBed.createComponent(TestCmp);
      fixture.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('injected value');
    });

    it('should be able to inject special tokens like ChangeDetectorRef', () => {
      const TOKEN = new InjectionToken<string>('TOKEN');

      @Component({
        standalone: true,
        selector: 'test-cmp',
        template: '{{value}}',
      })
      class TestCmp {
        cdr = inject(ChangeDetectorRef);
        value = 'before';
      }

      const fixture = TestBed.createComponent(TestCmp);
      fixture.componentInstance.value = 'after';
      fixture.componentInstance.cdr.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('after');
    });

    it('should work in a service constructor', () => {
      const TOKEN = new InjectionToken<string>('TOKEN', {
        providedIn: 'root',
        factory: () => 'injected value',
      });

      @Injectable({providedIn: 'root'})
      class Service {
        value: string;
        constructor() {
          this.value = inject(TOKEN);
        }
      }

      const service = TestBed.inject(Service);
      expect(service.value).toEqual('injected value');
    });

    it('should work in a useFactory definition for a service', () => {
      const TOKEN = new InjectionToken<string>('TOKEN', {
        providedIn: 'root',
        factory: () => 'injected value',
      });

      @Injectable({
        providedIn: 'root',
        useFactory: () => new Service(inject(TOKEN)),
      })
      class Service {
        constructor(readonly value: string) {}
      }

      expect(TestBed.inject(Service).value).toEqual('injected value');
    });

    it('should work for field injection', () => {
      const TOKEN = new InjectionToken<string>('TOKEN', {
        providedIn: 'root',
        factory: () => 'injected value',
      });

      @Injectable({providedIn: 'root'})
      class Service {
        value = inject(TOKEN);
      }

      const service = TestBed.inject(Service);
      expect(service.value).toEqual('injected value');
    });

    it('should not give non-node services access to the node context', () => {
      const TOKEN = new InjectionToken<string>('TOKEN');

      @Injectable({providedIn: 'root'})
      class Service {
        value: string;
        constructor() {
          this.value = inject(TOKEN, InjectFlags.Optional) ?? 'default value';
        }
      }

      @Component({
        standalone: true,
        selector: 'test-cmp',
        template: '{{service.value}}',
        providers: [{provide: TOKEN, useValue: 'injected value'}],
      })
      class TestCmp {
        service: Service;
        constructor() {
          // `Service` is injected starting from the component context, where `inject` is
          // `ɵɵdirectiveInject` under the hood. However, this should reach the root injector which
          // should _not_ use `ɵɵdirectiveInject` to inject dependencies of `Service`, so `TOKEN`
          // should not be visible to `Service`.
          this.service = inject(Service);
        }
      }

      const fixture = TestBed.createComponent(TestCmp);
      fixture.detectChanges();
      expect(fixture.nativeElement.innerHTML).toEqual('default value');
    });

    describe('with an options object argument', () => {
      it('should be able to optionally inject a service', () => {
        const TOKEN = new InjectionToken<string>('TOKEN');

        @Component({
          standalone: true,
          template: '',
        })
        class TestCmp {
          value = inject(TOKEN, {optional: true});
        }

        expect(TestBed.createComponent(TestCmp).componentInstance.value).toBeNull();
      });

      it('should be able to use skipSelf injection', () => {
        const TOKEN = new InjectionToken<string>('TOKEN', {
          providedIn: 'root',
          factory: () => 'from root',
        });
        @Component({
          standalone: true,
          template: '',
          providers: [{provide: TOKEN, useValue: 'from component'}],
        })
        class TestCmp {
          value = inject(TOKEN, {skipSelf: true});
        }

        expect(TestBed.createComponent(TestCmp).componentInstance.value).toEqual('from root');
      });

      it('should be able to use self injection', () => {
        const TOKEN = new InjectionToken<string>('TOKEN', {
          providedIn: 'root',
          factory: () => 'from root',
        });

        @Component({
          standalone: true,
          template: '',
        })
        class TestCmp {
          value = inject(TOKEN, {self: true, optional: true});
        }

        expect(TestBed.createComponent(TestCmp).componentInstance.value).toBeNull();
      });

      it('should be able to use host injection', () => {
        const TOKEN = new InjectionToken<string>('TOKEN');

        @Component({
          standalone: true,
          selector: 'child',
          template: '{{value}}',
        })
        class ChildCmp {
          value = inject(TOKEN, {host: true, optional: true}) ?? 'not found';
        }

        @Component({
          standalone: true,
          imports: [ChildCmp],
          template: '<child></child>',
          providers: [{provide: TOKEN, useValue: 'from parent'}],
          encapsulation: ViewEncapsulation.None,
        })
        class ParentCmp {}

        const fixture = TestBed.createComponent(ParentCmp);
        fixture.detectChanges();
        expect(fixture.nativeElement.innerHTML).toEqual('<child>not found</child>');
      });

      it('should not indicate it returns null when optional is explicitly false', () => {
        const TOKEN = new InjectionToken<string>('TOKEN', {
          providedIn: 'root',
          factory: () => 'from root',
        });

        @Component({
          standalone: true,
          template: '',
        })
        class TestCmp {
          // TypeScript will check if this assignment is legal, which won't be the case if
          // inject() erroneously returns a `string|null` type here.
          value: string = inject(TOKEN, {optional: false});
        }

        expect(TestBed.createComponent(TestCmp).componentInstance.value).toEqual('from root');
      });
    });
  });

  describe


/*!
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  Component,
  Directive,
  effect,
  ErrorHandler,
  EventEmitter,
  output,
  signal,
} from '@angular/core';
import {outputFromObservable} from '@angular/core/rxjs-interop';
import {setUseMicrotaskEffectsByDefault} from '@angular/core/src/render3/reactivity/effect';
import {TestBed} from '@angular/core/testing';
import {BehaviorSubject, Observable, share, Subject} from 'rxjs';

describe('output() function', () => {
  let prev: boolean;
  beforeEach(() => {
    prev = setUseMicrotaskEffectsByDefault(false);
  });
  afterEach(() => setUseMicrotaskEffectsByDefault(prev));

  it('should support emitting values', () => {
    @Directive({
      selector: '[dir]',
      standalone: true,
    })
    class Dir {
      onBla = output<number>();
    }

    @Component({
      template: '

',
      standalone: true,
      imports: [Dir],
    })
    class App {
      values: number[] = [];
    }

    const fixture = TestBed.createComponent(App);
    const dir = fixture.debugElement.children[0].injector.get(Dir);

    fixture.detectChanges();

    expect(fixture.componentInstance.values).toEqual([]);
    dir.onBla.emit(1);
    dir.onBla.emit(2);

    expect(fixture.componentInstance.values).toEqual([1, 2]);
  });

  it('should support emitting void values', () => {
    @Directive({
      selector: '[dir]',
      standalone: true,
    })
    class Dir {
      onBla = output();
    }

    @Component({
      template: '

',
      standalone: true,
      imports: [Dir],
    })
    class App {
      count = 0;
    }

    const fixture = TestBed.createComponent(App);
    const dir = fixture.debugElement.children[0].injector.get(Dir);

    fixture.detectChanges();

    expect(fixture.componentInstance.count).toEqual(0);
    dir.onBla.emit();
    dir.onBla.emit();

    expect(fixture.componentInstance.count).toEqual(2);
  });

  it('should error when emitting to a destroyed output', () => {
    @Directive({
      selector: '[dir]',
      standalone: true,
    })
    class Dir {
      onBla = output<number>();
    }

    @Component({
      template: `
        @if (show) {

        }
      `,
      standalone: true,
      imports: [Dir],
    })
    class App {
      show = true;
      values: number[] = [];
    }

    const fixture = TestBed.createComponent(App);
    fixture.detectChanges();
    const dir = fixture.debugElement.children[0].injector.get(Dir);

    expect(fixture.componentInstance.values).toEqual([]);
    dir.onBla.emit(1);
    dir.onBla.emit(2);
    expect(fixture.componentInstance.values).toEqual([1, 2]);

    fixture.componentInstance.show = false;
    fixture.detectChanges();

    expect(() => dir.onBla.emit(3)).toThrowError(/Unexpected emit for destroyed `OutputRef`/);
  });

  it('should error when subscribing to a destroyed output', () => {
    @Directive({
      selector: '[dir]',
      standalone: true,
    })
    class Dir {
      onBla = output<number>();
    }

    @Component({
      template: `
        @if (show) {

        }
      `,
      standalone: true,
      imports: [Dir],
    })
    class App {
      show = true;
      values: number[] = [];
    }

    const fixture = TestBed.createComponent(App);
    fixture.detectChanges();
    const dir = fixture.debugElement.children[0].injector.get(Dir);

    expect(fixture.componentInstance.values).toEqual([]);
    dir.onBla.emit(1);
    dir.onBla.emit(2);
    expect(fixture.componentInstance.values).toEqual([1, 2]);

    fixture.componentInstance.show = false;
    fixture.detectChanges();

    expect(() => dir.onBla.subscribe(() => {})).toThrowError(
      /Unexpected subscription to destroyed `OutputRef`/,
    );
  });

  it('should run listeners outside of `emit` reactive context', () => {
    @Directive({
      selector: '[dir]',
      standalone: true,
    })
    class Dir {
      onBla = output();
      effectCount = 0;

      constructor() {
        effect(() => {
          this.onBla.emit();
          this.effectCount++;
        });
      }
    }

    @Component({
      template: '

',
      standalone: true,
      imports: [Dir],
    })
    class App {
      signalUnrelatedToDir = signal(0);

      fnUsingSomeSignal() {
        // track this signal in this function.
        this.signalUnrelatedToDir();
      }
    }

    const fixture = TestBed.createComponent(App);
    const dir = fixture.debugElement.children[0].injector.get(Dir);

    fixture.detectChanges();

    expect(dir.effectCount).toEqual(1);
    fixture.componentInstance.signalUnrelatedToDir.update((v) => v + 1);
    fixture.detectChanges();

    expect(dir.effectCount).toEqual(1);
  });


describe('outputFromObservable()', () => {
    it('should support using a `Subject` as source', () => {
      @Directive({
        selector: '[dir]',
        standalone: true,
      })
      class Dir {
        onBla$ = new Subject<number>();
        onBla = outputFromObservable(this.onBla$);
      }

      @Component({
        template: '

',
        standalone: true,
        imports: [Dir],
      })
      class App {
        values: number[] = [];
      }

      const fixture = TestBed.createComponent(App);
      const dir = fixture.debugElement.children[0].injector.get(Dir);

      fixture.detectChanges();

      expect(fixture.componentInstance.values).toEqual([]);
      dir.onBla$.next(1);
      dir.onBla$.next(2);

      expect(fixture.componentInstance.values).toEqual([1, 2]);
    });

    it('should support using a `BehaviorSubject` as source', () => {
      @Directive({
        selector: '[dir]',
        standalone: true,
      })
      class Dir {
        onBla$ = new BehaviorSubject<number>(1);
        onBla = outputFromObservable(this.onBla$);
      }

      @Component({
        template: '

',
        standalone: true,
        imports: [Dir],
      })
      class App {
        values: number[] = [];
      }

      const fixture = TestBed.createComponent(App);
      const dir = fixture.debugElement.children[0].injector.get(Dir);

      fixture.detectChanges();

      expect(fixture.componentInstance.values).toEqual([1]);
      dir.onBla$.next(2);
      dir.onBla$.next(3);

      expect(fixture.componentInstance.values).toEqual([1, 2, 3]);
    });

    it('should support using an `EventEmitter` as source', () => {
      @Directive({
        selector: '[dir]',
        standalone: true,
      })
      class Dir {
        onBla$ = new EventEmitter<number>();
        onBla = outputFromObservable(this.onBla$);
      }

      @Component({
        template: '

',
        standalone: true,
        imports: [Dir],
      })
      class App {
        values: number[] = [];
      }

      const fixture = TestBed.createComponent(App);
      const dir = fixture.debugElement.children[0].injector.get(Dir);

      fixture.detectChanges();

      expect(fixture.componentInstance.values).toEqual([]);
      dir.onBla$.next(1);
      dir.onBla$.next(2);

      expect(fixture.componentInstance.values).toEqual([1, 2]);
    });

    it('should support lazily creating an observer upon subscription', () => {
      @Directive({
        selector: '[dir]',
        standalone: true,
      })
      class Dir {
        streamStarted = false;
        onBla$ = new Observable((obs) => {
          this.streamStarted = true;
          obs.next(1);
        }).pipe(share());

        onBla = outputFromObservable(this.onBla$);
      }

      @Component({
        template: `

        `,
        standalone: true,
        imports: [Dir],
      })
      class App {}

      const fixture = TestBed.createComponent(App);
      const dir = fixture.debugElement.children[0].injector.get(Dir);
      const dir2 = fixture.debugElement.children[1].injector.get(Dir);

      fixture.detectChanges();

      expect(dir.streamStarted).toBe(false);
      expect(dir2.streamStarted).toBe(true);
    });

    it('should report subscription listener errors to `ErrorHandler` and continue', () => {
      @Directive({
        selector: '[dir]',
        standalone: true,
      })
      class Dir {
        onBla = output();
      }

      @Component({
        template: `

        `,
        standalone: true,
        imports: [Dir],
      })
      class App {}

      let handledErrors: unknown[] = [];
      TestBed.configureTestingModule({
        providers: [
          {
            provide: ErrorHandler,
            useClass: class Handler extends ErrorHandler {
              override handleError(error: unknown): void {
                handledErrors.push(error);
              }
            },
          },
        ],
      });

      const fixture = TestBed.createComponent(App);
      const dir = fixture.debugElement.children[0].injector.get(Dir);
      fixture.detectChanges();

      let triggered = 0;
      dir.onBla.subscribe(() => {
        throw new Error('first programmatic listener failure');
      });
      dir.onBla.subscribe(() => {
        triggered++;
      });

      dir.onBla.emit();

      expect(handledErrors.length).toBe(1);
      expect((handledErrors[0] as Error).message).toBe('first programmatic listener failure');
      expect(triggered).toBe(1);
    });
  });
});


describe('reactivity', () => {
  let prev: boolean;
  beforeEach(() => {
    prev = setUseMicrotaskEffectsByDefault(false);
  });
  afterEach(() => setUseMicrotaskEffectsByDefault(prev));

  describe('effects', () => {
    beforeEach(destroyPlatform);
    afterEach(destroyPlatform);

    it(
      'should run effects in the zone in which they get created',
      withBody('<test-cmp></test-cmp>', async () => {
        const log: string[] = [];
        @Component({
          selector: 'test-cmp',
          standalone: true,
          template: '',
        })
        class Cmp {
          constructor(ngZone: NgZone) {
            effect(() => {
              log.push(Zone.current.name);
            });

            ngZone.runOutsideAngular(() => {
              effect(() => {
                log.push(Zone.current.name);
              });
            });
          }
        }

        await bootstrapApplication(Cmp);

        expect(log).not.toEqual(['angular', 'angular']);
      }),
    );

    it('should contribute to application stableness when an effect is pending', async () => {
      const someSignal = signal('initial');
      const appRef = TestBed.inject(ApplicationRef);

      const isStable: boolean[] = [];
      const sub = appRef.isStable.subscribe((stable) => isStable.push(stable));
      expect(isStable).toEqual([true]);

      TestBed.runInInjectionContext(() => effect(() => someSignal()));
      expect(isStable).toEqual([true, false]);

      appRef.tick();

      expect(isStable).toEqual([true, false, true]);
    });

    it('should propagate errors to the ErrorHandler', () => {
      TestBed.configureTestingModule({
        providers: [{provide: ErrorHandler, useFactory: () => new FakeErrorHandler()}],
        rethrowApplicationErrors: false,
      });

      let run = false;

      let lastError: any = null;
      class FakeErrorHandler extends ErrorHandler {
        override handleError(error: any): void {
          lastError = error;
        }
      }
      const appRef = TestBed.inject(ApplicationRef);
      effect(
        () => {
          run = true;
          throw new Error('fail!');
        },
        {injector: appRef.injector},
      );
      appRef.tick();
      expect(run).toBeTrue();
      expect(lastError.message).toBe('fail!');
    });

    // Disabled while we consider whether this actually makes sense.
    // This test _used_ to show that `effect()` was usable inside component error handlers, partly
    // because effect errors used to report to component error handlers. Now, effect errors are
    // always reported to the top-level error handler, which has never been able to use `effect()`
    // as `effect()` depends transitively on `ApplicationRef` which depends circularly on
    // `ErrorHandler`.
    xit('should be usable inside an ErrorHandler', async () => {
      const shouldError = signal(false);
      let lastError: any = null;

      class FakeErrorHandler extends ErrorHandler {
        constructor() {
          super();
          effect(() => {
            if (shouldError()) {
              throw new Error('fail!');
            }
          });
        }

        override handleError(error: any): void {
          lastError = error;
        }
      }

      TestBed.configureTestingModule({
        providers: [{provide: ErrorHandler, useClass: FakeErrorHandler}],
        rethrowApplicationErrors: false,
      });

      const appRef = TestBed.inject(ApplicationRef);
      expect(() => appRef.tick()).not.toThrow();

      shouldError.set(true);
      expect(() => appRef.tick()).not.toThrow();
      expect(lastError?.message).toBe('fail!');
    });

    it('should run effect cleanup function on destroy', async () => {
      let counterLog: number[] = [];
      let cleanupCount = 0;

      @Component({
        selector: 'test-cmp',
        standalone: true,
        template: '',
      })
      class Cmp {
        counter = signal(0);
        effectRef = effect((onCleanup) => {
          counterLog.push(this.counter());
          onCleanup(() => {
            cleanupCount++;
          });
        });
      }

      const fixture = TestBed.createComponent(Cmp);
      fixture.detectChanges();
      await fixture.whenStable();
      expect(counterLog).toEqual([0]);
      // initially an effect runs but the default cleanup function is noop
      expect(cleanupCount).toBe(0);

      fixture.componentInstance.counter.set(5);
      fixture.detectChanges();
      await fixture.whenStable();
      expect(counterLog).toEqual([0, 5]);
      expect(cleanupCount).toBe(1);

      fixture.destroy();
      expect(counterLog).toEqual([0, 5]);
      expect(cleanupCount).toBe(2);
    });

    it('should run effects created in ngAfterViewInit', () => {
      let didRun = false;

      @Component({
        selector: 'test-cmp',
        standalone: true,
        template: '',
      })
      class Cmp implements AfterViewInit {
        injector = inject(Injector);

        ngAfterViewInit(): void {
          effect(
            () => {
              didRun = true;
            },
            {injector: this.injector},
          );
        }
      }

      const fixture = TestBed.createComponent(Cmp);
      fixture.detectChanges();

      expect(didRun).toBeTrue();
    });

    it('should create root effects when outside of a component, using injection context', () => {
      TestBed.configureTestingModule({});
      const counter = signal(0);
      const log: number[] = [];
      TestBed.runInInjectionContext(() => effect(() => log.push(counter())));

      TestBed.flushEffects();
      expect(log).toEqual([0]);

      counter.set(1);
      TestBed.flushEffects();
      expect(log).toEqual([0, 1]);
    });

    it('should create root effects when outside of a component, using an injector', () => {
      TestBed.configureTestingModule({});
      const counter = signal(0);
      const log: number[] = [];
      effect(() => log.push(counter()), {injector: TestBed.inject(Injector)});

      TestBed.flushEffects();
      expect(log).toEqual([0]);

      counter.set(1);
      TestBed.flushEffects();
      expect(log).toEqual([0, 1]);
    });

    it('should create root effects inside a component when specified', () => {
      TestBed.configureTestingModule({});
      const counter = signal(0);
      const log: number[] = [];

      @Component({
        standalone: true,
        template: '',
      })
      class TestCmp {
        constructor() {
          effect(() => log.push(counter()), {forceRoot: true});
        }
      }

      // Running this creates the effect. Note: we never CD this component.
      TestBed.createComponent(TestCmp);

      TestBed.flushEffects();
      expect(log).toEqual([0]);

      counter.set(1);
      TestBed.flushEffects();
      expect(log).toEqual([0, 1]);
    });

    it('should check components made dirty from markForCheck() from an effect', async () => {
      TestBed.configureTestingModule({
        providers: [provideExperimentalZonelessChangeDetection()],
      });

      const source = signal('');
      @Component({
        standalone: true,
        changeDetection: ChangeDetectionStrategy.OnPush,
        template: '{{ data }}',
      })
      class TestCmp {
        cdr = inject(ChangeDetectorRef);
        data = '';
        effectRef = effect(() => {
          if (this.data !== source()) {
            this.data = source();
            this.cdr.markForCheck();
          }
        });
      }

      const fix = TestBed.createComponent(TestCmp);
      await fix.whenStable();

      source.set('test');
      await fix.whenStable();

      expect(fix.nativeElement.innerHTML).toBe('test');
    });

    it('should check components made dirty from markForCheck() from an effect in a service', async () => {
      TestBed.configureTestingModule({
        providers: [provideExperimentalZonelessChangeDetection()],
      });

      const source = signal('');

      @Injectable()
      class Service {
        data = '';
        cdr = inject(ChangeDetectorRef);
        effectRef = effect(() => {
          if (this.data !== source()) {
            this.data = source();
            this.cdr.markForCheck();
          }
        });
      }

      @Component({
        standalone: true,
        changeDetection: ChangeDetectionStrategy.OnPush,
        providers: [Service],
        template: '{{ service.data }}',
      })
      class TestCmp {
        service = inject(Service);
      }

      const fix = TestBed.createComponent(TestCmp);
      await fix.whenStable();

      source.set('test');
      await fix.whenStable();

      expect(fix.nativeElement.innerHTML).toBe('test');
    });


it('should check views made dirty from markForCheck() from an effect in a directive', async () => {
      TestBed.configureTestingModule({
        providers: [provideExperimentalZonelessChangeDetection()],
      });

      const source = signal('');

      @Directive({
        standalone: true,
        selector: '[dir]',
      })
      class Dir {
        tpl = inject(TemplateRef);
        vcr = inject(ViewContainerRef);
        cdr = inject(ChangeDetectorRef);
        ctx = {
          $implicit: '',
        };
        ref = this.vcr.createEmbeddedView(this.tpl, this.ctx);

        effectRef = effect(() => {
          if (this.ctx.$implicit !== source()) {
            this.ctx.$implicit = source();
            this.cdr.markForCheck();
          }
        });
      }

      @Component({
        standalone: true,
        imports: [Dir],
        template: `<ng-template dir let-data>{{data}}</ng-template>`,
        changeDetection: ChangeDetectionStrategy.OnPush,
      })
      class TestCmp {}

      const fix = TestBed.createComponent(TestCmp);
      await fix.whenStable();

      source.set('test');
      await fix.whenStable();

      expect(fix.nativeElement.innerHTML).toContain('test');
    });

    describe('destruction', () => {
      it('should still destroy root effects with the DestroyRef of the component', () => {
        TestBed.configureTestingModule({});
        const counter = signal(0);
        const log: number[] = [];

        @Component({
          standalone: true,
          template: '',
        })
        class TestCmp {
          constructor() {
            effect(() => log.push(counter()), {forceRoot: true});
          }
        }

        const fix = TestBed.createComponent(TestCmp);

        TestBed.flushEffects();
        expect(log).toEqual([0]);

        // Destroy the effect.
        fix.destroy();

        counter.set(1);
        TestBed.flushEffects();
        expect(log).toEqual([0]);
      });

      it('should destroy effects when the parent component is destroyed', () => {
        let destroyed = false;
        @Component({
          standalone: true,
        })
        class TestCmp {
          constructor() {
            effect((onCleanup) => onCleanup(() => (destroyed = true)));
          }
        }

        const fix = TestBed.createComponent(TestCmp);
        fix.detectChanges();

        fix.destroy();
        expect(destroyed).toBeTrue();
      });

      it('should destroy effects when their view is destroyed, separately from DestroyRef', () => {
        let destroyed = false;
        @Component({
          standalone: true,
        })
        class TestCmp {
          readonly injector = Injector.create({providers: [], parent: inject(Injector)});

          constructor() {
            effect((onCleanup) => onCleanup(() => (destroyed = true)), {injector: this.injector});
          }
        }

        const fix = TestBed.createComponent(TestCmp);
        fix.detectChanges();

        fix.destroy();
        expect(destroyed).toBeTrue();
      });

      it('should destroy effects when their DestroyRef is separately destroyed', () => {
        let destroyed = false;
        @Component({
          standalone: true,
        })
        class TestCmp {
          readonly injector = Injector.create({providers: [], parent: inject(Injector)});

          constructor() {
            effect((onCleanup) => onCleanup(() => (destroyed = true)), {injector: this.injector});
          }
        }

        const fix = TestBed.createComponent(TestCmp);
        fix.detectChanges();

        (fix.componentInstance.injector as Injector & {destroy(): void}).destroy();
        expect(destroyed).toBeTrue();
      });
    });
  });


describe('safeguards', () => {
    it('should allow writing to signals within effects', () => {
      const counter = signal(0);

      effect(() => counter.set(1), {injector: TestBed.inject(Injector)});
      TestBed.flushEffects();
      expect(counter()).toBe(1);
    });

    it('should allow writing to signals in ngOnChanges', () => {
      @Component({
        selector: 'with-input',
        standalone: true,
        template: '{{inSignal()}}',
      })
      class WithInput implements OnChanges {
        inSignal = signal<string | undefined>(undefined);
        @Input() in: string | undefined;

        ngOnChanges(changes: SimpleChanges): void {
          if (changes['in']) {
            this.inSignal.set(changes['in'].currentValue);
          }
        }
      }

      @Component({
        selector: 'test-cmp',
        standalone: true,
        imports: [WithInput],
        template: `<with-input [in]="'A'" />|<with-input [in]="'B'" />`,
      })
      class Cmp {}

      const fixture = TestBed.createComponent(Cmp);
      fixture.detectChanges();
      expect(fixture.nativeElement.textContent).toBe('A|B');
    });

    it('should allow writing to signals in a constructor', () => {
      @Component({
        selector: 'with-constructor',
        standalone: true,
        template: '{{state()}}',
      })
      class WithConstructor {
        state = signal('property initializer');

        constructor() {
          this.state.set('constructor');
        }
      }

      @Component({
        selector: 'test-cmp',
        standalone: true,
        imports: [WithConstructor],
        template: `<with-constructor />`,
      })
      class Cmp {}

      const fixture = TestBed.createComponent(Cmp);
      fixture.detectChanges();
      expect(fixture.nativeElement.textContent).toBe('constructor');
    });

    it('should allow writing to signals in input setters', () => {
      @Component({
        selector: 'with-input-setter',
        standalone: true,
        template: '{{state()}}',
      })
      class WithInputSetter {
        state = signal('property initializer');

        @Input()
        set testInput(newValue: string) {
          this.state.set(newValue);
        }
      }

      @Component({
        selector: 'test-cmp',
        standalone: true,
        imports: [WithInputSetter],
        template: `
          <with-input-setter [testInput]="'binding'" />|<with-input-setter testInput="static" />
      `,
      })
      class Cmp {}

      const fixture = TestBed.createComponent(Cmp);
      fixture.detectChanges();
      expect(fixture.nativeElement.textContent).toBe('binding|static');
    });

    it('should allow writing to signals in query result setters', () => {
      @Component({
        selector: 'with-query',
        standalone: true,
        template: '{{items().length}}',
      })
      class WithQuery {
        items = signal<unknown[]>([]);

        @ContentChildren('item')
        set itemsQuery(result: QueryList<unknown>) {
          this.items.set(result.toArray());
        }
      }

      @Component({
        selector: 'test-cmp',
        standalone: true,
        imports: [WithQuery],
        template: `<with-query><div #item></div></with-query>`,
      })
      class Cmp {}

      const fixture = TestBed.createComponent(Cmp);
      fixture.detectChanges();
      expect(fixture.nativeElement.textContent).toBe('1');
    });

    it('should not execute query setters in the reactive context', () => {
      const state = signal('initial');

      @Component({
        selector: 'with-query-setter',
        standalone: true,
        template: '<div #el></div>',
      })
      class WithQuerySetter {
        el: unknown;
        @ViewChild('el', {static: true})
        set elQuery(result: unknown) {
          // read a signal in a setter - I want to verify that framework executes this code outside of
          // the reactive context
          state();
          this.el = result;
        }
      }

      @Component({
        selector: 'test-cmp',
        standalone: true,
        template: ``,
      })
      class Cmp {
        noOfCmpCreated = 0;
        constructor(environmentInjector: EnvironmentInjector) {
          // A slightly artificial setup where a component instance is created using imperative APIs.
          // We don't have control over the timing / reactive context of such API calls so need to
          // code defensively in the framework.

          // Here we want to specifically verify that an effect is _not_ re-run if a signal read
          // happens in a query setter of a dynamically created component.
          effect(() => {
            createComponent(WithQuerySetter, {environmentInjector});
            this.noOfCmpCreated++;
          });
        }
      }

      const fixture = TestBed.createComponent(Cmp);
      fixture.detectChanges();

      expect(fixture.componentInstance.noOfCmpCreated).toBe(1);

      state.set('changed');
      fixture.detectChanges();

      expect(fixture.componentInstance.noOfCmpCreated).toBe(1);
    });

    it('should allow toObservable subscription in template (with async pipe)', () => {
      @Component({
        selector: 'test-cmp',
        standalone: true,
        imports: [AsyncPipe],
        template: '{{counter$ | async}}',
      })
      class Cmp {
        counter$ = toObservable(signal(0));
      }

      const fixture = TestBed.createComponent(Cmp);
      expect(() => fixture.detectChanges(true)).not.toThrow();
      fixture.detectChanges();

      expect(fixture.nativeElement.textContent).toBe('0');
    });

    it('should assign a debugName to the underlying node for an effect', async () => {
      @Component({
        selector: 'test-cmp',
        standalone: true,
        template: '',
      })
      class Cmp {
        effectRef = effect(() => {}, {debugName: 'TEST_DEBUG_NAME'});
      }

      const fixture = TestBed.createComponent(Cmp);
      fixture.detectChanges();
      const component = fixture.componentInstance;
      const effectRef = component.effectRef as unknown as {[SIGNAL]: EffectNode};
      expect(effectRef[SIGNAL].debugName).toBe('TEST_DEBUG_NAME');
    });


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {ChangeDetectorRef, Component, Injectable, NgModule, ViewEncapsulation} from '@angular/core';
import {loadTranslations} from '@angular/localize';
import {BrowserModule, platformBrowser} from '@angular/platform-browser';

import {translations} from './translations';

class Todo {
  editing: boolean;

  get title() {
    return this._title;
  }
  set title(value: string) {
    this._title = value.trim();
  }

  constructor(
    private _title: string,
    public completed: boolean = false,
  ) {
    this.editing = false;
  }
}

@Injectable({providedIn: 'root'})
class TodoStore {
  todos: Array<Todo> = [
    new Todo($localize`Demonstrate Components`),
    new Todo($localize`Demonstrate Structural Directives`, true),
    // Using a placeholder
    new Todo($localize`Demonstrate ${'NgModules'}:value:`),
    new Todo($localize`Demonstrate zoneless change detection`),
    new Todo($localize`Demonstrate internationalization`),
  ];

  private getWithCompleted(completed: boolean) {
    return this.todos.filter((todo: Todo) => todo.completed === completed);
  }

  allCompleted() {
    return this.todos.length === this.getCompleted().length;
  }

  setAllTo(completed: boolean) {
    this.todos.forEach((t: Todo) => (t.completed = completed));
  }

  removeCompleted() {
    this.todos = this.getWithCompleted(false);
  }

  getRemaining() {
    return this.getWithCompleted(false);
  }

  getCompleted() {
    return this.getWithCompleted(true);
  }

  toggleCompletion(todo: Todo) {
    todo.completed = !todo.completed;
  }

  remove(todo: Todo) {
    this.todos.splice(this.todos.indexOf(todo), 1);
  }

  add(title: string) {
    this.todos.push(new Todo(title));
  }
}

@Component({
  selector: 'todo-app',
  // TODO(misko): make this work with `[(ngModel)]`
  encapsulation: ViewEncapsulation.None,
  template: `

todos

      <input class="new-todo" i18n-placeholder placeholder="What needs to be done?" autofocus=""
             [value]="newTodoText"
             (keyup)="$event.code == 'Enter' ? addTodo() : updateNewTodoValue($event.target.value)">

 0" class="main">
      <input *ngIf="todoStore.todos.length"
             #toggleall class="toggle-all" type="checkbox"
             [checked]="todoStore.allCompleted()"
             (click)="toggleAllTodos(toggleall.checked)">

            <input class="toggle" type="checkbox"
                   (click)="toggleCompletion(todo)"
                   [checked]="todo.completed">
            {{todo.title}}
            <button class="destroy" (click)="remove(todo)"></button>

          <input *ngIf="todo.editing"
                 class="edit" #editedtodo
                 [value]="todo.title"
                 (blur)="updateEditedTodoValue(todo, editedtodo.value)"
                 (keyup)="updateEditedTodoValue(todo, $event.target.value)"
                 (keyup)="$event.code == 'Enter' && updateEditedTodoValue(todo, editedtodo.value)"
                 (keyup)="$event.code == 'Escape' && cancelEditingTodo(todo)">

     0" class="footer">

        {{todoStore.getRemaining().length}} {todoStore.getRemaining().length, plural, =1 {item left} other {items left}}

      <button *ngIf="todoStore.getCompleted().length > 0" class="clear-completed" (click)="removeCompleted()" i18n>
        Clear completed
      </button>

  `,
  standalone: false,
})
class ToDoAppComponent {
  newTodoText = '';

  constructor(
    public todoStore: TodoStore,
    private readonly cdr: ChangeDetectorRef,
  ) {
    (window as any).todoAppComponent = this;
  }

  cancelEditingTodo(todo: Todo) {
    todo.editing = false;
    this.cdr.detectChanges();
  }

  finishUpdatingTodo(todo: Todo, editedTitle: string) {
    editedTitle = editedTitle.trim();

    if (editedTitle.length === 0) {
      this.remove(todo);
    }

    todo.title = editedTitle;
    this.cancelEditingTodo(todo);
  }

  editTodo(todo: Todo) {
    todo.editing = true;
    this.cdr.detectChanges();
  }

  removeCompleted() {
    this.todoStore.removeCompleted();
    this.cdr.detectChanges();
  }

  toggleCompletion(todo: Todo) {
    this.todoStore.toggleCompletion(todo);
    this.cdr.detectChanges();
  }

  remove(todo: Todo) {
    this.todoStore.remove(todo);
    this.cdr.detectChanges();
  }

  addTodo() {
    if (this.newTodoText.trim().length) {
      this.todoStore.add(this.newTodoText);
      this.newTodoText = '';
    }
    this.cdr.detectChanges();
  }

  toggleAllTodos(checked: boolean) {
    this.todoStore.setAllTo(checked);
    this.cdr.detectChanges();
  }

  updateEditedTodoValue(todo: Todo, value: string) {
    todo.title = value;
    this.cdr.detectChanges();
  }

  updateNewTodoValue(value: string) {
    this.newTodoText = value;
    this.cdr.detectChanges();
  }
}

@NgModule({
  declarations: [ToDoAppComponent],
  imports: [BrowserModule],
  bootstrap: [ToDoAppComponent],
})
class ToDoAppModule {}

loadTranslations(translations);

(window as any).appReady = platformBrowser().bootstrapModule(ToDoAppModule, {ngZone: 'noop'});


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */
import {Component, NgModule} from '@angular/core';
import {
  FormArray,
  FormBuilder,
  FormControl,
  FormGroup,
  ReactiveFormsModule,
  Validators,
} from '@angular/forms';
import {BrowserModule, platformBrowser} from '@angular/platform-browser';

@Component({
  selector: 'app-reactive-forms',
  template: `
    <form [formGroup]="profileForm">

        First Name:
        <input type="text" formControlName="firstName" />

        Last Name:
        <input type="text" formControlName="lastName" />

        Subscribe:
        <input type="checkbox" formControlName="subscribed" />

Disabled: <input formControlName="disabledInput" />

City: <input formControlName="city" />

      <button (click)="addCity()">Add City</button>
    </form>
  `,
  standalone: false,
})
class ReactiveFormsComponent {
  profileForm!: FormGroup;
  addresses!: FormArray;

  get itemControls() {
    return (this.profileForm.get('addresses') as FormArray).controls;
  }

  constructor(private formBuilder: FormBuilder) {
    // We use this reference in our test
    (window as any).reactiveFormsComponent = this;
  }

  ngOnInit() {
    this.profileForm = new FormGroup({
      firstName: new FormControl('', Validators.required),
      lastName: new FormControl(''),
      addresses: new FormArray([]),
      subscribed: new FormControl(),
      disabledInput: new FormControl({value: '', disabled: true}),
    });

    this.addCity();
  }

  createItem(): FormGroup {
    return this.formBuilder.group({
      city: '',
    });
  }

  addCity(): void {
    this.addresses = this.profileForm.get('addresses') as FormArray;
    this.addresses.push(this.createItem());
  }
}

@Component({
  selector: 'app-root',
  template: `
    <app-reactive-forms></app-reactive-forms>
  `,
  standalone: false,
})
class RootComponent {}

@NgModule({
  declarations: [RootComponent, ReactiveFormsComponent],
  imports: [BrowserModule, ReactiveFormsModule],
})
class FormsExampleModule {
  ngDoBootstrap(app: any) {
    app.bootstrap(RootComponent);
  }
}

function bootstrapApp() {
  return platformBrowser().bootstrapModule(FormsExampleModule, {ngZone: 'noop'});
}

(window as any).bootstrapApp = bootstrapApp;


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */
import {Component, NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {BrowserModule, platformBrowser} from '@angular/platform-browser';

@Component({
  selector: 'app-template-forms',
  template: `
    <form novalidate>

          First Name:
          <input name="first" ngModel required />

          Last Name:
          <input name="last" ngModel />

          Subscribe:
          <input name="subscribed" type="checkbox" ngModel />

Disabled: <input name="foo" ngModel disabled />

          City <input [(ngModel)]="addresses[i].city" name="name" />

        <button (click)="addCity()">Add City</button>

    </form>
  `,
  standalone: false,
})
class TemplateFormsComponent {
  name = {first: 'Nancy', last: 'Drew', subscribed: true};
  addresses = [{city: 'Toronto'}];
  constructor() {
    // We use this reference in our test
    (window as any).templateFormsComponent = this;
  }

  addCity() {
    this.addresses.push({city: ''});
  }
}

@Component({
  selector: 'app-root',
  template: `
    <app-template-forms></app-template-forms>
  `,
  standalone: false,
})
class RootComponent {}

@NgModule({
  declarations: [RootComponent, TemplateFormsComponent],
  imports: [BrowserModule, FormsModule],
})
class FormsExampleModule {
  ngDoBootstrap(app: any) {
    app.bootstrap(RootComponent);
  }
}

function bootstrapApp() {
  return platformBrowser().bootstrapModule(FormsExampleModule, {ngZone: 'noop'});
}

(window as any).bootstrapApp = bootstrapApp;


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */
import {animate, style, transition, trigger} from '@angular/animations';
import {Component, NgModule, ɵNgModuleFactory as NgModuleFactory} from '@angular/core';
import {bootstrapApplication, BrowserModule, platformBrowser} from '@angular/platform-browser';
import {BrowserAnimationsModule, provideAnimations} from '@angular/platform-browser/animations';

@Component({
  selector: 'app-animations',
  template: `
    <div [@myAnimation]="exp"></div>
    `,
  animations: [
    trigger('myAnimation', [transition('* => on', [animate(1000, style({opacity: 1}))])]),
  ],
  standalone: true,
})
class AnimationsComponent {
  exp: any = false;
}

@Component({
  selector: 'app-root',
  template: `
     <app-animations></app-animations>
   `,
  standalone: true,
  imports: [AnimationsComponent],
})
class RootComponent {}

(window as any).waitForApp = bootstrapApplication(RootComponent, {providers: provideAnimations()});


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */
import {animate, style, transition, trigger} from '@angular/animations';
import {Component, NgModule, ɵNgModuleFactory as NgModuleFactory} from '@angular/core';
import {BrowserModule, platformBrowser} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

@Component({
  selector: 'app-animations',
  template: `
    <div [@myAnimation]="exp"></div>
    `,
  animations: [
    trigger('myAnimation', [transition('* => on', [animate(1000, style({opacity: 1}))])]),
  ],
  standalone: false,
})
class AnimationsComponent {
  exp: any = false;
}

@Component({
  selector: 'app-root',
  template: `
     <app-animations></app-animations>
   `,
  standalone: false,
})
class RootComponent {}

@NgModule({
  declarations: [RootComponent, AnimationsComponent],
  imports: [BrowserModule, BrowserAnimationsModule],
})
class AnimationsExampleModule {
  ngDoBootstrap(app: any) {
    app.bootstrap(RootComponent);
  }
}

(window as any).waitForApp = platformBrowser().bootstrapModule(AnimationsExampleModule, {
  ngZone: 'noop',
});


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {APP_BASE_HREF} from '@angular/common';
import {Component, OnInit} from '@angular/core';
import {bootstrapApplication} from '@angular/platform-browser';
import {
  ActivatedRoute,
  provideRouter,
  Router,
  RouterLink,
  RouterLinkActive,
  RouterOutlet,
  Routes,
} from '@angular/router';

@Component({
  selector: 'app-list',
  template: `

List Item 1

List Item 2

List Item 3

  `,
  standalone: true,
  imports: [RouterLink, RouterLinkActive],
})
class ListComponent {}

@Component({
  selector: 'app-item',
  template: `
  Item {{id}}

<button (click)="viewList()">Back to List</button>
`,
  standalone: true,
})
class ItemComponent implements OnInit {
  id = -1;
  constructor(
    private activatedRoute: ActivatedRoute,
    private router: Router,
  ) {}

  ngOnInit() {
    this.activatedRoute.paramMap.subscribe((paramsMap) => {
      this.id = +paramsMap.get('id')!;
    });
  }

  viewList() {
    this.router.navigate(['/list']);
  }
}

@Component({
  selector: 'app-root',
  template: `<router-outlet></router-outlet>`,
  standalone: true,
  imports: [RouterOutlet],
})
class RootComponent {}

const ROUTES: Routes = [
  {path: '', redirectTo: '/list', pathMatch: 'full'},
  {path: 'list', component: ListComponent},
  {path: 'item/:id', component: ItemComponent},
];

(window as any).waitForApp = bootstrapApplication(RootComponent, {
  providers: [provideRouter(ROUTES), {provide: APP_BASE_HREF, useValue: ''}],
});


# Angular Signals Implementation

This directory contains the code which powers Angular's reactive primitive, an implementation of the "signal" concept. A signal is a value which is "reactive", meaning it can notify interested consumers when it changes. There are many different implementations of this concept, with different designs for how these notifications are subscribed to and propagated, how cleanup/unsubscription works, how dependencies are tracked, etc. This document describes the algorithm behind our specific implementation of the signal pattern.

## Conceptual surface

Angular Signals are zero-argument functions (`() => T`). When executed, they return the current value of the signal. Executing signals does not trigger side effects, though it may lazily recompute intermediate values (lazy memoization).

Particular contexts (such as template expressions) can be _reactive_. In such contexts, executing a signal will return the value, but also register the signal as a dependency of the context in question. The context's owner will then be notified if any of its signal dependencies produces a new value (usually, this results in the re-execution of those expressions to consume the new values).

This context and getter function mechanism allows for signal dependencies of a context to be tracked _automatically_ and _implicitly_. Users do not need to declare arrays of dependencies, nor does the set of dependencies of a particular context need to remain static across executions.

### Source signals

### Writable signals: `signal()`

The `createSignal()` function produces a specific type of signal that tracks a stored value. In addition to providing a getter function, these signals can be wired up with additional APIs for changing the value of the signal (along with notifying any dependents of the change). These include the `.set` operation for replacing the signal value, and `.update` for deriving a new value. In Angular, these are exposed as functions on the signal getter itself. For example:

```typescript
const counter = signal(0);

counter.set(2);
counter.update(count => count + 1);
```

#### Equality

The signal creation function one can, optionally, specify an equality comparator function. The comparator is used to decide whether the new supplied value is the same, or different, as compared to the current signal’s value.

If the equality function determines that 2 values are equal it will:
* block update of signal’s value;
* skip change propagation.

### Declarative derived values

`createComputed()` creates a memoizing signal, which calculates its value from the values of some number of input signals. In Angular this is wrapped into the `computed` constructor:

```typescript
const counter = signal(0);

// Automatically updates when `counter` changes:
const isEven = computed(() => counter() % 2 === 0);
```

Because the calculation function used to create the `computed` is executed in a reactive context, any signals read by that calculation will be tracked as dependencies, and the value of the computed signal recalculated whenever any of those dependencies changes.

Similarly to signals, the `computed` can (optionally) specify an equality comparator function.

### Side effects: `createWatch()`

The signals library provides an operation to watch a reactive function and receive notifications when the dependencies of that function change. This is used within Angular to build `effect()`.

`effect()` schedules and runs a side-effectful function inside a reactive context. Signal dependencies of this function are captured, and the side effect is re-executed whenever any of its dependencies produces a new value.

```typescript
const counter = signal(0);
effect(() => console.log('The counter is:', counter()));
// The counter is: 0

counter.set(1);
// The counter is: 1
```

Effects do not execute synchronously with the set (see the section on glitch-free execution below), but are scheduled and resolved by the framework. The exact timing of effects is unspecified.

## Producer and Consumer

Internally, the signals implementation is defined in terms of two abstractions, producers and consumers. Producers represents values which can deliver change notifications, such as the various flavors of `Signal`s. Consumers represents a reactive context which may depend on some number of producers. In other words, producers produce reactivity, and consumers consume it.

Implementers of either abstraction define a node object which implements the `ReactiveNode` interface, which models participation in the reactive graph. Any `ReactiveNode` can act in the role of a producer, a consumer, or both, by interacting with the appropriate subset of APIs. For example, `WritableSignal`s implement `ReactiveNode` but only operate against the producer APIs, since `WritableSignal`s don't consume other signal values.

Some concepts are both producers _and_ consumers. For example, derived `computed` expressions consume other signals to produce new reactive values.

Throughout the rest of this document, "producer" and "consumer" are used to describe `ReactiveNode`s acting in that capacity.

### The Dependency Graph

`ReactiveNode`s are linked together through a dependency graph. This dependency graph is bidirectional, but there are differences in which dependencies are tracked in each direction.

Consumers always keep track of the producers they depend on. Producers only track dependencies from consumers which are considered "live". A consumer is "live" when either:

* It sets the `consumerIsAlwaysLive` property of its `ReactiveNode` to `true`, or
* It's also a producer which is depended upon by a live consumer.

In that sense, "liveness" is a transitive property of consumers.

In practice, effects (including template pseudo-effects) are live consumers, which `computed`s are not inherently live. This means that any `computed` used in an `effect` will be treated as live, but a `computed` not read in any effects will not be.

#### Liveness and memory management

The concept of liveness allows for producer-to-consumer dependency tracking without risking memory leaks.

Consider this contrived case of a `signal` and a `computed`:

```typescript
const counter = signal(1);

let double = computed(() => counter() * 2);
console.log(double()); // 2
double = null;
```

If the dependency graph maintained a hard reference from `counter` to `double`, then `double` would be retained(not garbage collected) even though the user dropped their last reference to the actual signal. But because `double` is not live, the graph doesn't hold a reference from `counter` to `double`, and `double` can be freed when the user drops it.
#### Non-live consumers and polling

A consequence of not tracking an edge from `counter` to `double` is that when counter is changed:

```typescript
counter.set(2);
```

No notification can propagate in the graph from `counter` to `double`, to let the `computed` know that it needs to throw away its memoized value (2) and recompute (producing 4).

Instead, when `double()` is read, it polls its producers (which _are_ tracked in the graph) and checks whether any of them report having changed since the last time `double` was calculated. If not, it can safely use its memoized value.

#### With a live consumer

If an `effect` is created:

```typescript
effect(() => console.log(double()));
```

Then `double` becomes a live consumer, as it's a dependency of a live consumer (the effect), and the graph will have a hard reference from `counter` to `double` to the effect consumer. There is no risk of memory leaks though, since the effect is referencing `double` directly anyway, and the effect cannot just be dropped and must be manually destroyed (which would cause `double` to no longer be live). That is, there is no way for a reference from a producer to a live consumer to exist in the graph _without_ the consumer also referencing the producer outside of the graph.`

## "


Glitch Free" property

Consider the following setup:

```typescript
const counter = signal(0);
const evenOrOdd = computed(() => counter() % 2 === 0 ? 'even' : 'odd');
effect(() => console.log(counter() + ' is ' + evenOrOdd()));

counter.set(1);
```

When the effect is first created, it will print "0 is even", as expected, and record that both `counter` and `evenOrOdd` are dependencies of the logging effect.

When `counter` is set to `1`, this invalidates both `evenOrOdd` and the logging effect. If `counter.set()` iterated through the dependencies of `counter` and triggered the logging effect first, before notifying `evenOrOdd` of the change, however, we might observe the inconsistent logging statement "1 is even". Eventually `evenOrOdd` would be notified, which would trigger the logging effect again, logging the correct statement "1 is odd".

In this situation, the logging effect's observation of the inconsistent state "1 is even" is known as a _glitch_. A major goal of reactive system design is to prevent such intermediate states from ever being observed, and ensure _glitch-free execution_.

### Push/Pull Algorithm

Angular Signals guarantees glitch-free execution by separating updates to the `ReactiveNode` graph into two phases. The first phase is performed eagerly when a producer value is changed. This change notification is propagated through the graph, notifying live consumers which depend on the producer of the potential update. Some of these consumers may be derived values and thus also producers, which invalidate their cached values and then continue the propagation of the change notification to their own live consumers, and so on. Ultimately this notification reaches effects, which schedule themselves for re-execution.

Crucially, during this first phase, no side effects are run, and no recomputation of intermediate or derived values is performed, only invalidation of cached values. This allows the change notification to reach all affected nodes in the graph without the possibility of observing intermediate or glitchy states.

Once this change propagation has completed (synchronously), the second phase can begin. In this second phase, signal values may be read by the application or framework, triggering recomputation of any needed derived values which were previously invalidated.

We refer to this as the "push/pull" algorithm: "dirtiness" is eagerly _pushed_ through the graph when a source signal is changed, but recalculation is performed lazily, only when values are _pulled_ by reading their signals.

## Dynamic Dependency Tracking

When a reactive context operation (for example, an `effect`'s side effect function) is executed, the signals that it reads are tracked as dependencies. However, this may not be the same set of signals from one execution to the next. For example, this computed signal:

```typescript
const dynamic = computed(() => useA() ? dataA() : dataB());
```

reads either `dataA` or `dataB` depending on the value of the `useA` signal. At any given point, it will have a dependency set of either `[useA, dataA]` or `[useA, dataB]`, and it can never depend on `dataA` and `dataB` at the same time.

The potential dependencies of a reactive context are unbounded. Signals may be stored in variables or other data structures and swapped out with other signals from time to time. Thus, the signals implementation must deal with potential changes in the set of dependencies of a consumer on each execution.

Dependencies of a computation are tracked in an array. When the computation is rerun, a pointer into that array is initialized to the index `0`, and each dependency read is compared against the dependency from the previous run at the pointer's current location. If there's a mismatch, then the dependencies have changed since the last run, and the old dependency can be dropped and replaced with the new one. At the end of the run, any remaining unmatched dependencies can be dropped.

## Equality Semantics

Producers may lazily produce their value (such as a `computed` which only recalculates its value when pulled). However, a producer may also choose to apply an equality check to the values that it produces, and determine that the newly computed value is "equal" semantically to the previous. In this case, consumers which depend on that value should not be re-executed. For example, the following effect:

```typescript
const counter = signal(0);
const isEven = computed(() => counter() % 2 === 0);
effect(() => console.log(isEven() ? 'even!' : 'odd!'));
```

should run if `counter` is updated to `1` as the value of `isEven` switches from `true` to `false`. But if `counter` is then set to `3`, `isEven` will recompute the same value: `false`. Therefore the logging effect should not run.

This is a tricky property to guarantee in our implementation because values are not recomputed during the push phase of change propagation. `isEven` is invalidated when `counter` is changed, which causes the logging `effect` to also be invalidated and scheduled. Naively, `isEven` wouldn't be recomputed until the logging effect actually runs and attempts to read its value, which is too late to notice that it didn't need to run at all.

### Value Versioning

To solve this problem, our implementation uses a similar technique to tracking dependency staleness. Producers track a monotonically increasing `version`, representing the semantic identity of their value. `version` is incremented when the producer produces a semantically new value. The current `version` of each dependency (producer) is saved as part of the tracked dependencies of a consumer.

Before consumers trigger their reactive operations (e.g. the side effect function for `effect`s, or the recomputation for `computed`s), they poll their dependencies and ask for `version` to be refreshed if needed. For a `computed`, this will trigger recomputation of the value and the subsequent equality check, if the value is stale (which makes this polling a recursive process as the `computed` is also a consumer which will poll its own producers). If this recomputation produces a semantically changed value, `version` is incremented.

The consumer can then compare the `version` of the new value with its last read version to determine if that particular dependency really did change. By doing this for all producers the consumer can determine that, if all `version`s match, that no _actual_ change to any dependency has occurred, and it can skip reacting to that change (e.g. skip running the side effect function).

## `Watch` primitive

`Watch` is a primitive used to build different types of effects. `Watch`es are consumers that run side-effectful functions in their reactive context, but where the scheduling of the side effect is delegated to the implementor. The `Watch` will call this scheduling operation when it receives a notification that it's stale.


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

/**
 * A comparison function which can determine if two values are equal.
 */
export type ValueEqualityFn<T> = (a: T, b: T) => boolean;

/**
 * The default equality function used for `signal` and `computed`, which uses referential equality.
 */
export function defaultEquals<T>(a: T, b: T) {
  return Object.is(a, b);
}


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {defaultEquals, ValueEqualityFn} from './equality';
import {throwInvalidWriteToSignalError} from './errors';
import {
  producerAccessed,
  producerIncrementEpoch,
  producerNotifyConsumers,
  producerUpdatesAllowed,
  REACTIVE_NODE,
  ReactiveNode,
  SIGNAL,
} from './graph';

// Required as the signals library is in a separate package, so we need to explicitly ensure the
// global `ngDevMode` type is defined.
declare const ngDevMode: boolean | undefined;

/**
 * If set, called after `WritableSignal`s are updated.
 *
 * This hook can be used to achieve various effects, such as running effects synchronously as part
 * of setting a signal.
 */
let postSignalSetFn: (() => void) | null = null;

export interface SignalNode<T> extends ReactiveNode {
  value: T;
  equal: ValueEqualityFn<T>;
}

export type SignalBaseGetter<T> = (() => T) & {readonly [SIGNAL]: unknown};

// Note: Closure *requires* this to be an `interface` and not a type, which is why the
// `SignalBaseGetter` type exists to provide the correct shape.
export interface SignalGetter<T> extends SignalBaseGetter<T> {
  readonly [SIGNAL]: SignalNode<T>;
}

/**
 * Create a `Signal` that can be set or updated directly.
 */
export function createSignal<T>(initialValue: T): SignalGetter<T> {
  const node: SignalNode<T> = Object.create(SIGNAL_NODE);
  node.value = initialValue;
  const getter = (() => {
    producerAccessed(node);
    return node.value;
  }) as SignalGetter<T>;
  (getter as any)[SIGNAL] = node;
  return getter;
}

export function setPostSignalSetFn(fn: (() => void) | null): (() => void) | null {
  const prev = postSignalSetFn;
  postSignalSetFn = fn;
  return prev;
}

export function signalGetFn<T>(this: SignalNode<T>): T {
  producerAccessed(this);
  return this.value;
}

export function signalSetFn<T>(node: SignalNode<T>, newValue: T) {
  if (!producerUpdatesAllowed()) {
    throwInvalidWriteToSignalError();
  }

  if (!node.equal(node.value, newValue)) {
    node.value = newValue;
    signalValueChanged(node);
  }
}

export function signalUpdateFn<T>(node: SignalNode<T>, updater: (value: T) => T): void {
  if (!producerUpdatesAllowed()) {
    throwInvalidWriteToSignalError();
  }

  signalSetFn(node, updater(node.value));
}

export function runPostSignalSetFn(): void {
  postSignalSetFn?.();
}

// Note: Using an IIFE here to ensure that the spread assignment is not considered
// a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.
// TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.
export const SIGNAL_NODE: SignalNode<unknown> = /* @__PURE__ */ (() => {
  return {
    ...REACTIVE_NODE,
    equal: defaultEquals,
    value: undefined,
  };
})();

function signalValueChanged<T>(node: SignalNode<T>): void {
  node.version++;
  producerIncrementEpoch();
  producerNotifyConsumers(node);
  postSignalSetFn?.();
}


<T>(type: Type<T>): ComponentFixture<T> {
    const testComponentRenderer = this.inject(TestComponentRenderer);
    const rootElId = `root${_nextRootElementId++}`;
    testComponentRenderer.insertRootElement(rootElId);

    if (getAsyncClassMetadataFn(type)) {
      throw new Error(
        `Component '${type.name}' has unresolved metadata. ` +
          `Please call \`await TestBed.compileComponents()\` before running this test.`,
      );
    }

    const componentDef = (type as any).ɵcmp;

    if (!componentDef) {
      throw new Error(`It looks like '${stringify(type)}' has not been compiled.`);
    }

    const componentFactory = new ComponentFactory(componentDef);
    const initComponent = () => {
      const componentRef = componentFactory.create(
        Injector.NULL,
        [],
        `#${rootElId}`,
        this.testModuleRef,
      ) as ComponentRef<T>;
      return this.runInInjectionContext(() => new ComponentFixture(componentRef));
    };
    const noNgZone = this.inject(ComponentFixtureNoNgZone, false);
    const ngZone = noNgZone ? null : this.inject(NgZone, null);
    const fixture = ngZone ? ngZone.run(initComponent) : initComponent();
    this._activeFixtures.push(fixture);
    return fixture;
  }

  /**
   * @internal strip this from published d.ts files due to
   * https://github.com/microsoft/TypeScript/issues/36216
   */
  private get compiler(): TestBedCompiler {
    if (this._compiler === null) {
      throw new Error(`Need to call TestBed.initTestEnvironment() first`);
    }
    return this._compiler;
  }

  /**
   * @internal strip this from published d.ts files due to
   * https://github.com/microsoft/TypeScript/issues/36216
   */
  private get testModuleRef(): NgModuleRef<any> {
    if (this._testModuleRef === null) {
      this._testModuleRef = this.compiler.finalize();
    }
    return this._testModuleRef;
  }

  private assertNotInstantiated(methodName: string, methodDescription: string) {
    if (this._testModuleRef !== null) {
      throw new Error(
        `Cannot ${methodDescription} when the test module has already been instantiated. ` +
          `Make sure you are not using \`inject\` before \`${methodName}\`.`,
      );
    }
  }

  /**
   * Check whether the module scoping queue should be flushed, and flush it if needed.
   *
   * When the TestBed is reset, it clears the JIT module compilation queue, cancelling any
   * in-progress module compilation. This creates a potential hazard - the very first time the
   * TestBed is initialized (or if it's reset without being initialized), there may be pending
   * compilations of modules declared in global scope. These compilations should be finished.
   *
   * To ensure that globally declared modules have their components scoped properly, this function
   * is called whenever TestBed is initialized or reset. The _first_ time that this happens, prior
   * to any other operations, the scoping queue is flushed.
   */
  private checkGlobalCompilationFinished(): void {
    // Checking _testNgModuleRef is null should not be necessary, but is left in as an additional
    // guard that compilations queued in tests (after instantiation) are never flushed accidentally.
    if (!this.globalCompilationChecked && this._testModuleRef === null) {
      flushModuleScopingQueueAsMuchAsPossible();
    }
    this.globalCompilationChecked = true;
  }

  private destroyActiveFixtures(): void {
    let errorCount = 0;
    this._activeFixtures.forEach((fixture) => {
      try {
        fixture.destroy();
      } catch (e) {
        errorCount++;
        console.error('Error during cleanup of component', {
          component: fixture.componentInstance,
          stacktrace: e,
        });
      }
    });
    this._activeFixtures = [];

    if (errorCount > 0 && this.shouldRethrowTeardownErrors()) {
      throw Error(
        `${errorCount} ${errorCount === 1 ? 'component' : 'components'} ` +
          `threw errors during cleanup`,
      );
    }
  }

  shouldRethrowTeardownErrors(): boolean {
    const instanceOptions = this._instanceTeardownOptions;
    const environmentOptions = TestBedImpl._environmentTeardownOptions;

    // If the new teardown behavior hasn't been configured, preserve the old behavior.
    if (!instanceOptions && !environmentOptions) {
      return TEARDOWN_TESTING_MODULE_ON_DESTROY_DEFAULT;
    }

    // Otherwise use the configured behavior or default to rethrowing.
    return (
      instanceOptions?.rethrowErrors ??
      environmentOptions?.rethrowErrors ??
      this.shouldTearDownTestingModule()
    );
  }

  shouldThrowErrorOnUnknownElements(): boolean {
    // Check if a configuration has been provided to throw when an unknown element is found
    return (
      this._instanceErrorOnUnknownElementsOption ??
      TestBedImpl._environmentErrorOnUnknownElementsOption ??
      THROW_ON_UNKNOWN_ELEMENTS_DEFAULT
    );
  }

  shouldThrowErrorOnUnknownProperties(): boolean {
    // Check if a configuration has been provided to throw when an unknown property is found
    return (
      this._instanceErrorOnUnknownPropertiesOption ??
      TestBedImpl._environmentErrorOnUnknownPropertiesOption ??
      THROW_ON_UNKNOWN_PROPERTIES_DEFAULT
    );
  }

  shouldTearDownTestingModule(): boolean {
    return (
      this._instanceTeardownOptions?.destroyAfterEach ??
      TestBedImpl._environmentTeardownOptions?.destroyAfterEach ??
      TEARDOWN_TESTING_MODULE_ON_DESTROY_DEFAULT
    );
  }

  getDeferBlockBehavior(): DeferBlockBehavior {
    return this._instanceDeferBlockBehavior;
  }

  tearDownTestingModule() {
    // If the module ref has already been destroyed, we won't be able to get a test renderer.
    if (this._testModuleRef === null) {
      return;
    }
    // Resolve the renderer ahead of time, because we want to remove the root elements as the very
    // last step, but the injector will be destroyed as a part of the module ref destruction.
    const testRenderer = this.inject(TestComponentRenderer);
    try {
      this._testModuleRef.destroy();
    } catch (e) {
      if (this.shouldRethrowTeardownErrors()) {
        throw e;
      } else {
        console.error('Error during cleanup of a testing module', {
          component: this._testModuleRef.instance,
          stacktrace: e,
        });
      }
    } finally {
      testRenderer.removeAllRootElements?.();
    }
  }

  /**
   * Execute any pending effects.
   *
   * @developerPreview
   */
  flushEffects(): void {
    this.inject(MicrotaskEffectScheduler).flush();
    this.inject(EffectScheduler).flush();
  }
}

/**
 * @description
 * Configures and initializes environment for unit testing and provides methods for
 * creating components and services in unit tests.
 *
 * `TestBed` is the primary api for writing unit tests for Angular applications and libraries.
 *
 * @publicApi
 */
export const TestBed: TestBedStatic = TestBedImpl;

/**
 * Allows injecting dependencies in `beforeEach()` and `it()`. Note: this function
 * (imported from the `@angular/core/testing` package) can **only** be used to inject dependencies
 * in tests. To inject dependencies in your application code, use the [`inject`](api/core/inject)
 * function from the `@angular/core` package instead.
 *
 * Example:
 *
 * ```
 * beforeEach(inject([Dependency, AClass], (dep, object) => {
 *   // some code that uses `dep` and `object`
 *   // ...
 * }));
 *
 * it('...', inject([AClass], (object) => {
 *   object.doSomething();
 *   expect(...);
 * })
 * ```
 *
 * @publicApi
 */
export function inject(tokens: any[], fn: Function): () => any {
  const testBed = TestBedImpl.INSTANCE;
  // Not using an arrow function to preserve context passed from call site
  return function (this: unknown) {
    return testBed.execute(tokens, fn, this);
  };
}

/**
 * @publicApi
 */
export class InjectSetupWrapper {
  constructor(private _moduleDef: () => TestModuleMetadata) {}

  private _addModule() {
    const moduleDef = this._moduleDef();
    if (moduleDef) {
      TestBedImpl.configureTestingModule(moduleDef);
    }
  }

  inject(tokens: any[], fn: Function): () => any {
    const self = this;
    // Not using an arrow function to preserve context passed from call site
    return function (this: unknown) {
      self._addModule();
      return inject(tokens, fn).call(this);
    };
  }
}

/**
 * @publicApi
 */
export function withModule(moduleDef: TestModuleMetadata): InjectSetupWrapper;
export function withModule(moduleDef: TestModuleMetadata, fn: Function): () => any;
export function


describe('toSignal()', () => {
  it(
    'should reflect the last emitted value of an Observable',
    test(() => {
      const counter$ = new BehaviorSubject(0);
      const counter = toSignal(counter$);

      expect(counter()).toBe(0);
      counter$.next(1);
      expect(counter()).toBe(1);
      counter$.next(3);
      expect(counter()).toBe(3);
    }),
  );

  it(
    'should notify when the last emitted value of an Observable changes',
    test(() => {
      let seenValue: number = 0;
      const counter$ = new BehaviorSubject(1);
      const counter = toSignal(counter$);

      expect(counter()).toBe(1);

      counter$.next(2);
      expect(counter()).toBe(2);
    }),
  );

  it(
    'should propagate an error returned by the Observable',
    test(() => {
      const counter$ = new BehaviorSubject(1);
      const counter = toSignal(counter$);

      expect(counter()).toBe(1);

      counter$.error('fail');
      expect(counter).toThrow('fail');
    }),
  );

  it(
    'should unsubscribe when the current context is destroyed',
    test(() => {
      const counter$ = new BehaviorSubject(0);
      const injector = Injector.create({providers: []}) as EnvironmentInjector;
      const counter = runInInjectionContext(injector, () => toSignal(counter$));

      expect(counter()).toBe(0);
      counter$.next(1);
      expect(counter()).toBe(1);

      // Destroying the injector should unsubscribe the Observable.
      injector.destroy();

      // The signal should have the last value observed.
      expect(counter()).toBe(1);

      // And this value should no longer be updating (unsubscribed).
      counter$.next(2);
      expect(counter()).toBe(1);
    }),
  );

  it(
    'should unsubscribe when an explicitly provided injector is destroyed',
    test(() => {
      const counter$ = new BehaviorSubject(0);
      const injector = Injector.create({providers: []}) as EnvironmentInjector;
      const counter = toSignal(counter$, {injector});

      expect(counter()).toBe(0);
      counter$.next(1);
      expect(counter()).toBe(1);

      // Destroying the injector should unsubscribe the Observable.
      injector.destroy();

      // The signal should have the last value observed.
      expect(counter()).toBe(1);

      // And this value should no longer be updating (unsubscribed).
      counter$.next(2);
      expect(counter()).toBe(1);
    }),
  );

  it('should not require an injection context when manualCleanup is passed', () => {
    const counter$ = new BehaviorSubject(0);
    expect(() => toSignal(counter$, {manualCleanup: true})).not.toThrow();
    counter$.complete();
  });

  it('should not unsubscribe when manualCleanup is passed', () => {
    const counter$ = new BehaviorSubject(0);
    const injector = Injector.create({providers: []}) as EnvironmentInjector;
    const counter = runInInjectionContext(injector, () =>
      toSignal(counter$, {manualCleanup: true}),
    );

    injector.destroy();

    // Destroying the injector should not have unsubscribed the Observable.
    counter$.next(1);
    expect(counter()).toBe(1);

    counter$.complete();

    // The signal should have the last value observed before the observable completed.
    expect(counter()).toBe(1);
  });

  it('should not allow toSignal creation in a reactive context', () => {
    const counter$ = new BehaviorSubject(1);
    const doubleCounter = computed(() => {
      const counter = toSignal(counter$, {requireSync: true});
      return counter() * 2;
    });

    expect(() => doubleCounter()).toThrowError(
      /toSignal\(\) cannot be called from within a reactive context. Invoking `toSignal` causes new subscriptions every time./,
    );
  });

  it('should throw the error back to RxJS if rejectErrors is set', () => {
    let capturedObserver: Observer<number> = null!;
    const fake$ = {
      subscribe(observer: Observer<number>): Unsubscribable {
        capturedObserver = observer;
        return {unsubscribe(): void {}};
      },
    } as Subscribable<number>;

    const s = toSignal(fake$, {initialValue: 0, rejectErrors: true, manualCleanup: true});
    expect(s()).toBe(0);
    if (capturedObserver === null) {
      return fail('Observer not captured as expected.');
    }

    capturedObserver.next(1);
    expect(s()).toBe(1);

    expect(() => capturedObserver.error('test')).toThrow('test');
    expect(s()).toBe(1);
  });

  describe('with no initial value', () => {
    it(
      'should return `undefined` if read before a value is emitted',
      test(() => {
        const counter$ = new Subject<number>();
        const counter = toSignal(counter$);

        expect(counter()).toBeUndefined();
        counter$.next(1);
        expect(counter()).toBe(1);
      }),
    );

    it(
      'should not throw if a value is emitted before called',
      test(() => {
        const counter$ = new Subject<number>();
        const counter = toSignal(counter$);

        counter$.next(1);
        expect(() => counter()).not.toThrow();
      }),
    );
  });

  describe('with requireSync', () => {
    it(
      'should throw if created before a value is emitted',
      test(() => {
        const counter$ = new Subject<number>();
        expect(() => toSignal(counter$, {requireSync: true})).toThrow();
      }),
    );

    it(
      'should not throw if a value emits synchronously on creation',
      test(() => {
        const counter$ = new ReplaySubject<number>(1);
        counter$.next(1);
        const counter = toSignal(counter$);
        expect(counter()).toBe(1);
      }),
    );
  });

  describe('with an initial value', () => {
    it(
      'should return the initial value if called before a value is emitted',
      test(() => {
        const counter$ = new Subject<number>();
        const counter = toSignal(counter$, {initialValue: null});

        expect(counter()).toBeNull();
        counter$.next(1);
        expect(counter()).toBe(1);
      }),
    );

    it(
      'should not return the initial value if called after a value is emitted',
      test(() => {
        const counter$ = new Subject<number>();
        const counter = toSignal(counter$, {initialValue: null});

        counter$.next(1);
        expect(counter()).not.toBeNull();
      }),
    );
  });

  describe('with an equality function', () => {
    it(
      'should not update for values considered equal',
      test(() => {
        const counter$ = new Subject<{value: number}>();
        const counter = toSignal(counter$, {
          initialValue: {value: 0},
          equal: (a, b) => a.value === b.value,
        });

        let updates = 0;
        const tracker = computed(() => {
          updates++;
          return counter();
        });

        expect(tracker()).toEqual({value: 0});
        counter$.next({value: 1});
        expect(tracker()).toEqual({value: 1});
        expect(updates).toBe(2);

        counter$.next({value: 1}); // same value as before
        expect(tracker()).toEqual({value: 1});
        expect(updates).toBe(2); // no downstream changes, since value was equal.

        counter$.next({value: 2});
        expect(tracker()).toEqual({value: 2});
        expect(updates).toBe(3);
      }),
    );

    it(
      'should update when values are reference equal but equality function says otherwise',
      test(() => {
        const numsSet = new Set<number>();
        const nums$ = new BehaviorSubject<Set<number>>(numsSet);
        const nums = toSignal(nums$, {
          requireSync: true,
          equal: () => false,
        });

        let updates = 0;
        const tracker = computed(() => {
          updates++;
          return Array.from(nums()!.values());
        });

        expect(tracker()).toEqual([]);
        numsSet.add(1);
        nums$.next(numsSet); // same value as before
        expect(tracker()).toEqual([1]);
        expect(updates).toBe(2);
      }),
    );
  });

  describe('in a @Component', () => {
    it('should support `toSignal` as a class member initializer', () => {
      @Component({
        template: '{{counter()}}',
        changeDetection: ChangeDetectionStrategy.OnPush,
        standalone: false,
      })
      class TestCmp {
        // Component creation should not run inside the template effect/consumer,
        // hence using `toSignal` should be allowed/supported.
        counter$ = new Subject<number>();
        counter = toSignal(this.counter$);
      }

      const fixture = TestBed.createComponent(TestCmp);
      fixture.detectChanges();

      expect(fixture.nativeElement.textContent).toBe('');

      fixture.componentInstance.counter$.next(2);
      fixture.detectChanges();

      expect(fixture.nativeElement.textContent).toBe('2');
    });
  });


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  Component,
  computed,
  createEnvironmentInjector,
  EnvironmentInjector,
  Injector,
  Signal,
  signal,
} from '@angular/core';
import {toObservable} from '@angular/core/rxjs-interop';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {take, toArray} from 'rxjs/operators';

describe('toObservable()', () => {
  let fixture!: ComponentFixture<unknown>;
  let injector!: EnvironmentInjector;

  @Component({
    template: '',
    standalone: true,
  })
  class Cmp {}

  beforeEach(() => {
    fixture = TestBed.createComponent(Cmp);
    injector = TestBed.inject(EnvironmentInjector);
  });

  function flushEffects(): void {
    fixture.detectChanges();
  }

  it('should produce an observable that tracks a signal', async () => {
    const counter = signal(0);
    const counterValues = toObservable(counter, {injector}).pipe(take(3), toArray()).toPromise();

    // Initial effect execution, emits 0.
    flushEffects();

    counter.set(1);
    // Emits 1.
    flushEffects();

    counter.set(2);
    counter.set(3);
    // Emits 3 (ignores 2 as it was batched by the effect).
    flushEffects();

    expect(await counterValues).toEqual([0, 1, 3]);
  });

  it('should propagate errors from the signal', () => {
    const source = signal(1);
    const counter = computed(() => {
      const value = source();
      if (value === 2) {
        throw 'fail';
      } else {
        return value;
      }
    });

    const counter$ = toObservable(counter, {injector});

    let currentValue: number = 0;
    let currentError: any = null;

    const sub = counter$.subscribe({
      next: (value) => (currentValue = value),
      error: (err) => (currentError = err),
    });

    flushEffects();
    expect(currentValue).toBe(1);

    source.set(2);
    flushEffects();
    expect(currentError).toBe('fail');

    sub.unsubscribe();
  });

  it('monitors the signal even if the Observable is never subscribed', () => {
    let counterRead = false;
    const counter = computed(() => {
      counterRead = true;
      return 0;
    });

    toObservable(counter, {injector});

    // Simply creating the Observable shouldn't trigger a signal read.
    expect(counterRead).toBeFalse();

    // The signal is read after effects have run.
    flushEffects();
    expect(counterRead).toBeTrue();
  });

  it('should still monitor the signal if the Observable has no active subscribers', () => {
    const counter = signal(0);

    // Tracks how many reads of `counter()` there have been.
    let readCount = 0;
    const trackedCounter = computed(() => {
      readCount++;
      return counter();
    });

    const counter$ = toObservable(trackedCounter, {injector});

    const sub = counter$.subscribe();
    expect(readCount).toBe(0);

    flushEffects();
    expect(readCount).toBe(1);

    // Sanity check of the read tracker - updating the counter should cause it to be read again
    // by the active effect.
    counter.set(1);
    flushEffects();
    expect(readCount).toBe(2);

    // Tear down the only subscription.
    sub.unsubscribe();

    // Now, setting the signal still triggers additional reads
    counter.set(2);
    flushEffects();
    expect(readCount).toBe(3);
  });

  it('stops monitoring the signal once injector is destroyed', () => {
    const counter = signal(0);

    // Tracks how many reads of `counter()` there have been.
    let readCount = 0;
    const trackedCounter = computed(() => {
      readCount++;
      return counter();
    });

    const childInjector = createEnvironmentInjector([], injector);
    toObservable(trackedCounter, {injector: childInjector});

    expect(readCount).toBe(0);

    flushEffects();
    expect(readCount).toBe(1);

    // Now, setting the signal shouldn't trigger any additional reads, as the Injector was destroyed
    childInjector.destroy();
    counter.set(2);
    flushEffects();
    expect(readCount).toBe(1);
  });

  it('does not track downstream signal reads in the effect', () => {
    const counter = signal(0);
    const emits = signal(0);
    toObservable(counter, {injector}).subscribe(() => {
      // Read emits. If we are still tracked in the effect, this will cause an infinite loop by
      // triggering the effect again.
      emits();
      emits.update((v) => v + 1);
    });
    flushEffects();
    expect(emits()).toBe(1);
    flushEffects();
    expect(emits()).toBe(1);
  });
});


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  assertInInjectionContext,
  DestroyRef,
  effect,
  inject,
  Injector,
  Signal,
  untracked,
  ɵmicrotaskEffect as microtaskEffect,
} from '@angular/core';
import {Observable, ReplaySubject} from 'rxjs';

/**
 * Options for `toObservable`.
 *
 * @developerPreview
 */
export interface ToObservableOptions {
  /**
   * The `Injector` to use when creating the underlying `effect` which watches the signal.
   *
   * If this isn't specified, the current [injection context](guide/di/dependency-injection-context)
   * will be used.
   */
  injector?: Injector;
}

/**
 * Exposes the value of an Angular `Signal` as an RxJS `Observable`.
 *
 * The signal's value will be propagated into the `Observable`'s subscribers using an `effect`.
 *
 * `toObservable` must be called in an injection context unless an injector is provided via options.
 *
 * @developerPreview
 */
export function toObservable<T>(source: Signal<T>, options?: ToObservableOptions): Observable<T> {
  !options?.injector && assertInInjectionContext(toObservable);
  const injector = options?.injector ?? inject(Injector);
  const subject = new ReplaySubject<T>(1);

  const watcher = effect(
    () => {
      let value: T;
      try {
        value = source();
      } catch (err) {
        untracked(() => subject.error(err));
        return;
      }
      untracked(() => subject.next(value));
    },
    {injector, manualCleanup: true},
  );

  injector.get(DestroyRef).onDestroy(() => {
    watcher.destroy();
    subject.complete();
  });

  return subject.asObservable();
}

export function toObservableMicrotask<T>(
  source: Signal<T>,
  options?: ToObservableOptions,
): Observable<T> {
  !options?.injector && assertInInjectionContext(toObservable);
  const injector = options?.injector ?? inject(Injector);
  const subject = new ReplaySubject<T>(1);

  const watcher = microtaskEffect(
    () => {
      let value: T;
      try {
        value = source();
      } catch (err) {
        untracked(() => subject.error(err));
        return;
      }
      untracked(() => subject.next(value));
    },
    {injector, manualCleanup: true},
  );

  injector.get(DestroyRef).onDestroy(() => {
    watcher.destroy();
    subject.complete();
  });

  return subject.asObservable();
}


/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  assertInInjectionContext,
  assertNotInReactiveContext,
  computed,
  DestroyRef,
  inject,
  Injector,
  signal,
  Signal,
  WritableSignal,
  ɵRuntimeError,
  ɵRuntimeErrorCode,
} from '@angular/core';
import {ValueEqualityFn} from '@angular/core/primitives/signals';
import {Observable, Subscribable} from 'rxjs';

/**
 * Options for `toSignal`.
 *
 * @publicApi
 */
export interface ToSignalOptions<T> {
  /**
   * Initial value for the signal produced by `toSignal`.
   *
   * This will be the value of the signal until the observable emits its first value.
   */
  initialValue?: unknown;

  /**
   * Whether to require that the observable emits synchronously when `toSignal` subscribes.
   *
   * If this is `true`, `toSignal` will assert that the observable produces a value immediately upon
   * subscription. Setting this option removes the need to either deal with `undefined` in the
   * signal type or provide an `initialValue`, at the cost of a runtime error if this requirement is
   * not met.
   */
  requireSync?: boolean;

  /**
   * `Injector` which will provide the `DestroyRef` used to clean up the Observable subscription.
   *
   * If this is not provided, a `DestroyRef` will be retrieved from the current [injection
   * context](guide/di/dependency-injection-context), unless manual cleanup is requested.
   */
  injector?: Injector;

  /**
   * Whether the subscription should be automatically cleaned up (via `DestroyRef`) when
   * `toSignal`'s creation context is destroyed.
   *
   * If manual cleanup is enabled, then `DestroyRef` is not used, and the subscription will persist
   * until the `Observable` itself completes.
   */
  manualCleanup?: boolean;

  /**
   * Whether `toSignal` should throw errors from the Observable error channel back to RxJS, where
   * they'll be processed as uncaught exceptions.
   *
   * In practice, this means that the signal returned by `toSignal` will keep returning the last
   * good value forever, as Observables which error produce no further values. This option emulates
   * the behavior of the `async` pipe.
   */
  rejectErrors?: boolean;

  /**
   * A comparison function which defines equality for values emitted by the observable.
   *
   * Equality comparisons are executed against the initial value if one is provided.
   */
  equal?: ValueEqualityFn<T>;
}

// Base case: no options -> `undefined` in the result type.
export function toSignal<T>(source: Observable<T> | Subscribable<T>): Signal<T | undefined>;
// Options with `undefined` initial value and no `requiredSync` -> `undefined`.
export function toSignal<T>(
  source: Observable<T> | Subscribable<T>,
  options: NoInfer<ToSignalOptions<T | undefined>> & {
    initialValue?: undefined;
    requireSync?: false;
  },
): Signal<T | undefined>;
// Options with `null` initial value -> `null`.
export function toSignal<T>(
  source: Observable<T> | Subscribable<T>,
  options: NoInfer<ToSignalOptions<T | null>> & {initialValue?: null; requireSync?: false},
): Signal<T | null>;
// Options with `undefined` initial value and `requiredSync` -> strict result type.
export function toSignal<T>(
  source: Observable<T> | Subscribable<T>,
  options: NoInfer<ToSignalOptions<T>> & {initialValue?: undefined; requireSync: true},
): Signal<T>;
// Options with a more specific initial value type.
export function toSignal<T, const U extends T>(
  source: Observable<T> | Subscribable<T>,
  options: NoInfer<ToSignalOptions<T | U>> & {initialValue: U; requireSync?: false},
): Signal<T | U>;

/**
 * Get the current value of an `Observable` as a reactive `Signal`.
 *
 * `toSignal` returns a `Signal` which provides synchronous reactive access to values produced
 * by the given `Observable`, by subscribing to that `Observable`. The returned `Signal` will always
 * have the most recent value emitted by the subscription, and will throw an error if the
 * `Observable` errors.
 *
 * With `requireSync` set to `true`, `toSignal` will assert that the `Observable` produces a value
 * immediately upon subscription. No `initialValue` is needed in this case, and the returned signal
 * does not include an `undefined` type.
 *
 * By default, the subscription will be automatically cleaned up when the current [injection
 * context](guide/di/dependency-injection-context) is destroyed. For example, when `toSignal` is
 * called during the construction of a component, the subscription will be cleaned up when the
 * component is destroyed. If an injection context is not available, an explicit `Injector` can be
 * passed instead.
 *
 * If the subscription should persist until the `Observable` itself completes, the `manualCleanup`
 * option can be specified instead, which disables the automatic subscription teardown. No injection
 * context is needed in this configuration as well.
 *
 * @developerPreview
 */
e