escribe('ngIf directive', () => {
  let fixture: ComponentFixture<any>;

  function getComponent(): TestComponent {
    return fixture.componentInstance;
  }

  afterEach(() => {
    fixture = null!;
  });

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [TestComponent],
      imports: [CommonModule],
    });
  });

  it('should work in a template attribute', waitForAsync(() => {
    const template = 'hello';
    fixture = createTestComponent(template);
    fixture.detectChanges();
    expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
    expect(fixture.nativeElement).toHaveText('hello');
  }));

  it('should work on a template element', waitForAsync(() => {
    const template = '<ng-template [ngIf]="booleanCondition">hello2</ng-template>';
    fixture = createTestComponent(template);
    fixture.detectChanges();
    expect(fixture.nativeElement).toHaveText('hello2');
  }));

  it('should toggle node when condition changes', waitForAsync(() => {
    const template = 'hello';
    fixture = createTestComponent(template);
    getComponent().booleanCondition = false;
    fixture.detectChanges();
    expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
    expect(fixture.nativeElement).toHaveText('');

    getComponent().booleanCondition = true;
    fixture.detectChanges();
    expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
    expect(fixture.nativeElement).toHaveText('hello');

    getComponent().booleanCondition = false;
    fixture.detectChanges();
    expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
    expect(fixture.nativeElement).toHaveText('');
  }));

  it('should handle nested if correctly', waitForAsync(() => {
    const template =
      '
hello
';

    fixture = createTestComponent(template);

    getComponent().booleanCondition = false;
    fixture.detectChanges();
    expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
    expect(fixture.nativeElement).toHaveText('');

    getComponent().booleanCondition = true;
    fixture.detectChanges();
    expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
    expect(fixture.nativeElement).toHaveText('hello');

    getComponent().nestedBooleanCondition = false;
    fixture.detectChanges();
    expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
    expect(fixture.nativeElement).toHaveText('');

    getComponent().nestedBooleanCondition = true;
    fixture.detectChanges();
    expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
    expect(fixture.nativeElement).toHaveText('hello');

    getComponent().booleanCondition = false;
    fixture.detectChanges();
    expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
    expect(fixture.nativeElement).toHaveText('');
  }));

  it('should update several nodes with if', waitForAsync(() => {
    const template =
      '= 2">helloNumber' +
      'helloString' +
      'helloFunction';

    fixture = createTestComponent(template);

    fixture.detectChanges();
    expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(3);
    expect(fixture.nativeElement.textContent).toEqual('helloNumberhelloStringhelloFunction');

    getComponent().numberCondition = 0;
    fixture.detectChanges();
    expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
    expect(fixture.nativeElement).toHaveText('helloString');

    getComponent().numberCondition = 1;
    getComponent().stringCondition = 'bar';
    fixture.detectChanges();
    expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
    expect(fixture.nativeElement).toHaveText('helloNumber');
  }));

  it('should not add the element twice if the condition goes from truthy to truthy', waitForAsync(() => {
    const template = 'hello';

    fixture = createTestComponent(template);

    fixture.detectChanges();
    let els = fixture.debugElement.queryAll(By.css('span'));
    expect(els.length).toEqual(1);
    els[0].nativeElement.classList.add('marker');
    expect(fixture.nativeElement).toHaveText('hello');

    getComponent().numberCondition = 2;
    fixture.detectChanges();
    els = fixture.debugElement.queryAll(By.css('span'));
    expect(els.length).toEqual(1);
    expect(els[0].nativeElement.classList.contains('marker')).toBe(true);

    expect(fixture.nativeElement).toHaveText('hello');
  }));

  describe('then/else templates', () => {
    it('should support else', waitForAsync(() => {
      const template =
        'TRUE' +
        '<ng-template #elseBlock>FALSE</ng-template>';

      fixture = createTestComponent(template);

      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('TRUE');

      getComponent().booleanCondition = false;
      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('FALSE');
    }));

    it('should support then and else', waitForAsync(() => {
      const template =
        'IGNORE' +
        '<ng-template #thenBlock>THEN</ng-template>' +
        '<ng-template #elseBlock>ELSE</ng-template>';

      fixture = createTestComponent(template);

      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('THEN');

      getComponent().booleanCondition = false;
      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('ELSE');
    }));

    it('should support removing the then/else templates', () => {
      const template = `
        <ng-template #tplRef>Template</ng-template>`;

      fixture = createTestComponent(template);
      const comp = fixture.componentInstance;
      // then template
      comp.booleanCondition = true;

      comp.nestedBooleanCondition = true;
      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('Template');

      comp.nestedBooleanCondition = false;
      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('');

      // else template
      comp.booleanCondition = true;

      comp.nestedBooleanCondition = true;
      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('Template');

      comp.nestedBooleanCondition = false;
      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('');
    });

    it('should support dynamic else', waitForAsync(() => {
      const template =
        'TRUE' +
        '<ng-template #b1>FALSE1</ng-template>' +
        '<ng-template #b2>FALSE2</ng-template>';

      fixture = createTestComponent(template);

      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('TRUE');

      getComponent().booleanCondition = false;
      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('FALSE1');

      getComponent().nestedBooleanCondition = false;
      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('FALSE2');
    }));

    it('should support binding to variable using let', waitForAsync(() => {
      const template =
        '{{v}}' +
        '<ng-template #elseBlock let-v>{{v}}</ng-template>';

      fixture = createTestComponent(template);

      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('true');

      getComponent().booleanCondition = false;
      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('false');
    }));

    it('should support binding to variable using as', waitForAsync(() => {
      const template =
        '{{v}}' +
        '<ng-template #elseBlock let-v>{{v}}</ng-template>';

      fixture = createTestComponent(template);

      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('true');

      getComponent().booleanCondition = false;
      fixture.detectChanges();
      expect(fixture.nativeElement).toHaveText('false');
    }));

    it('should be available as a standalone directive', () => {
      @Component({
        selector: 'test-component',
        imports: [NgIf],
        template: `

Hello

World

        `,
        standalone: true,
      })
      class TestComponent {}

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

      expect(fixture.nativeElement.textContent).toBe('Hello');
      expect(fixture.nativeElement.textContent).not.toBe('World');
    });
  });


it('should display last item correctly', waitForAsync(() => {
    const template =
      '{{isLast.toString()}}';
    fixture = createTestComponent(template);

    getComponent().items = [0, 1, 2];
    detectChangesAndExpectText('falsefalsetrue');

    getComponent().items = [2, 1];
    detectChangesAndExpectText('falsetrue');
  }));

  it('should display even items correctly', waitForAsync(() => {
    const template =
      '{{isEven.toString()}}';
    fixture = createTestComponent(template);

    getComponent().items = [0, 1, 2];
    detectChangesAndExpectText('truefalsetrue');

    getComponent().items = [2, 1];
    detectChangesAndExpectText('truefalse');
  }));

  it('should display odd items correctly', waitForAsync(() => {
    const template = '{{isOdd.toString()}}';
    fixture = createTestComponent(template);

    getComponent().items = [0, 1, 2, 3];
    detectChangesAndExpectText('falsetruefalsetrue');

    getComponent().items = [2, 1];
    detectChangesAndExpectText('falsetrue');
  }));

  it('should allow to use a custom template', waitForAsync(() => {
    const template =
      '<ng-container *ngFor="let item of items; template: tpl"></ng-container>' +
      '<ng-template let-item let-i="index" #tpl>
{{i}}: {{item}};
</ng-template>';
    fixture = createTestComponent(template);
    getComponent().items = ['a', 'b', 'c'];
    fixture.detectChanges();
    detectChangesAndExpectText('0: a;1: b;2: c;');
  }));

  it('should use a default template if a custom one is null', waitForAsync(() => {
    const template = `<ul><ng-container *ngFor="let item of items; template: null; let i=index">{{i}}: {{item}};</ng-container></ul>`;
    fixture = createTestComponent(template);
    getComponent().items = ['a', 'b', 'c'];
    fixture.detectChanges();
    detectChangesAndExpectText('0: a;1: b;2: c;');
  }));

  it('should use a custom template when both default and a custom one are present', waitForAsync(() => {
    const template =
      '<ng-container *ngFor="let item of items; template: tpl">{{i}};</ng-container>' +
      '<ng-template let-item let-i="index" #tpl>{{i}}: {{item}};</ng-template>';
    fixture = createTestComponent(template);
    getComponent().items = ['a', 'b', 'c'];
    fixture.detectChanges();
    detectChangesAndExpectText('0: a;1: b;2: c;');
  }));

  describe('track by', () => {
    it('should console.warn if trackBy is not a function', waitForAsync(() => {
      // TODO(vicb): expect a warning message when we have a proper log service
      const template = `<p *ngFor="let item of items; trackBy: value"></p>`;
      fixture = createTestComponent(template);
      fixture.componentInstance.value = 0;
      fixture.detectChanges();
    }));

    it('should track by identity when trackBy is to `null` or `undefined`', waitForAsync(() => {
      // TODO(vicb): expect no warning message when we have a proper log service
      const template = `<p *ngFor="let item of items; trackBy: value">{{ item }}</p>`;
      fixture = createTestComponent(template);
      fixture.componentInstance.items = ['a', 'b', 'c'];
      fixture.componentInstance.value = null;
      detectChangesAndExpectText('abc');
      fixture.componentInstance.value = undefined;
      detectChangesAndExpectText('abc');
    }));

    it('should set the context to the component instance', waitForAsync(() => {
      const template = `<p *ngFor="let item of items; trackBy: trackByContext.bind(this)"></p>`;
      fixture = createTestComponent(template);

      thisArg = null;
      fixture.detectChanges();
      expect(thisArg).toBe(getComponent());
    }));

    it('should not replace tracked items', waitForAsync(() => {
      const template = `<p *ngFor="let item of items; trackBy: trackById; let i=index">{{items[i]}}</p>`;
      fixture = createTestComponent(template);

      const buildItemList = () => {
        getComponent().items = [{'id': 'a'}];
        fixture.detectChanges();
        return fixture.debugElement.queryAll(By.css('p'))[0];
      };

      const firstP = buildItemList();
      const finalP = buildItemList();
      expect(finalP.nativeElement).toBe(firstP.nativeElement);
    }));

    it('should update implicit local variable on view', waitForAsync(() => {
      const template = `<div *ngFor="let item of items; trackBy: trackById">{{item['color']}}</div>`;
      fixture = createTestComponent(template);

      getComponent().items = [{'id': 'a', 'color': 'blue'}];
      detectChangesAndExpectText('blue');

      getComponent().items = [{'id': 'a', 'color': 'red'}];
      detectChangesAndExpectText('red');
    }));

    it('should move items around and keep them updated ', waitForAsync(() => {
      const template = `<div *ngFor="let item of items; trackBy: trackById">{{item['color']}}</div>`;
      fixture = createTestComponent(template);

      getComponent().items = [
        {'id': 'a', 'color': 'blue'},
        {'id': 'b', 'color': 'yellow'},
      ];
      detectChangesAndExpectText('blueyellow');

      getComponent().items = [
        {'id': 'b', 'color': 'orange'},
        {'id': 'a', 'color': 'red'},
      ];
      detectChangesAndExpectText('orangered');
    }));

    it('should handle added and removed items properly when tracking by index', waitForAsync(() => {
      const template = `<div *ngFor="let item of items; trackBy: trackByIndex">{{item}}</div>`;
      fixture = createTestComponent(template);

      getComponent().items = ['a', 'b', 'c', 'd'];
      fixture.detectChanges();
      getComponent().items = ['e', 'f', 'g', 'h'];
      fixture.detectChanges();
      getComponent().items = ['e', 'f', 'h'];
      detectChangesAndExpectText('efh');
    }));
  });

  it('should be available as a standalone directive', () => {
    @Component({
      selector: 'test-component',
      imports: [NgForOf],
      template: ` <ng-container *ngFor="let item of items">{{ item }}|</ng-container> `,
      standalone: true,
    })
    class TestComponent {
      items = [1, 2, 3];
    }

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

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

  it('should be available as a standalone directive using an `NgFor` alias', () => {
    @Component({
      selector: 'test-component',
      imports: [NgFor],
      template: ` <ng-container *ngFor="let item of items">{{ item }}|</ng-container> `,
      standalone: true,
    })
    class TestComponent {
      items = [1, 2, 3];
    }

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

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

class Foo {
  toString() {
    return 'foo';
  }
}

@Component({
  selector: 'test-cmp',
  template: '',
  standalone: false,
})
class TestComponent {
  value: any;
  items: any[] = [1, 2];
  trackById(index: number, item: any): string {
    return item['id'];
  }
  trackByIndex(index: number, item: any): number {
    return index;
  }
  trackByContext(): void {
    thisArg = this;
  }
}

const TEMPLATE = '
{{item.toString()}};
';

function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> {
  return TestBed.overrideComponent(TestComponent, {set: {template: template}}).createComponent(
    TestComponent,
  );
}


/**
 * @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 {AsyncPipe} from '@angular/common';
import {ChangeDetectorRef, Component, computed, EventEmitter, signal} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {Observable, of, Subscribable, Unsubscribable} from 'rxjs';

describe('AsyncPipe', () => {
  let pipe: AsyncPipe;
  let ref: ChangeDetectorRef & jasmine.SpyObj<ChangeDetectorRef>;

  function getChangeDetectorRefSpy() {
    return jasmine.createSpyObj('ChangeDetectorRef', ['markForCheck', 'detectChanges']);
  }

  beforeEach(() => {
    ref = getChangeDetectorRefSpy();
    pipe = new AsyncPipe(ref);
  });

  afterEach(() => {
    pipe.ngOnDestroy(); // Close all subscriptions.
  });

  describe('Observable', () => {
    // only expose methods from the Subscribable interface, to ensure that
    // the implementation does not rely on other methods:
    const wrapSubscribable = <T>(input: Subscribable<T>): Subscribable<T> => ({
      subscribe(...args: any): Unsubscribable {
        const subscription = input.subscribe.apply(input, args);
        return {
          unsubscribe() {
            subscription.unsubscribe();
          },
        };
      },
    });

    let emitter: EventEmitter<any>;
    let subscribable: Subscribable<any>;
    const message = {};

    beforeEach(() => {
      emitter = new EventEmitter();
      subscribable = wrapSubscribable(emitter);
    });

    describe('transform', () => {
      it('should return null when subscribing to an observable', () => {
        expect(pipe.transform(subscribable)).toBe(null);
      });

      it('should return the latest available value', (done) => {
        pipe.transform(subscribable);
        emitter.emit(message);

        setTimeout(() => {
          expect(pipe.transform(subscribable)).toEqual(message);
          done();
        }, 0);
      });

      it('should return same value when nothing has changed since the last call', (done) => {
        pipe.transform(subscribable);
        emitter.emit(message);

        setTimeout(() => {
          pipe.transform(subscribable);
          expect(pipe.transform(subscribable)).toBe(message);
          done();
        }, 0);
      });

      it('should dispose of the existing subscription when subscribing to a new observable', (done) => {
        pipe.transform(subscribable);

        const newEmitter = new EventEmitter();
        const newSubscribable = wrapSubscribable(newEmitter);
        expect(pipe.transform(newSubscribable)).toBe(null);
        emitter.emit(message);

        // this should not affect the pipe
        setTimeout(() => {
          expect(pipe.transform(newSubscribable)).toBe(null);
          done();
        }, 0);
      });

      it('should request a change detection check upon receiving a new value', (done) => {
        pipe.transform(subscribable);
        emitter.emit(message);

        setTimeout(() => {
          expect(ref.markForCheck).toHaveBeenCalled();
          done();
        }, 10);
      });

      it('should return value for unchanged NaN', () => {
        emitter.emit(null);
        pipe.transform(subscribable);
        emitter.next(NaN);
        const firstResult = pipe.transform(subscribable);
        const secondResult = pipe.transform(subscribable);
        expect(firstResult).toBeNaN();
        expect(secondResult).toBeNaN();
      });

      it('should not track signal reads in subscriptions', () => {
        const trigger = signal(false);

        const obs = new Observable(() => {
          // Whenever `obs` is subscribed, synchronously read `trigger`.
          trigger();
        });

        let trackCount = 0;
        const tracker = computed(() => {
          // Subscribe to `obs` within this `computed`. If the subscription side effect runs
          // within the computed, then changes to `trigger` will invalidate this computed.
          pipe.transform(obs);

          // The computed returns how many times it's run.
          return ++trackCount;
        });

        expect(tracker()).toBe(1);
        trigger.set(true);
        expect(tracker()).toBe(1);
      });
    });

    describe('ngOnDestroy', () => {
      it('should do nothing when no subscription', () => {
        expect(() => pipe.ngOnDestroy()).not.toThrow();
      });

      it('should dispose of the existing subscription', (done) => {
        pipe.transform(subscribable);
        pipe.ngOnDestroy();
        emitter.emit(message);

        setTimeout(() => {
          expect(pipe.transform(subscribable)).toBe(null);
          done();
        }, 0);
      });
    });
  });

  describe('Subscribable', () => {
    it('should infer the type from the subscribable', () => {
      const emitter = new EventEmitter<{name: 'T'}>();
      // The following line will fail to compile if the type cannot be inferred.
      const name = pipe.transform(emitter)?.name;
    });
  });

  describe('Promise', () => {
    const message = {};
    let resolve: (result: any) => void;
    let reject: (error: any) => void;
    let promise: Promise<any>;
    // adds longer timers for passing tests in IE
    const timer = 10;

    beforeEach(() => {
      promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
      });
    });

    describe('transform', () => {
      it('should return null when subscribing to a promise', () => {
        expect(pipe.transform(promise)).toBe(null);
      });

      it('should return the latest available value', (done) => {
        pipe.transform(promise);

        resolve(message);

        setTimeout(() => {
          expect(pipe.transform(promise)).toEqual(message);
          done();
        }, timer);
      });

      it('should return value when nothing has changed since the last call', (done) => {
        pipe.transform(promise);
        resolve(message);

        setTimeout(() => {
          pipe.transform(promise);
          expect(pipe.transform(promise)).toBe(message);
          done();
        }, timer);
      });

      it('should dispose of the existing subscription when subscribing to a new promise', (done) => {
        pipe.transform(promise);

        promise = new Promise<any>(() => {});
        expect(pipe.transform(promise)).toBe(null);

        resolve(message);

        setTimeout(() => {
          expect(pipe.transform(promise)).toBe(null);
          done();
        }, timer);
      });

      it('should request a change detection check upon receiving a new value', (done) => {
        pipe.transform(promise);
        resolve(message);

        setTimeout(() => {
          expect(ref.markForCheck).toHaveBeenCalled();
          done();
        }, timer);
      });

      describe('ngOnDestroy', () => {
        it('should do nothing when no source', () => {
          expect(() => pipe.ngOnDestroy()).not.toThrow();
        });

        it('should dispose of the existing source', (done) => {
          pipe.transform(promise);
          expect(pipe.transform(promise)).toBe(null);
          resolve(message);

          setTimeout(() => {
            expect(pipe.transform(promise)).toEqual(message);
            pipe.ngOnDestroy();
            expect(pipe.transform(promise)).toBe(null);
            done();
          }, timer);
        });

        it('should ignore signals after the pipe has been destroyed', (done) => {
          pipe.transform(promise);
          expect(pipe.transform(promise)).toBe(null);
          pipe.ngOnDestroy();
          resolve(message);

          setTimeout(() => {
            expect(pipe.transform(promise)).toBe(null);
            done();
          }, timer);
        });
      });
    });
  });

  describe('null', () => {
    it('should return null when given null', () => {
      expect(pipe.transform(null)).toEqual(null);
    });
  });

  describe('undefined', () => {
    it('should return null when given undefined', () => {
      expect(pipe.transform(undefined)).toEqual(null);
    });
  });

  describe('other types', () => {
    it('should throw when given an invalid object', () => {
      expect(() => pipe.transform('some bogus object' as any)).toThrowError();
    });
  });

  it('should be available as a standalone pipe', () => {
    @Component({
      selector: 'test-component',
      imports: [AsyncPipe],
      template: '{{ value | async }}',
      standalone: true,
    })
    class TestComponent {
      value = of('foo');
    }

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

    const content = fixture.nativeElement.textContent;
    expect(content).toBe('foo');
  });
});


Implements an HTTP client API for Angular apps that relies on the `XMLHttpRequest` interface exposed by browsers.

Includes testability features, typed request and response objects, request and response interception,
observable APIs, and streamlined error handling.

For usage information, see the [HTTP Client](guide/http) guide.


/**
 * @license
 * Copyright Google LLC All Rights Reserved.sonpCallbackContext
 *
 * 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 {HttpHandler} from '@angular/common/http/src/backend';
import {HttpClient} from '@angular/common/http/src/client';
import {HttpContext, HttpContextToken} from '@angular/common/http/src/context';
import {HTTP_INTERCEPTORS, HttpInterceptor} from '@angular/common/http/src/interceptor';
import {HttpRequest} from '@angular/common/http/src/request';
import {HttpEvent, HttpResponse} from '@angular/common/http/src/response';
import {HttpTestingController} from '@angular/common/http/testing/src/api';
import {HttpClientTestingModule} from '@angular/common/http/testing/src/module';
import {TestRequest} from '@angular/common/http/testing/src/request';
import {Injectable, Injector} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';

const IS_INTERCEPTOR_C_ENABLED = new HttpContextToken<boolean | undefined>(() => undefined);

class TestInterceptor implements HttpInterceptor {
  constructor(private value: string) {}

  intercept(req: HttpRequest<any>, delegate: HttpHandler): Observable<HttpEvent<any>> {
    const existing = req.headers.get('Intercepted');
    const next = !!existing ? existing + ',' + this.value : this.value;
    req = req.clone({setHeaders: {'Intercepted': next}});
    return delegate.handle(req).pipe(
      map((event) => {
        if (event instanceof HttpResponse) {
          const existing = event.headers.get('Intercepted');
          const next = !!existing ? existing + ',' + this.value : this.value;
          return event.clone({headers: event.headers.set('Intercepted', next)});
        }
        return event;
      }),
    );
  }
}

class InterceptorA extends TestInterceptor {
  constructor() {
    super('A');
  }
}

class InterceptorB extends TestInterceptor {
  constructor() {
    super('B');
  }
}

class InterceptorC extends TestInterceptor {
  constructor() {
    super('C');
  }

  override intercept(req: HttpRequest<any>, delegate: HttpHandler): Observable<HttpEvent<any>> {
    if (req.context.get(IS_INTERCEPTOR_C_ENABLED) === true) {
      return super.intercept(req, delegate);
    }
    return delegate.handle(req);
  }
}

@Injectable()
class ReentrantInterceptor implements HttpInterceptor {
  constructor(private client: HttpClient) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req);
  }
}

describe('HttpClientModule', () => {
  let injector: Injector;
  beforeEach(() => {
    injector = TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        {provide: HTTP_INTERCEPTORS, useClass: InterceptorA, multi: true},
        {provide: HTTP_INTERCEPTORS, useClass: InterceptorB, multi: true},
        {provide: HTTP_INTERCEPTORS, useClass: InterceptorC, multi: true},
      ],
    });
  });
  it('initializes HttpClient properly', (done) => {
    injector
      .get(HttpClient)
      .get('/test', {responseType: 'text'})
      .subscribe((value: string) => {
        expect(value).toBe('ok!');
        done();
      });
    injector.get(HttpTestingController).expectOne('/test').flush('ok!');
  });
  it('intercepts outbound responses in the order in which interceptors were bound', (done) => {
    injector
      .get(HttpClient)
      .get('/test', {observe: 'response', responseType: 'text'})
      .subscribe(() => done());
    const req = injector.get(HttpTestingController).expectOne('/test') as TestRequest;
    expect(req.request.headers.get('Intercepted')).toEqual('A,B');
    req.flush('ok!');
  });
  it('intercepts outbound responses in the order in which interceptors were bound and include specifically enabled interceptor', (done) => {
    injector
      .get(HttpClient)
      .get('/test', {
        observe: 'response',
        responseType: 'text',
        context: new HttpContext().set(IS_INTERCEPTOR_C_ENABLED, true),
      })
      .subscribe((value) => done());
    const req = injector.get(HttpTestingController).expectOne('/test') as TestRequest;
    expect(req.request.headers.get('Intercepted')).toEqual('A,B,C');
    req.flush('ok!');
  });
  it('intercepts inbound responses in the right (reverse binding) order', (done) => {
    injector
      .get(HttpClient)
      .get('/test', {observe: 'response', responseType: 'text'})
      .subscribe((value: HttpResponse<string>) => {
        expect(value.headers.get('Intercepted')).toEqual('B,A');
        done();
      });
    injector.get(HttpTestingController).expectOne('/test').flush('ok!');
  });
  it('allows interceptors to inject HttpClient', (done) => {
    TestBed.resetTestingModule();
    injector = TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [{provide: HTTP_INTERCEPTORS, useClass: ReentrantInterceptor, multi: true}],
    });
    injector
      .get(HttpClient)
      .get('/test')
      .subscribe(() => {
        done();
      });
    injector.get(HttpTestingController).expectOne('/test').flush('ok!');
  });
});


describe('provideHttpClient', () => {
  beforeEach(() => {
    setCookie('');
    TestBed.resetTestingModule();
  });

  afterEach(() => {
    let controller: HttpTestingController;
    try {
      controller = TestBed.inject(HttpTestingController);
    } catch (err) {
      // A failure here means that TestBed wasn't successfully configured. Some tests intentionally
      // test configuration errors and therefore exit without setting up TestBed for HTTP, so just
      // exit here without performing verification on the `HttpTestingController` in that case.
      return;
    }
    controller.verify();
  });

  it('should configure HttpClient', () => {
    TestBed.configureTestingModule({
      providers: [provideHttpClient(), provideHttpClientTesting()],
    });

    TestBed.inject(HttpClient).get('/test', {responseType: 'text'}).subscribe();
    TestBed.inject(HttpTestingController).expectOne('/test').flush('');
  });

  it('should not contribute to stability', () => {
    TestBed.configureTestingModule({
      providers: [provideHttpClient(), provideHttpClientTesting()],
    });

    let stable = false;
    TestBed.inject(ApplicationRef).isStable.subscribe((v) => {
      stable = v;
    });

    expect(stable).toBe(true);
    TestBed.inject(HttpClient).get('/test', {responseType: 'text'}).subscribe();
    expect(stable).toBe(true);
    TestBed.inject(HttpTestingController).expectOne('/test').flush('');
    expect(stable).toBe(true);
  });

  it('should not use legacy interceptors by default', () => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideLegacyInterceptor('legacy'),
        provideHttpClientTesting(),
      ],
    });

    TestBed.inject(HttpClient).get('/test', {responseType: 'text'}).subscribe();
    const req = TestBed.inject(HttpTestingController).expectOne('/test');
    expect(req.request.headers.has('X-Tag')).toBeFalse();
    req.flush('');
  });

  it('withInterceptorsFromDi() should enable legacy interceptors', () => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(withInterceptorsFromDi()),
        provideLegacyInterceptor('alpha'),
        provideLegacyInterceptor('beta'),
        provideHttpClientTesting(),
      ],
    });

    TestBed.inject(HttpClient).get('/test', {responseType: 'text'}).subscribe();
    const req = TestBed.inject(HttpTestingController).expectOne('/test');
    expect(req.request.headers.get('X-Tag')).toEqual('alpha,beta');
    req.flush('');
  });

  describe('interceptor functions', () => {
    it('should allow configuring interceptors', () => {
      TestBed.configureTestingModule({
        providers: [
          provideHttpClient(
            withInterceptors([
              makeLiteralTagInterceptorFn('alpha'),
              makeLiteralTagInterceptorFn('beta'),
            ]),
          ),
          provideHttpClientTesting(),
        ],
      });

      TestBed.inject(HttpClient).get('/test', {responseType: 'text'}).subscribe();
      const req = TestBed.inject(HttpTestingController).expectOne('/test');
      expect(req.request.headers.get('X-Tag')).toEqual('alpha,beta');
      req.flush('');
    });

    it('should accept multiple separate interceptor configs', () => {
      TestBed.configureTestingModule({
        providers: [
          provideHttpClient(
            withInterceptors([makeLiteralTagInterceptorFn('alpha')]),
            withInterceptors([makeLiteralTagInterceptorFn('beta')]),
          ),
          provideHttpClientTesting(),
        ],
      });

      TestBed.inject(HttpClient).get('/test', {responseType: 'text'}).subscribe();
      const req = TestBed.inject(HttpTestingController).expectOne('/test');
      expect(req.request.headers.get('X-Tag')).toEqual('alpha,beta');
      req.flush('');
    });

    it('should allow injection from an interceptor context', () => {
      const ALPHA = new InjectionToken<string>('alpha', {
        providedIn: 'root',
        factory: () => 'alpha',
      });
      const BETA = new InjectionToken<string>('beta', {providedIn: 'root', factory: () => 'beta'});

      TestBed.configureTestingModule({
        providers: [
          provideHttpClient(
            withInterceptors([makeTokenTagInterceptorFn(ALPHA), makeTokenTagInterceptorFn(BETA)]),
          ),
          provideHttpClientTesting(),
        ],
      });

      TestBed.inject(HttpClient).get('/test', {responseType: 'text'}).subscribe();
      const req = TestBed.inject(HttpTestingController).expectOne('/test');
      expect(req.request.headers.get('X-Tag')).toEqual('alpha,beta');
      req.flush('');
    });

    it('should allow combination with legacy interceptors, before the legacy stack', () => {
      TestBed.configureTestingModule({
        providers: [
          provideHttpClient(
            withInterceptors([makeLiteralTagInterceptorFn('functional')]),
            withInterceptorsFromDi(),
          ),
          provideHttpClientTesting(),
          provideLegacyInterceptor('legacy'),
        ],
      });

      TestBed.inject(HttpClient).get('/test', {responseType: 'text'}).subscribe();
      const req = TestBed.inject(HttpTestingController).expectOne('/test');
      expect(req.request.headers.get('X-Tag')).toEqual('functional,legacy');
      req.flush('');
    });

    it('should allow combination with legacy interceptors, after the legacy stack', () => {
      TestBed.configureTestingModule({
        providers: [
          provideHttpClient(
            withInterceptorsFromDi(),
            withInterceptors([makeLiteralTagInterceptorFn('functional')]),
          ),
          provideHttpClientTesting(),
          provideLegacyInterceptor('legacy'),
        ],
      });

      TestBed.inject(HttpClient).get('/test', {responseType: 'text'}).subscribe();
      const req = TestBed.inject(HttpTestingController).expectOne('/test');
      expect(req.request.headers.get('X-Tag')).toEqual('legacy,functional');
      req.flush('');
    });
  });

  describe('xsrf protection', () => {
    it('should enable xsrf protection by default', () => {
      TestBed.configureTestingModule({
        providers: [
          provideHttpClient(),
          provideHttpClientTesting(),
          {provide: PLATFORM_ID, useValue: 'test'},
        ],
      });

      setXsrfToken('abcdefg');

      TestBed.inject(HttpClient).post('/test', '', {responseType: 'text'}).subscribe();
      const req = TestBed.inject(HttpTestingController).expectOne('/test');
      expect(req.request.headers.get('X-XSRF-TOKEN')).toEqual('abcdefg');
      req.flush('');
    });

    it('withXsrfConfiguration() should allow customization of xsrf config', () => {
      TestBed.configureTestingModule({
        providers: [
          provideHttpClient(
            withXsrfConfiguration({
              cookieName: 'XSRF-CUSTOM-COOKIE',
              headerName: 'X-Custom-Xsrf-Header',
            }),
          ),
          provideHttpClientTesting(),
          {provide: PLATFORM_ID, useValue: 'test'},
        ],
      });

      setCookie('XSRF-CUSTOM-COOKIE=abcdefg');
      TestBed.inject(HttpClient).post('/test', '', {responseType: 'text'}).subscribe();
      const req = TestBed.inject(HttpTestingController).expectOne('/test');
      expect(req.request.headers.get('X-Custom-Xsrf-Header')).toEqual('abcdefg');
      req.flush('');
    });

    it('withNoXsrfProtection() should disable xsrf protection', () => {
      TestBed.configureTestingModule({
        providers: [
          provideHttpClient(withNoXsrfProtection()),
          provideHttpClientTesting(),
          {provide: PLATFORM_ID, useValue: 'test'},
        ],
      });
      setXsrfToken('abcdefg');

      TestBed.inject(HttpClient).post('/test', '', {responseType: 'text'}).subscribe();
      const req = TestBed.inject(HttpTestingController).expectOne('/test');
      expect(req.request.headers.has('X-Custom-Xsrf-Header')).toBeFalse();
      req.flush('');
    });

    it('should error if withXsrfConfiguration() and withNoXsrfProtection() are combined', () => {
      expect(() => {
        TestBed.configureTestingModule({
          providers: [
            provideHttpClient(withNoXsrfProtection(), withXsrfConfiguration({})),
            provideHttpClientTesting(),
            {provide: PLATFORM_ID, useValue: 'test'},
          ],
        });
      }).toThrow();
    });
  });

  describe('JSONP support', () => {
    it('should not be enabled by default', () => {
      TestBed.configureTestingModule({
        providers: [provideHttpClient(), provideHttpClientTesting()],
      });

      TestBed.inject(HttpClient).jsonp('/test', 'callback').subscribe();

      // Because no JSONP interceptor should be registered, this request should go to the testing
      // backend.
      TestBed.inject(HttpTestingController).expectOne('/test?callback=JSONP_CALLBACK').flush('');
    });

    it('should be enabled when using withJsonpSupport()', () => {
      TestBed.configureTestingModule({
        providers: [
          provideHttpClient(withJsonpSupport()),
          provideHttpClientTesting(),
          FAKE_JSONP_BACKEND_PROVIDER,
        ],
      });

      TestBed.inject(HttpClient).jsonp('/test', 'callback').subscribe();
      TestBed.inject(HttpTestingController).expectNone('/test?callback=JSONP_CALLBACK');
    });
  });


/**
 * @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 {HttpClientModule} from '@angular/common/http';
import {NgModule} from '@angular/core';

import {provideHttpClientTesting} from './provider';

/**
 * Configures `HttpClientTestingBackend` as the `HttpBackend` used by `HttpClient`.
 *
 * Inject `HttpTestingController` to expect and flush requests in your tests.
 *
 * @publicApi
 *
 * @deprecated Add `provideHttpClientTesting()` to your providers instead.
 */
@NgModule({
  imports: [HttpClientModule],
  providers: [provideHttpClientTesting()],
})
export class HttpClientTestingModule {}


/**
 * @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 {ModuleWithProviders, NgModule} from '@angular/core';

import {HTTP_INTERCEPTORS} from './interceptor';
import {
  provideHttpClient,
  withInterceptorsFromDi,
  withJsonpSupport,
  withNoXsrfProtection,
  withXsrfConfiguration,
} from './provider';
import {
  HttpXsrfCookieExtractor,
  HttpXsrfInterceptor,
  HttpXsrfTokenExtractor,
  XSRF_DEFAULT_COOKIE_NAME,
  XSRF_DEFAULT_HEADER_NAME,
  XSRF_ENABLED,
} from './xsrf';

/**
 * Configures XSRF protection support for outgoing requests.
 *
 * For a server that supports a cookie-based XSRF protection system,
 * use directly to configure XSRF protection with the correct
 * cookie and header names.
 *
 * If no names are supplied, the default cookie name is `XSRF-TOKEN`
 * and the default header name is `X-XSRF-TOKEN`.
 *
 * @publicApi
 * @deprecated Use withXsrfConfiguration({cookieName: 'XSRF-TOKEN', headerName: 'X-XSRF-TOKEN'}) as
 *     providers instead or `withNoXsrfProtection` if you want to disabled XSRF protection.
 */
@NgModule({
  providers: [
    HttpXsrfInterceptor,
    {provide: HTTP_INTERCEPTORS, useExisting: HttpXsrfInterceptor, multi: true},
    {provide: HttpXsrfTokenExtractor, useClass: HttpXsrfCookieExtractor},
    withXsrfConfiguration({
      cookieName: XSRF_DEFAULT_COOKIE_NAME,
      headerName: XSRF_DEFAULT_HEADER_NAME,
    }).ɵproviders,
    {provide: XSRF_ENABLED, useValue: true},
  ],
})
export class HttpClientXsrfModule {
  /**
   * Disable the default XSRF protection.
   */
  static disable(): ModuleWithProviders<HttpClientXsrfModule> {
    return {
      ngModule: HttpClientXsrfModule,
      providers: [withNoXsrfProtection().ɵproviders],
    };
  }

  /**
   * Configure XSRF protection.
   * @param options An object that can specify either or both
   * cookie name or header name.
   * - Cookie name default is `XSRF-TOKEN`.
   * - Header name default is `X-XSRF-TOKEN`.
   *
   */
  static withOptions(
    options: {
      cookieName?: string;
      headerName?: string;
    } = {},
  ): ModuleWithProviders<HttpClientXsrfModule> {
    return {
      ngModule: HttpClientXsrfModule,
      providers: withXsrfConfiguration(options).ɵproviders,
    };
  }
}

/**
 * Configures the dependency injector for `HttpClient`
 * with supporting services for XSRF. Automatically imported by `HttpClientModule`.
 *
 * You can add interceptors to the chain behind `HttpClient` by binding them to the
 * multiprovider for built-in DI token `HTTP_INTERCEPTORS`.
 *
 * @publicApi
 * @deprecated use `provideHttpClient(withInterceptorsFromDi())` as providers instead
 */
@NgModule({
  /**
   * Configures the dependency injector where it is imported
   * with supporting services for HTTP communications.
   */
  providers: [provideHttpClient(withInterceptorsFromDi())],
})
export class HttpClientModule {}

/**
 * Configures the dependency injector for `HttpClient`
 * with supporting services for JSONP.
 * Without this module, Jsonp requests reach the backend
 * with method JSONP, where they are rejected.
 *
 * @publicApi
 * @deprecated `withJsonpSupport()` as providers instead
 */
@NgModule({
  providers: [withJsonpSupport().ɵproviders],
})
export class HttpClientJsonpModule {}


/**
 * @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 {XhrFactory} from '@angular/common';
import {Injectable, ɵRuntimeError as RuntimeError} from '@angular/core';
import {from, Observable, Observer, of} from 'rxjs';
import {switchMap} from 'rxjs/operators';

import {HttpBackend} from './backend';
import {RuntimeErrorCode} from './errors';
import {HttpHeaders} from './headers';
import {HttpRequest} from './request';
import {
  HTTP_STATUS_CODE_NO_CONTENT,
  HTTP_STATUS_CODE_OK,
  HttpDownloadProgressEvent,
  HttpErrorResponse,
  HttpEvent,
  HttpEventType,
  HttpHeaderResponse,
  HttpJsonParseError,
  HttpResponse,
  HttpUploadProgressEvent,
} from './response';

const XSSI_PREFIX = /^\)\]\}',?\n/;

/**
 * Determine an appropriate URL for the response, by checking either
 * XMLHttpRequest.responseURL or the X-Request-URL header.
 */
function getResponseUrl(xhr: any): string | null {
  if ('responseURL' in xhr && xhr.responseURL) {
    return xhr.responseURL;
  }
  if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) {
    return xhr.getResponseHeader('X-Request-URL');
  }
  return null;
}

/**
 * Uses `XMLHttpRequest` to send requests to a backend server.
 * @see {@link HttpHandler}
 * @see {@link JsonpClientBackend}
 *
 * @publicApi
 */
@Injectable()
export class HttpXhrBackend implements HttpBackend {
  constructor(private xhrFactory: XhrFactory) {}

  /**
   * Processes a request and returns a stream of response events.
   * @param req The request object.
   * @returns An observable of the response events.
   */
  handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
    // Quick check to give a better error message when a user attempts to use
    // HttpClient.jsonp() without installing the HttpClientJsonpModule
    if (req.method === 'JSONP') {
      throw new RuntimeError(
        RuntimeErrorCode.MISSING_JSONP_MODULE,
        (typeof ngDevMode === 'undefined' || ngDevMode) &&
          `Cannot make a JSONP request without JSONP support. To fix the problem, either add the \`withJsonpSupport()\` call (if \`provideHttpClient()\` is used) or import the \`HttpClientJsonpModule\` in the root NgModule.`,
      );
    }

    // Check whether this factory has a special function to load an XHR implementation
    // for various non-browser environments. We currently limit it to only `ServerXhr`
    // class, which needs to load an XHR implementation.
    const xhrFactory: XhrFactory & {ɵloadImpl?: () => Promise<void>} = this.xhrFactory;
    const source: Observable<void | null> = xhrFactory.ɵloadImpl
      ? from(xhrFactory.ɵloadImpl())
      : of(null);


/**
 * @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 {
  EnvironmentProviders,
  inject,
  InjectionToken,
  makeEnvironmentProviders,
  Provider,
} from '@angular/core';

import {HttpBackend, HttpHandler} from './backend';
import {HttpClient} from './client';
import {FetchBackend} from './fetch';
import {
  HTTP_INTERCEPTOR_FNS,
  HttpInterceptorFn,
  HttpInterceptorHandler,
  legacyInterceptorFnFactory,
} from './interceptor';
import {
  jsonpCallbackContext,
  JsonpCallbackContext,
  JsonpClientBackend,
  jsonpInterceptorFn,
} from './jsonp';
import {HttpXhrBackend} from './xhr';
import {
  HttpXsrfCookieExtractor,
  HttpXsrfTokenExtractor,
  XSRF_COOKIE_NAME,
  XSRF_ENABLED,
  XSRF_HEADER_NAME,
  xsrfInterceptorFn,
} from './xsrf';

/**
 * Identifies a particular kind of `HttpFeature`.
 *
 * @publicApi
 */
export enum HttpFeatureKind {
  Interceptors,
  LegacyInterceptors,
  CustomXsrfConfiguration,
  NoXsrfProtection,
  JsonpSupport,
  RequestsMadeViaParent,
  Fetch,
}

/**
 * A feature for use when configuring `provideHttpClient`.
 *
 * @publicApi
 */
export interface HttpFeature<KindT extends HttpFeatureKind> {
  ɵkind: KindT;
  ɵproviders: Provider[];
}

function makeHttpFeature<KindT extends HttpFeatureKind>(
  kind: KindT,
  providers: Provider[],
): HttpFeature<KindT> {
  return {
    ɵkind: kind,
    ɵproviders: providers,
  };
}

/**
 * Configures Angular's `HttpClient` service to be available for injection.
 *
 * By default, `HttpClient` will be configured for injection with its default options for XSRF
 * protection of outgoing requests. Additional configuration options can be provided by passing
 * feature functions to `provideHttpClient`. For example, HTTP interceptors can be added using the
 * `withInterceptors(...)` feature.
 *
 * <div class="alert is-helpful">
 *
 * It's strongly recommended to enable
 * [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) for applications that use
 * Server-Side Rendering for better performance and compatibility. To enable `fetch`, add
 * `withFetch()` feature to the `provideHttpClient()` call at the root of the application:
 *
 * ```
 * provideHttpClient(withFetch());
 * ```
 *
 * </div>
 *
 * @see {@link withInterceptors}
 * @see {@link withInterceptorsFromDi}
 * @see {@link withXsrfConfiguration}
 * @see {@link withNoXsrfProtection}
 * @see {@link withJsonpSupport}
 * @see {@link withRequestsMadeViaParent}
 * @see {@link withFetch}
 */
export function provideHttpClient(
  ...features: HttpFeature<HttpFeatureKind>[]
): EnvironmentProviders {
  if (ngDevMode) {
    const featureKinds = new Set(features.map((f) => f.ɵkind));
    if (
      featureKinds.has(HttpFeatureKind.NoXsrfProtection) &&
      featureKinds.has(HttpFeatureKind.CustomXsrfConfiguration)
    ) {
      throw new Error(
        ngDevMode
          ? `Configuration error: found both withXsrfConfiguration() and withNoXsrfProtection() in the same call to provideHttpClient(), which is a contradiction.`
          : '',
      );
    }
  }

  const providers: Provider[] = [
    HttpClient,
    HttpXhrBackend,
    HttpInterceptorHandler,
    {provide: HttpHandler, useExisting: HttpInterceptorHandler},
    {
      provide: HttpBackend,
      useFactory: () => {
        return inject(FetchBackend, {optional: true}) ?? inject(HttpXhrBackend);
      },
    },
    {
      provide: HTTP_INTERCEPTOR_FNS,
      useValue: xsrfInterceptorFn,
      multi: true,
    },
    {provide: XSRF_ENABLED, useValue: true},
    {provide: HttpXsrfTokenExtractor, useClass: HttpXsrfCookieExtractor},
  ];

  for (const feature of features) {
    providers.push(...feature.ɵproviders);
  }

  return makeEnvironmentProviders(providers);
}

/**
 * Adds one or more functional-style HTTP interceptors to the configuration of the `HttpClient`
 * instance.
 *
 * @see {@link HttpInterceptorFn}
 * @see {@link provideHttpClient}
 * @publicApi
 */
export function withInterceptors(
  interceptorFns: HttpInterceptorFn[],
): HttpFeature<HttpFeatureKind.Interceptors> {
  return makeHttpFeature(
    HttpFeatureKind.Interceptors,
    interceptorFns.map((interceptorFn) => {
      return {
        provide: HTTP_INTERCEPTOR_FNS,
        useValue: interceptorFn,
        multi: true,
      };
    }),
  );
}

const LEGACY_INTERCEPTOR_FN = new InjectionToken<HttpInterceptorFn>(
  ngDevMode ? 'LEGACY_INTERCEPTOR_FN' : '',
);

/**
 * Includes class-based interceptors configured using a multi-provider in the current injector into
 * the configured `HttpClient` instance.
 *
 * Prefer `withInterceptors` and functional interceptors instead, as support for DI-provided
 * interceptors may be phased out in a later release.
 *
 * @see {@link HttpInterceptor}
 * @see {@link HTTP_INTERCEPTORS}
 * @see {@link provideHttpClient}
 */
export function withInterceptorsFromDi(): HttpFeature<HttpFeatureKind.LegacyInterceptors> {
  // Note: the legacy interceptor function is provided here via an intermediate token
  // (`LEGACY_INTERCEPTOR_FN`), using a pattern which guarantees that if these providers are
  // included multiple times, all of the multi-provider entries will have the same instance of the
  // interceptor function. That way, the `HttpINterceptorHandler` will dedup them and legacy
  // interceptors will not run multiple times.
  return makeHttpFeature(HttpFeatureKind.LegacyInterceptors, [
    {
      provide: LEGACY_INTERCEPTOR_FN,
      useFactory: legacyInterceptorFnFactory,
    },
    {
      provide: HTTP_INTERCEPTOR_FNS,
      useExisting: LEGACY_INTERCEPTOR_FN,
      multi: true,
    },
  ]);
}

/**
 * Customizes the XSRF protection for the configuration of the current `HttpClient` instance.
 *
 * This feature is incompatible with the `withNoXsrfProtection` feature.
 *
 * @see {@link provideHttpClient}
 */
export function withXsrfConfiguration({
  cookieName,
  headerName,
}: {
  cookieName?: string;
  headerName?: string;
}): HttpFeature<HttpFeatureKind.CustomXsrfConfiguration> {
  const providers: Provider[] = [];
  if (cookieName !== undefined) {
    providers.push({provide: XSRF_COOKIE_NAME, useValue: cookieName});
  }
  if (headerName !== undefined) {
    providers.push({provide: XSRF_HEADER_NAME, useValue: headerName});
  }

  return makeHttpFeature(HttpFeatureKind.CustomXsrfConfiguration, providers);
}

/**
 * Disables XSRF protection in the configuration of the current `HttpClient` instance.
 *
 * This feature is incompatible with the `withXsrfConfiguration` feature.
 *
 * @see {@link provideHttpClient}
 */
export function withNoXsrfProtection(): HttpFeature<HttpFeatureKind.NoXsrfProtection> {
  return makeHttpFeature(HttpFeatureKind.NoXsrfProtection, [
    {
      provide: XSRF_ENABLED,
      useValue: false,
    },
  ]);
}

/**
 * Add JSONP support to the configuration of the current `HttpClient` instance.
 *
 * @see {@link provideHttpClient}
 */
export function withJsonpSupport(): HttpFeature<HttpFeatureKind.JsonpSupport> {
  return makeHttpFeature(HttpFeatureKind.JsonpSupport, [
    JsonpClientBackend,
    {provide: JsonpCallbackContext, useFactory: jsonpCallbackContext},
    {provide: HTTP_INTERCEPTOR_FNS, useValue: jsonpInterceptorFn, multi: true},
  ]);
}

/**


/**
 * @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
 */

export const PLATFORM_BROWSER_ID = 'browser';
export const PLATFORM_SERVER_ID = 'server';

/**
 * Returns whether a platform id represents a browser platform.
 * @publicApi
 */
export function isPlatformBrowser(platformId: Object): boolean {
  return platformId === PLATFORM_BROWSER_ID;
}

/**
 * Returns whether a platform id represents a server platform.
 * @publicApi
 */
export function isPlatformServer(platformId: Object): boolean {
  return platformId === PLATFORM_SERVER_ID;
}


/**
 * @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 {inject, PLATFORM_ID, ɵɵdefineInjectable} from '@angular/core';

import {DOCUMENT} from './dom_tokens';
import {isPlatformBrowser} from './platform_id';

/**
 * Defines a scroll position manager. Implemented by `BrowserViewportScroller`.
 *
 * @publicApi
 */
export abstract class ViewportScroller {
  // De-sugared tree-shakable injection
  // See #23917
  /** @nocollapse */
  static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({
    token: ViewportScroller,
    providedIn: 'root',
    factory: () =>
      isPlatformBrowser(inject(PLATFORM_ID))
        ? new BrowserViewportScroller(inject(DOCUMENT), window)
        : new NullViewportScroller(),
  });

  /**
   * Configures the top offset used when scrolling to an anchor.
   * @param offset A position in screen coordinates (a tuple with x and y values)
   * or a function that returns the top offset position.
   *
   */
  abstract setOffset(offset: [number, number] | (() => [number, number])): void;

  /**
   * Retrieves the current scroll position.
   * @returns A position in screen coordinates (a tuple with x and y values).
   */
  abstract getScrollPosition(): [number, number];

  /**
   * Scrolls to a specified position.
   * @param position A position in screen coordinates (a tuple with x and y values).
   */
  abstract scrollToPosition(position: [number, number]): void;

  /**
   * Scrolls to an anchor element.
   * @param anchor The ID of the anchor element.
   */
  abstract scrollToAnchor(anchor: string): void;

  /**
   * Disables automatic scroll restoration provided by the browser.
   * See also [window.history.scrollRestoration
   * info](https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration).
   */
  abstract setHistoryScrollRestoration(scrollRestoration: 'auto' | 'manual'): void;
}

/**
 * Manages the scroll position for a browser window.
 */
export class BrowserViewportScroller implements ViewportScroller {
  private offset: () => [number, number] = () => [0, 0];

  constructor(
    private document: Document,
    private window: Window,
  ) {}

  /**
   * Configures the top offset used when scrolling to an anchor.
   * @param offset A position in screen coordinates (a tuple with x and y values)
   * or a function that returns the top offset position.
   *
   */
  setOffset(offset: [number, number] | (() => [number, number])): void {
    if (Array.isArray(offset)) {
      this.offset = () => offset;
    } else {
      this.offset = offset;
    }
  }

  /**
   * Retrieves the current scroll position.
   * @returns The position in screen coordinates.
   */
  getScrollPosition(): [number, number] {
    return [this.window.scrollX, this.window.scrollY];
  }

  /**
   * Sets the scroll position.
   * @param position The new position in screen coordinates.
   */
  scrollToPosition(position: [number, number]): void {
    this.window.scrollTo(position[0], position[1]);
  }

  /**
   * Scrolls to an element and attempts to focus the element.
   *
   * Note that the function name here is misleading in that the target string may be an ID for a
   * non-anchor element.
   *
   * @param target The ID of an element or name of the anchor.
   *
   * @see https://html.spec.whatwg.org/#the-indicated-part-of-the-document
   * @see https://html.spec.whatwg.org/#scroll-to-fragid
   */
  scrollToAnchor(target: string): void {
    const elSelected = findAnchorFromDocument(this.document, target);

    if (elSelected) {
      this.scrollToElement(elSelected);
      // After scrolling to the element, the spec dictates that we follow the focus steps for the
      // target. Rather than following the robust steps, simply attempt focus.
      //
      // @see https://html.spec.whatwg.org/#get-the-focusable-area
      // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/focus
      // @see https://html.spec.whatwg.org/#focusable-area
      elSelected.focus();
    }
  }

  /**
   * Disables automatic scroll restoration provided by the browser.
   */
  setHistoryScrollRestoration(scrollRestoration: 'auto' | 'manual'): void {
    this.window.history.scrollRestoration = scrollRestoration;
  }

  /**
   * Scrolls to an element using the native offset and the specified offset set on this scroller.
   *
   * The offset can be used when we know that there is a floating header and scrolling naively to an
   * element (ex: `scrollIntoView`) leaves the element hidden behind the floating header.
   */
  private scrollToElement(el: HTMLElement): void {
    const rect = el.getBoundingClientRect();
    const left = rect.left + this.window.pageXOffset;
    const top = rect.top + this.window.pageYOffset;
    const offset = this.offset();
    this.window.scrollTo(left - offset[0], top - offset[1]);
  }
}

function findAnchorFromDocument(document: Document, target: string): HTMLElement | null {
  const documentResult = document.getElementById(target) || document.getElementsByName(target)[0];

  if (documentResult) {
    return documentResult;
  }

  // `getElementById` and `getElementsByName` won't pierce through the shadow DOM so we
  // have to traverse the DOM manually and do the lookup through the shadow roots.
  if (
    typeof document.createTreeWalker === 'function' &&
    document.body &&
    typeof document.body.attachShadow === 'function'
  ) {
    const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT);
    let currentNode = treeWalker.currentNode as HTMLElement | null;

    while (currentNode) {
      const shadowRoot = currentNode.shadowRoot;

      if (shadowRoot) {
        // Note that `ShadowRoot` doesn't support `getElementsByName`
        // so we have to fall back to `querySelector`.
        const result =
          shadowRoot.getElementById(target) || shadowRoot.querySelector(`[name="${target}"]`);
        if (result) {
          return result;
        }
      }

      currentNode = treeWalker.nextNode() as HTMLElement | null;
    }
  }

  return null;
}

/**
 * Provides an empty implementation of the viewport scroller.
 */
export class NullViewportScroller implements ViewportScroller {
  /**
   * Empty implementation
   */
  setOffset(offset: [number, number] | (() => [number, number])): void {}

  /**
   * Empty implementation
   */
  getScrollPosition(): [number, number] {
    return [0, 0];
  }

  /**
   * Empty implementation
   */
  scrollToPosition(position: [number, number]): void {}

  /**
   * Empty implementation
   */
  scrollToAnchor(anchor: string): void {}

  /**
   * Empty implementation
   */
  setHistoryScrollRestoration(scrollRestoration: 'auto' | 'manual'): void {}
}


/**
 * @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 {NgModule} from '@angular/core';

import {COMMON_DIRECTIVES} from './directives/index';
import {COMMON_PIPES} from './pipes/index';

// Note: This does not contain the location providers,
// as they need some platform specific implementations to work.
/**
 * Exports all the basic Angular directives and pipes,
 * such as `NgIf`, `NgForOf`, `DecimalPipe`, and so on.
 * Re-exported by `BrowserModule`, which is included automatically in the root
 * `AppModule` when you create a new app with the CLI `new` command.
 *
 * @publicApi
 */
@NgModule({
  imports: [COMMON_DIRECTIVES, COMMON_PIPES],
  exports: [COMMON_DIRECTIVES, COMMON_PIPES],
})
export class CommonModule {}


/**
 * @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 {
  ComponentRef,
  createNgModule,
  Directive,
  DoCheck,
  Injector,
  Input,
  NgModuleFactory,
  NgModuleRef,
  OnChanges,
  OnDestroy,
  SimpleChanges,
  Type,
  ViewContainerRef,
} from '@angular/core';

/**
 * Instantiates a {@link Component} type and inserts its Host View into the current View.
 * `NgComponentOutlet` provides a declarative approach for dynamic component creation.
 *
 * `NgComponentOutlet` requires a component type, if a falsy value is set the view will clear and
 * any existing component will be destroyed.
 *
 * @usageNotes
 *
 * ### Fine tune control
 *
 * You can control the component creation process by using the following optional attributes:
 *
 * * `ngComponentOutletInputs`: Optional component inputs object, which will be bind to the
 * component.
 *
 * * `ngComponentOutletInjector`: Optional custom {@link Injector} that will be used as parent for
 * the Component. Defaults to the injector of the current view container.
 *
 * * `ngComponentOutletContent`: Optional list of projectable nodes to insert into the content
 * section of the component, if it exists.
 *
 * * `ngComponentOutletNgModule`: Optional NgModule class reference to allow loading another
 * module dynamically, then loading a component from that module.
 *
 * * `ngComponentOutletNgModuleFactory`: Deprecated config option that allows providing optional
 * NgModule factory to allow loading another module dynamically, then loading a component from that
 * module. Use `ngComponentOutletNgModule` instead.
 *
 * ### Syntax
 *
 * Simple
 * ```
 * <ng-container *ngComponentOutlet="componentTypeExpression"></ng-container>
 * ```
 *
 * With inputs
 * ```
 * <ng-container *ngComponentOutlet="componentTypeExpression;
 *                                   inputs: inputsExpression;">
 * </ng-container>
 * ```
 *
 * Customized injector/content
 * ```
 * <ng-container *ngComponentOutlet="componentTypeExpression;
 *                                   injector: injectorExpression;
 *                                   content: contentNodesExpression;">
 * </ng-container>
 * ```
 *
 * Customized NgModule reference
 * ```
 * <ng-container *ngComponentOutlet="componentTypeExpression;
 *                                   ngModule: ngModuleClass;">
 * </ng-container>
 * ```
 *
 * ### A simple example
 *
 * {@example common/ngComponentOutlet/ts/module.ts region='SimpleExample'}
 *
 * A more complete example with additional options:
 *
 * {@example common/ngComponentOutlet/ts/module.ts region='CompleteExample'}
 *
 * @publicApi
 * @ngModule CommonModule
 */
@Directive({
  selector: '[ngComponentOutlet]',
  standalone: true,
})
export class NgComponentOutlet implements OnChanges, DoCheck, OnDestroy {
  @Input() ngComponentOutlet: Type<any> | null = null;

  @Input() ngComponentOutletInputs?: Record<string, unknown>;
  @Input() ngComponentOutletInjector?: Injector;
  @Input() ngComponentOutletContent?: any[][];

  @Input() ngComponentOutletNgModule?: Type<any>;
  /**
   * @deprecated This input is deprecated, use `ngComponentOutletNgModule` instead.
   */
  @Input() ngComponentOutletNgModuleFactory?: NgModuleFactory<any>;

  private _componentRef: ComponentRef<any> | undefined;
  private _moduleRef: NgModuleRef<any> | undefined;

  /**
   * A helper data structure that allows us to track inputs that were part of the
   * ngComponentOutletInputs expression. Tracking inputs is necessary for proper removal of ones
   * that are no longer referenced.
   */
  private _inputsUsed = new Map<string, boolean>();

  constructor(private _viewContainerRef: ViewContainerRef) {}

  private _needToReCreateNgModuleInstance(changes: SimpleChanges): boolean {
    // Note: square brackets property accessor is safe for Closure compiler optimizations (the
    // `changes` argument of the `ngOnChanges` lifecycle hook retains the names of the fields that
    // were changed).
    return (
      changes['ngComponentOutletNgModule'] !== undefined ||
      changes['ngComponentOutletNgModuleFactory'] !== undefined
    );
  }

  private _needToReCreateComponentInstance(changes: SimpleChanges): boolean {
    // Note: square brackets property accessor is safe for Closure compiler optimizations (the
    // `changes` argument of the `ngOnChanges` lifecycle hook retains the names of the fields that
    // were changed).
    return (
      changes['ngComponentOutlet'] !== undefined ||
      changes['ngComponentOutletContent'] !== undefined ||
      changes['ngComponentOutletInjector'] !== undefined ||
      this._needToReCreateNgModuleInstance(changes)
    );
  }

  /** @nodoc */
  ngOnChanges(changes: SimpleChanges) {
    if (this._needToReCreateComponentInstance(changes)) {
      this._viewContainerRef.clear();
      this._inputsUsed.clear();
      this._componentRef = undefined;

      if (this.ngComponentOutlet) {
        const injector = this.ngComponentOutletInjector || this._viewContainerRef.parentInjector;

        if (this._needToReCreateNgModuleInstance(changes)) {
          this._moduleRef?.destroy();

          if (this.ngComponentOutletNgModule) {
            this._moduleRef = createNgModule(
              this.ngComponentOutletNgModule,
              getParentInjector(injector),
            );
          } else if (this.ngComponentOutletNgModuleFactory) {
            this._moduleRef = this.ngComponentOutletNgModuleFactory.create(
              getParentInjector(injector),
            );
          } else {
            this._moduleRef = undefined;
          }
        }

        this._componentRef = this._viewContainerRef.createComponent(this.ngComponentOutlet, {
          injector,
          ngModuleRef: this._moduleRef,
          projectableNodes: this.ngComponentOutletContent,
        });
      }
    }
  }

  /** @nodoc */
  ngDoCheck() {
    if (this._componentRef) {
      if (this.ngComponentOutletInputs) {
        for (const inputName of Object.keys(this.ngComponentOutletInputs)) {
          this._inputsUsed.set(inputName, true);
        }
      }

      this._applyInputStateDiff(this._componentRef);
    }
  }

  /** @nodoc */
  ngOnDestroy() {
    this._moduleRef?.destroy();
  }

  private _applyInputStateDiff(componentRef: ComponentRef<unknown>) {
    for (const [inputName, touched] of this._inputsUsed) {
      if (!touched) {
        // The input that was previously active no longer exists and needs to be set to undefined.
        componentRef.setInput(inputName, undefined);
        this._inputsUsed.delete(inputName);
      } else {
        // Since touched is true, it can be asserted that the inputs object is not empty.
        componentRef.setInput(inputName, this.ngComponentOutletInputs![inputName]);
        this._inputsUsed.set(inputName, false);
      }
    }
  }
}

// Helper function that returns an Injector instance of a parent NgModule.
function getParentInjector(injector: Injector): Injector {
  const parentNgModule = injector.get(NgModuleRef);
  return parentNgModule.injector;
}


/**
 * @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 {
  Directive,
  DoCheck,
  EmbeddedViewRef,
  Input,
  IterableChangeRecord,
  IterableChanges,
  IterableDiffer,
  IterableDiffers,
  NgIterable,
  TemplateRef,
  TrackByFunction,
  ViewContainerRef,
  ɵRuntimeError as RuntimeError,
} from '@angular/core';

import {RuntimeErrorCode} from '../errors';

/**
 * @publicApi
 */
export class NgForOfContext<T, U extends NgIterable<T> = NgIterable<T>> {
  constructor(
    /** Reference to the current item from the collection. */
    public $implicit: T,

    /**
     * The value of the iterable expression. Useful when the expression is
     * more complex then a property access, for example when using the async pipe
     * (`userStreams | async`).
     */
    public ngForOf: U,

    /** Returns an index of the current item in the collection. */
    public index: number,

    /** Returns total amount of items in the collection. */
    public count: number,
  ) {}

  // Indicates whether this is the first item in the collection.
  get first(): boolean {
    return this.index === 0;
  }

  // Indicates whether this is the last item in the collection.
  get last(): boolean {
    return this.index === this.count - 1;
  }

  // Indicates whether an index of this item in the collection is even.
  get even(): boolean {
    return this.index % 2 === 0;
  }

  // Indicates whether an index of this item in the collection is odd.
  get odd(): boolean {
    return !this.even;
  }
}

/**
 * A [structural directive](guide/directives/structural-directives) that renders
 * a template for each item in a collection.
 * The directive is placed on an element, which becomes the parent
 * of the cloned templates.
 *
 * The `ngForOf` directive is generally used in the
 * [shorthand form](guide/directives/structural-directives#asterisk) `*ngFor`.
 * In this form, the template to be rendered for each iteration is the content
 * of an anchor element containing the directive.
 *
 * The following example shows the shorthand syntax with some options,
 * contained in an `<li>` element.
 *
 * ```
 * <li *ngFor="let item of items; index as i; trackBy: trackByFn">...</li>
 * ```
 *
 * The shorthand form expands into a long form that uses the `ngForOf` selector
 * on an `<ng-template>` element.
 * The content of the `<ng-template>` element is the `<li>` element that held the
 * short-form directive.
 *
 * Here is the expanded version of the short-form example.
 *
 * ```
 * <ng-template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
 *   <li>...</li>
 * </ng-template>
 * ```
 *
 * Angular automatically expands the shorthand syntax as it compiles the template.
 * The context for each embedded view is logically merged to the current component
 * context according to its lexical position.
 *
 * When using the shorthand syntax, Angular allows only [one structural directive
 * on an element](guide/directives/structural-directives#one-per-element).
 * If you want to iterate conditionally, for example,
 * put the `*ngIf` on a container element that wraps the `*ngFor` element.
 * For further discussion, see
 * [Structural Directives](guide/directives/structural-directives#one-per-element).
 *
 * @usageNotes
 *
 * ### Local variables
 *
 * `NgForOf` provides exported values that can be aliased to local variables.
 * For example:
 *
 *  ```
 * <li *ngFor="let user of users; index as i; first as isFirst">
 *    {{i}}/{{users.length}}. {{user}} <span *ngIf="isFirst">default</span>
 * </li>
 * ```
 *
 * The following exported values can be aliased to local variables:
 *
 * - `$implicit: T`: The value of the individual items in the iterable (`ngForOf`).
 * - `ngForOf: NgIterable<T>`: The value of the iterable expression. Useful when the expression is
 * more complex then a property access, for example when using the async pipe (`userStreams |
 * async`).
 * - `index: number`: The index of the current item in the iterable.
 * - `count: number`: The length of the iterable.
 * - `first: boolean`: True when the item is the first item in the iterable.
 * - `last: boolean`: True when the item is the last item in the iterable.
 * - `even: boolean`: True when the item has an even index in the iterable.
 * - `odd: boolean`: True when the item has an odd index in the iterable.
 *
 * ### Change propagation
 *
 * When the contents of the iterator changes, `NgForOf` makes the corresponding changes to the DOM:
 *
 * * When an item is added, a new instance of the template is added to the DOM.
 * * When an item is removed, its template instance is removed from the DOM.
 * * When items are reordered, their respective templates are reordered in the DOM.
 *
 * Angular uses object identity to track insertions and deletions within the iterator and reproduce
 * those changes in the DOM. This has important implications for animations and any stateful
 * controls that are present, such as `<input>` elements that accept user input. Inserted rows can
 * be animated in, deleted rows can be animated out, and unchanged rows retain any unsaved state
 * such as user input.
 * For more on animations, see [Transitions and Triggers](guide/animations/transition-and-triggers).
 *
 * The identities of elements in the iterator can change while the data does not.
 * This can happen, for example, if the iterator is produced from an RPC to the server, and that
 * RPC is re-run. Even if the data hasn't changed, the second response produces objects with
 * different identities, and Angular must tear down the entire DOM and rebuild it (as if all old
 * elements were deleted and all new elements inserted).
 *
 * To avoid this expensive operation, you can customize the default tracking algorithm.
 * by supplying the `trackBy` option to `NgForOf`.
 * `trackBy` takes a function that has two arguments: `index` and `item`.
 * If `trackBy` is given, Angular tracks changes by the return value of the function.
 *
 * @see [Structural Directives](guide/directives/structural-directives)
 * @ngModule CommonModule
 * @publicApi
 */


Directive({
  selector: '[ngFor][ngForOf]',
  standalone: true,
})
export class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCheck {
  /**
   * The value of the iterable expression, which can be used as a
   * [template input variable](guide/directives/structural-directives#shorthand).
   */
  @Input()
  set ngForOf(ngForOf: (U & NgIterable<T>) | undefined | null) {
    this._ngForOf = ngForOf;
    this._ngForOfDirty = true;
  }
  /**
   * Specifies a custom `TrackByFunction` to compute the identity of items in an iterable.
   *
   * If a custom `TrackByFunction` is not provided, `NgForOf` will use the item's [object
   * identity](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)
   * as the key.
   *
   * `NgForOf` uses the computed key to associate items in an iterable with DOM elements
   * it produces for these items.
   *
   * A custom `TrackByFunction` is useful to provide good user experience in cases when items in an
   * iterable rendered using `NgForOf` have a natural identifier (for example, custom ID or a
   * primary key), and this iterable could be updated with new object instances that still
   * represent the same underlying entity (for example, when data is re-fetched from the server,
   * and the iterable is recreated and re-rendered, but most of the data is still the same).
   *
   * @see {@link TrackByFunction}
   */
  @Input()
  set ngForTrackBy(fn: TrackByFunction<T>) {
    if ((typeof ngDevMode === 'undefined' || ngDevMode) && fn != null && typeof fn !== 'function') {
      console.warn(
        `trackBy must be a function, but received ${JSON.stringify(fn)}. ` +
          `See https://angular.io/api/common/NgForOf#change-propagation for more information.`,
      );
    }
    this._trackByFn = fn;
  }

  get ngForTrackBy(): TrackByFunction<T> {
    return this._trackByFn;
  }

  private _ngForOf: U | undefined | null = null;
  private _ngForOfDirty: boolean = true;
  private _differ: IterableDiffer<T> | null = null;
  // TODO(issue/24571): remove '!'
  // waiting for microsoft/typescript#43662 to allow the return type `TrackByFunction|undefined` for
  // the getter
  private _trackByFn!: TrackByFunction<T>;

  constructor(
    private _viewContainer: ViewContainerRef,
    private _template: TemplateRef<NgForOfContext<T, U>>,
    private _differs: IterableDiffers,
  ) {}

  /**
   * A reference to the template that is stamped out for each item in the iterable.
   * @see [template reference variable](guide/templates/variables#template-reference-variables)
   */
  @Input()
  set ngForTemplate(value: TemplateRef<NgForOfContext<T, U>>) {
    // TODO(TS2.1): make TemplateRef<Partial<NgForRowOf<T>>> once we move to TS v2.1
    // The current type is too restrictive; a template that just uses index, for example,
    // should be acceptable.
    if (value) {
      this._template = value;
    }
  }

  /**
   * Applies the changes when needed.
   * @nodoc
   */
  ngDoCheck(): void {
    if (this._ngForOfDirty) {
      this._ngForOfDirty = false;
      // React on ngForOf changes only once all inputs have been initialized
      const value = this._ngForOf;
      if (!this._differ && value) {
        if (typeof ngDevMode === 'undefined' || ngDevMode) {
          try {
            // CAUTION: this logic is duplicated for production mode below, as the try-catch
            // is only present in development builds.
            this._differ = this._differs.find(value).create(this.ngForTrackBy);
          } catch {
            let errorMessage =
              `Cannot find a differ supporting object '${value}' of type '` +
              `${getTypeName(value)}'. NgFor only supports binding to Iterables, such as Arrays.`;
            if (typeof value === 'object') {
              errorMessage += ' Did you mean to use the keyvalue pipe?';
            }
            throw new RuntimeError(RuntimeErrorCode.NG_FOR_MISSING_DIFFER, errorMessage);
          }
        } else {
          // CAUTION: this logic is duplicated for development mode above, as the try-catch
          // is only present in development builds.
          this._differ = this._differs.find(value).create(this.ngForTrackBy);
        }
      }
    }
    if (this._differ) {
      const changes = this._differ.diff(this._ngForOf);
      if (changes) this._applyChanges(changes);
    }
  }

  private _applyChanges(changes: IterableChanges<T>) {
    const viewContainer = this._viewContainer;
    changes.forEachOperation(
      (
        item: IterableChangeRecord<T>,
        adjustedPreviousIndex: number | null,
        currentIndex: number | null,
      ) => {
        if (item.previousIndex == null) {
          // NgForOf is never "null" or "undefined" here because the differ detected
          // that a new item needs to be inserted from the iterable. This implies that
          // there is an iterable value for "_ngForOf".
          viewContainer.createEmbeddedView(
            this._template,
            new NgForOfContext<T, U>(item.item, this._ngForOf!, -1, -1),
            currentIndex === null ? undefined : currentIndex,
          );
        } else if (currentIndex == null) {
          viewContainer.remove(adjustedPreviousIndex === null ? undefined : adjustedPreviousIndex);
        } else if (adjustedPreviousIndex !== null) {
          const view = viewContainer.get(adjustedPreviousIndex)!;
          viewContainer.move(view, currentIndex);
          applyViewChange(view as EmbeddedViewRef<NgForOfContext<T, U>>, item);
        }
      },
    );

    for (let i = 0, ilen = viewContainer.length; i < ilen; i++) {
      const viewRef = <EmbeddedViewRef<NgForOfContext<T, U>>>viewContainer.get(i);
      const context = viewRef.context;
      context.index = i;
      context.count = ilen;
      context.ngForOf = this._ngForOf!;
    }

    changes.forEachIdentityChange((record: any) => {
      const viewRef = <EmbeddedViewRef<NgForOfContext<T, U>>>viewContainer.get(record.currentIndex);
      applyViewChange(viewRef, record);
    });
  }

  /**
   * Asserts the correct type of the context for the template that `NgForOf` will render.
   *
   * The presence of this method is a signal to the Ivy template type-check compiler that the
   * `NgForOf` structural directive renders its template with a specific context type.
   */
  static ngTemplateContextGuard<T, U extends NgIterable<T>>(
    dir: NgForOf<T, U>,
    ctx: any,
  ): ctx is NgForOfContext<T, U> {
    return true;
  }
}

// Also export the `NgForOf` class as `NgFor` to improve the DX for
// cases when the directive is used as standalone, so the class name
// matches the CSS selector (*ngFor).
export {NgForOf as NgFor};

function applyViewChange<T>(
  view: EmbeddedViewRef<NgForOfContext<T>>,
  record: IterableChangeRecord<T>,
) {
  view.context.$implicit = record.item;
}

function getTypeName(type: any): string {
  return type['name'] || typeof type;
}


/**
 * @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 {
  Directive,
  DoCheck,
  ElementRef,
  Input,
  IterableDiffers,
  KeyValueDiffers,
  Renderer2,
  ɵstringify as stringify,
} from '@angular/core';

type NgClassSupportedTypes = string[] | Set<string> | {[klass: string]: any} | null | undefined;

const WS_REGEXP = /\s+/;

const EMPTY_ARRAY: string[] = [];

/**
 * Represents internal object used to track state of each CSS class. There are 3 different (boolean)
 * flags that, combined together, indicate state of a given CSS class:
 * - enabled: indicates if a class should be present in the DOM (true) or not (false);
 * - changed: tracks if a class was toggled (added or removed) during the custom dirty-checking
 * process; changed classes must be synchronized with the DOM;
 * - touched: tracks if a class is present in the current object bound to the class / ngClass input;
 * classes that are not present any more can be removed from the internal data structures;
 */
interface CssClassState {
  // PERF: could use a bit mask to represent state as all fields are boolean flags
  enabled: boolean;
  changed: boolean;
  touched: boolean;
}

/**
 * @ngModule CommonModule
 *
 * @usageNotes
 * ```
 *     <some-element [ngClass]="'first second'">...</some-element>
 *
 *     <some-element [ngClass]="['first', 'second']">...</some-element>
 *
 *     <some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
 *
 *     <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
 *
 *     <some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
 * ```
 *
 * @description
 *
 * Adds and removes CSS classes on an HTML element.
 *
 * The CSS classes are updated as follows, depending on the type of the expression evaluation:
 * - `string` - the CSS classes listed in the string (space delimited) are added,
 * - `Array` - the CSS classes declared as Array elements are added,
 * - `Object` - keys are CSS classes that get added when the expression given in the value
 *              evaluates to a truthy value, otherwise they are removed.
 *
 * @publicApi
 */
@Directive({
  selector: '[ngClass]',
  standalone: true,
})
export class NgClass implements DoCheck {
  private initialClasses = EMPTY_ARRAY;
  private rawClass: NgClassSupportedTypes;

  private stateMap = new Map<string, CssClassState>();

  constructor(
    private _ngEl: ElementRef,
    private _renderer: Renderer2,
  ) {}

  @Input('class')
  set klass(value: string) {
    this.initialClasses = value != null ? value.trim().split(WS_REGEXP) : EMPTY_ARRAY;
  }

  @Input('ngClass')
  set ngClass(value: string | string[] | Set<string> | {[klass: string]: any} | null | undefined) {
    this.rawClass = typeof value === 'string' ? value.trim().split(WS_REGEXP) : value;
  }

  /*
  The NgClass directive uses the custom change detection algorithm for its inputs. The custom
  algorithm is necessary since inputs are represented as complex object or arrays that need to be
  deeply-compared.

  This algorithm is perf-sensitive since NgClass is used very frequently and its poor performance
  might negatively impact runtime performance of the entire change detection cycle. The design of
  this algorithm is making sure that:
  - there is no unnecessary DOM manipulation (CSS classes are added / removed from the DOM only when
  needed), even if references to bound objects change;
  - there is no memory allocation if nothing changes (even relatively modest memory allocation
  during the change detection cycle can result in GC pauses for some of the CD cycles).

  The algorithm works by iterating over the set of bound classes, staring with [class] binding and
  then going over [ngClass] binding. For each CSS class name:
  - check if it was seen before (this information is tracked in the state map) and if its value
  changed;
  - mark it as "touched" - names that are not marked are not present in the latest set of binding
  and we can remove such class name from the internal data structures;

  After iteration over all the CSS class names we've got data structure with all the information
  necessary to synchronize changes to the DOM - it is enough to iterate over the state map, flush
  changes to the DOM and reset internal data structures so those are ready for the next change
  detection cycle.
   */
  ngDoCheck(): void {
    // classes from the [class] binding
    for (const klass of this.initialClasses) {
      this._updateState(klass, true);
    }

    // classes from the [ngClass] binding
    const rawClass = this.rawClass;
    if (Array.isArray(rawClass) || rawClass instanceof Set) {
      for (const klass of rawClass) {
        this._updateState(klass, true);
      }
    } else if (rawClass != null) {
      for (const klass of Object.keys(rawClass)) {
        this._updateState(klass, Boolean(rawClass[klass]));
      }
    }

    this._applyStateDiff();
  }

  private _updateState(klass: string, nextEnabled: boolean) {
    const state = this.stateMap.get(klass);
    if (state !== undefined) {
      if (state.enabled !== nextEnabled) {
        state.changed = true;
        state.enabled = nextEnabled;
      }
      state.touched = true;
    } else {
      this.stateMap.set(klass, {enabled: nextEnabled, changed: true, touched: true});
    }
  }

  private _applyStateDiff() {
    for (const stateEntry of this.stateMap) {
      const klass = stateEntry[0];
      const state = stateEntry[1];

      if (state.changed) {
        this._toggleClass(klass, state.enabled);
        state.changed = false;
      } else if (!state.touched) {
        // A class that was previously active got removed from the new collection of classes -
        // remove from the DOM as well.
        if (state.enabled) {
          this._toggleClass(klass, false);
        }
        this.stateMap.delete(klass);
      }

      state.touched = false;
    }
  }

  private _toggleClass(klass: string, enabled: boolean): void {
    if (ngDevMode) {
      if (typeof klass !== 'string') {
        throw new Error(
          `NgClass can only toggle CSS classes expressed as strings, got ${stringify(klass)}`,
        );
      }
    }
    klass = klass.trim();
    if (klass.length > 0) {
      klass.split(WS_REGEXP).forEach((klass) => {
        if (enabled) {
          this._renderer.addClass(this._ngEl.nativeElement, klass);
        } else {
          this._renderer.removeClass(this._ngEl.nativeElement, klass);
        }
      });
    }
  }
}


/**
 * @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 {
  Directive,
  EmbeddedViewRef,
  Injector,
  Input,
  OnChanges,
  SimpleChange,
  SimpleChanges,
  TemplateRef,
  ViewContainerRef,
} from '@angular/core';

/**
 * @ngModule CommonModule
 *
 * @description
 *
 * Inserts an embedded view from a prepared `TemplateRef`.
 *
 * You can attach a context object to the `EmbeddedViewRef` by setting `[ngTemplateOutletContext]`.
 * `[ngTemplateOutletContext]` should be an object, the object's keys will be available for binding
 * by the local template `let` declarations.
 *
 * @usageNotes
 * ```
 * <ng-container *ngTemplateOutlet="templateRefExp; context: contextExp"></ng-container>
 * ```
 *
 * Using the key `$implicit` in the context object will set its value as default.
 *
 * ### Example
 *
 * {@example common/ngTemplateOutlet/ts/module.ts region='NgTemplateOutlet'}
 *
 * @publicApi
 */
@Directive({
  selector: '[ngTemplateOutlet]',
  standalone: true,
})
export class NgTemplateOutlet<C = unknown> implements OnChanges {
  private _viewRef: EmbeddedViewRef<C> | null = null;

  /**
   * A context object to attach to the {@link EmbeddedViewRef}. This should be an
   * object, the object's keys will be available for binding by the local template `let`
   * declarations.
   * Using the key `$implicit` in the context object will set its value as default.
   */
  @Input() public ngTemplateOutletContext: C | null = null;

  /**
   * A string defining the template reference and optionally the context object for the template.
   */
  @Input() public ngTemplateOutlet: TemplateRef<C> | null = null;

  /** Injector to be used within the embedded view. */
  @Input() public ngTemplateOutletInjector: Injector | null = null;

  constructor(private _viewContainerRef: ViewContainerRef) {}

  ngOnChanges(changes: SimpleChanges) {
    if (this._shouldRecreateView(changes)) {
      const viewContainerRef = this._viewContainerRef;

      if (this._viewRef) {
        viewContainerRef.remove(viewContainerRef.indexOf(this._viewRef));
      }

      // If there is no outlet, clear the destroyed view ref.
      if (!this.ngTemplateOutlet) {
        this._viewRef = null;
        return;
      }

      // Create a context forward `Proxy` that will always bind to the user-specified context,
      // without having to destroy and re-create views whenever the context changes.
      const viewContext = this._createContextForwardProxy();
      this._viewRef = viewContainerRef.createEmbeddedView(this.ngTemplateOutlet, viewContext, {
        injector: this.ngTemplateOutletInjector ?? undefined,
      });
    }
  }

  /**
   * We need to re-create existing embedded view if either is true:
   * - the outlet changed.
   * - the injector changed.
   */
  private _shouldRecreateView(changes: SimpleChanges): boolean {
    return !!changes['ngTemplateOutlet'] || !!changes['ngTemplateOutletInjector'];
  }

  /**
   * For a given outlet instance, we create a proxy object that delegates
   * to the user-specified context. This allows changing, or swapping out
   * the context object completely without having to destroy/re-create the view.
   */
  private _createContextForwardProxy(): C {
    return <C>new Proxy(
      {},
      {
        set: (_target, prop, newValue) => {
          if (!this.ngTemplateOutletContext) {
            return false;
          }
          return Reflect.set(this.ngTemplateOutletContext, prop, newValue);
        },
        get: (_target, prop, receiver) => {
          if (!this.ngTemplateOutletContext) {
            return undefined;
          }
          return Reflect.get(this.ngTemplateOutletContext, prop, receiver);
        },
      },
    );
  }
}