From 1748c8984cecf159c7bee1242ea6f9573930cd2a Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Sun, 4 Jan 2026 23:27:51 +0100 Subject: [PATCH] docs(docs-infra): Add package filter to the API list This commit also introduces the usage of signal forms on adev (cherry picked from commit 15308149db17e77a63e455ccc05abcdea152a843) --- .../search-dialog.component.html | 4 +- .../search-dialog/search-dialog.component.ts | 20 ++-- .../components/select/select.component.html | 6 +- .../components/select/select.component.ts | 48 ++-------- .../slide-toggle.component.spec.ts | 23 +---- .../slide-toggle/slide-toggle.component.ts | 41 +-------- .../text-field/text-field.component.ts | 55 +++-------- .../api-reference-list.component.html | 10 +- .../api-reference-list.component.spec.ts | 40 ++++---- .../api-reference-list.component.ts | 91 ++++++++++++------- 10 files changed, 123 insertions(+), 215 deletions(-) diff --git a/adev/shared-docs/components/search-dialog/search-dialog.component.html b/adev/shared-docs/components/search-dialog/search-dialog.component.html index a29ea4ed461..5dabbf714c8 100644 --- a/adev/shared-docs/components/search-dialog/search-dialog.component.html +++ b/adev/shared-docs/components/search-dialog/search-dialog.component.html @@ -3,7 +3,7 @@ } - } @else if (history.hasItems() && !this.searchControl.value.length) { + } @else if (history.hasItems() && !this.searchForm().value().length) { } @else {
diff --git a/adev/shared-docs/components/search-dialog/search-dialog.component.ts b/adev/shared-docs/components/search-dialog/search-dialog.component.ts index aa9312fe3fb..25c7b5b3eb0 100644 --- a/adev/shared-docs/components/search-dialog/search-dialog.component.ts +++ b/adev/shared-docs/components/search-dialog/search-dialog.component.ts @@ -20,19 +20,19 @@ import { viewChildren, } from '@angular/core'; -import {WINDOW} from '../../providers'; import {ClickOutside, SearchItem} from '../../directives'; +import {WINDOW} from '../../providers'; import {Search, SearchHistory} from '../../services'; -import {TextField} from '../text-field/text-field.component'; -import {FormControl, ReactiveFormsModule} from '@angular/forms'; import {ActiveDescendantKeyManager} from '@angular/cdk/a11y'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; +import {Field, form} from '@angular/forms/signals'; import {Router, RouterLink} from '@angular/router'; import {fromEvent} from 'rxjs'; -import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component'; import {RelativeLink} from '../../pipes'; +import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component'; import {SearchHistoryComponent} from '../search-history/search-history.component'; +import {TextField} from '../text-field/text-field.component'; @Component({ selector: 'docs-search-dialog', @@ -40,7 +40,7 @@ import {SearchHistoryComponent} from '../search-history/search-history.component imports: [ ClickOutside, TextField, - ReactiveFormsModule, + Field, SearchItem, AlgoliaIcon, RelativeLink, @@ -67,22 +67,14 @@ export class SearchDialog { this.injector, ).withWrap(); - readonly searchQuery = this.search.searchQuery; readonly resultsResource = this.search.resultsResource; readonly searchResults = this.search.searchResults; - // We use a FormControl instead of relying on NgModel+signal to avoid - // the issue https://github.com/angular/angular/issues/13568 - // TODO: Use signal forms when available - searchControl = new FormControl(this.searchQuery(), {nonNullable: true}); + searchForm = form(this.search.searchQuery); constructor() { inject(DestroyRef).onDestroy(() => this.keyManager.destroy()); - this.searchControl.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => { - this.searchQuery.set(value); - }); - // Thinking about refactoring this to a single afterRenderEffect ? // Answer: It won't have the same behavior effect(() => { diff --git a/adev/shared-docs/components/select/select.component.html b/adev/shared-docs/components/select/select.component.html index 4288253ba1b..0a2f4d85eab 100644 --- a/adev/shared-docs/components/select/select.component.html +++ b/adev/shared-docs/components/select/select.component.html @@ -1,10 +1,10 @@ diff --git a/adev/shared-docs/components/select/select.component.ts b/adev/shared-docs/components/select/select.component.ts index 0cf6f3121e1..4ca4938f420 100644 --- a/adev/shared-docs/components/select/select.component.ts +++ b/adev/shared-docs/components/select/select.component.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule} from '@angular/forms'; -import {ChangeDetectionStrategy, Component, model, forwardRef, input, signal} from '@angular/core'; +import {ChangeDetectionStrategy, Component, input, model} from '@angular/core'; +import {FormValueControl} from '@angular/forms/signals'; type SelectOptionValue = string | number | boolean; @@ -19,59 +19,25 @@ export interface SelectOption { @Component({ selector: 'docs-select', changeDetection: ChangeDetectionStrategy.OnPush, - imports: [FormsModule], templateUrl: './select.component.html', styleUrls: ['./select.component.scss'], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => Select), - multi: true, - }, - ], host: { class: 'docs-form-element', }, }) -export class Select implements ControlValueAccessor { +export class Select implements FormValueControl { + readonly value = model(null); + readonly id = input.required({alias: 'selectId'}); readonly name = input.required(); readonly options = input.required(); - readonly disabled = model(false); - - // Implemented as part of ControlValueAccessor. - private onChange: (value: SelectOptionValue) => void = (_: SelectOptionValue) => {}; - private onTouched: () => void = () => {}; - - protected readonly selectedOption = signal(null); - - // Implemented as part of ControlValueAccessor. - writeValue(value: SelectOptionValue): void { - this.selectedOption.set(value); - } - - // Implemented as part of ControlValueAccessor. - registerOnChange(fn: any): void { - this.onChange = fn; - } - - // Implemented as part of ControlValueAccessor. - registerOnTouched(fn: any): void { - this.onTouched = fn; - } - - // Implemented as part of ControlValueAccessor. - setDisabledState?(isDisabled: boolean): void { - this.disabled.set(isDisabled); - } + readonly disabled = input(false); setOption($event: SelectOptionValue): void { if (this.disabled()) { return; } - this.selectedOption.set($event); - this.onChange($event); - this.onTouched(); + this.value.set($event as string); } } diff --git a/adev/shared-docs/components/slide-toggle/slide-toggle.component.spec.ts b/adev/shared-docs/components/slide-toggle/slide-toggle.component.spec.ts index 0f97ac0cb17..841526d9732 100644 --- a/adev/shared-docs/components/slide-toggle/slide-toggle.component.spec.ts +++ b/adev/shared-docs/components/slide-toggle/slide-toggle.component.spec.ts @@ -31,27 +31,14 @@ describe('SlideToggle', () => { expect(component['checked']()).toBeTrue(); }); - it('should call onChange and onTouched when toggled', () => { - const onChangeSpy = jasmine.createSpy('onChangeSpy'); - const onTouchedSpy = jasmine.createSpy('onTouchedSpy'); - component.registerOnChange(onChangeSpy); - component.registerOnTouched(onTouchedSpy); - - component.toggle(); - - expect(onChangeSpy).toHaveBeenCalled(); - expect(onChangeSpy).toHaveBeenCalledWith(true); - expect(onTouchedSpy).toHaveBeenCalled(); - }); - - it('should set active class for button when is checked', async () => { - component.writeValue(true); - await fixture.whenStable(); + it('should set active class for button when is checked', () => { + component.checked.set(true); + fixture.detectChanges(); const buttonElement: HTMLButtonElement = fixture.nativeElement.querySelector('input'); expect(buttonElement.classList.contains('docs-toggle-active')).toBeTrue(); - component.writeValue(false); - await fixture.whenStable(); + component.checked.set(false); + fixture.detectChanges(); expect(buttonElement.classList.contains('docs-toggle-active')).toBeFalse(); }); }); diff --git a/adev/shared-docs/components/slide-toggle/slide-toggle.component.ts b/adev/shared-docs/components/slide-toggle/slide-toggle.component.ts index c91ddbd31fe..92375a0dd52 100644 --- a/adev/shared-docs/components/slide-toggle/slide-toggle.component.ts +++ b/adev/shared-docs/components/slide-toggle/slide-toggle.component.ts @@ -6,52 +6,21 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, forwardRef, model, input, signal} from '@angular/core'; -import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {ChangeDetectionStrategy, Component, input, model} from '@angular/core'; +import {FormCheckboxControl} from '@angular/forms/signals'; @Component({ selector: 'docs-slide-toggle', changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './slide-toggle.component.html', styleUrls: ['./slide-toggle.component.scss'], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => SlideToggle), - multi: true, - }, - ], }) -export class SlideToggle implements ControlValueAccessor { +export class SlideToggle implements FormCheckboxControl { readonly buttonId = input.required(); readonly label = input.required(); readonly disabled = model(false); - // Implemented as part of ControlValueAccessor. - private onChange: (value: boolean) => void = (_: boolean) => {}; - private onTouched: () => void = () => {}; - - protected readonly checked = signal(false); - - // Implemented as part of ControlValueAccessor. - writeValue(value: boolean): void { - this.checked.set(value); - } - - // Implemented as part of ControlValueAccessor. - registerOnChange(fn: any): void { - this.onChange = fn; - } - - // Implemented as part of ControlValueAccessor. - registerOnTouched(fn: any): void { - this.onTouched = fn; - } - - // Implemented as part of ControlValueAccessor. - setDisabledState(isDisabled: boolean): void { - this.disabled.set(isDisabled); - } + readonly checked = model(false); // Toggles the checked state of the slide-toggle. toggle(): void { @@ -60,7 +29,5 @@ export class SlideToggle implements ControlValueAccessor { } this.checked.update((checked) => !checked); - this.onChange(this.checked()); - this.onTouched(); } } diff --git a/adev/shared-docs/components/text-field/text-field.component.ts b/adev/shared-docs/components/text-field/text-field.component.ts index b3fdfb917f1..66ae3ea48fc 100644 --- a/adev/shared-docs/components/text-field/text-field.component.ts +++ b/adev/shared-docs/components/text-field/text-field.component.ts @@ -7,91 +7,58 @@ */ import { + afterNextRender, ChangeDetectionStrategy, Component, ElementRef, input, - afterNextRender, - forwardRef, - signal, model, viewChild, } from '@angular/core'; -import {ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {FormValueControl} from '@angular/forms/signals'; import {IconComponent} from '../icon/icon.component'; @Component({ selector: 'docs-text-field', changeDetection: ChangeDetectionStrategy.OnPush, - imports: [FormsModule, IconComponent], + imports: [IconComponent], templateUrl: './text-field.component.html', styleUrls: ['./text-field.component.scss'], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => TextField), - multi: true, - }, - ], host: { class: 'docs-form-element', }, }) -export class TextField implements ControlValueAccessor { +export class TextField implements FormValueControl { readonly input = viewChild.required>('inputRef'); - - readonly name = input(null); + readonly name = input(''); + readonly value = model(null); readonly placeholder = input(null); readonly disabled = model(false); readonly hideIcon = input(false); readonly autofocus = input(false); readonly resetLabel = input(null); - // Implemented as part of ControlValueAccessor. - private onChange: (value: string) => void = (_: string) => {}; - private onTouched: () => void = () => {}; - - protected readonly value = signal(null); - constructor() { afterNextRender(() => { if (this.autofocus()) { - this.input().nativeElement.focus(); + this.focus(); } }); } - // Implemented as part of ControlValueAccessor. - writeValue(value: unknown): void { - this.value.set(typeof value === 'string' ? value : null); - } - - // Implemented as part of ControlValueAccessor. - registerOnChange(fn: any): void { - this.onChange = fn; - } - - // Implemented as part of ControlValueAccessor. - registerOnTouched(fn: any): void { - this.onTouched = fn; - } - - // Implemented as part of ControlValueAccessor. - setDisabledState?(isDisabled: boolean): void { - this.disabled.set(isDisabled); - } - setValue(value: string): void { if (this.disabled()) { return; } this.value.set(value); - this.onChange(value); - this.onTouched(); } clearTextField() { this.setValue(''); } + + focus() { + this.input().nativeElement.focus(); + } } diff --git a/adev/src/app/features/references/api-reference-list/api-reference-list.component.html b/adev/src/app/features/references/api-reference-list/api-reference-list.component.html index 3d971d6f9ea..b493fb454db 100644 --- a/adev/src/app/features/references/api-reference-list/api-reference-list.component.html +++ b/adev/src/app/features/references/api-reference-list/api-reference-list.component.html @@ -4,7 +4,13 @@
- + + +
@@ -23,7 +29,7 @@ @for (itemType of itemTypes; track itemType) {
  • diff --git a/adev/src/app/features/references/api-reference-list/api-reference-list.component.spec.ts b/adev/src/app/features/references/api-reference-list/api-reference-list.component.spec.ts index 1b0eaf4c666..112611130f5 100644 --- a/adev/src/app/features/references/api-reference-list/api-reference-list.component.spec.ts +++ b/adev/src/app/features/references/api-reference-list/api-reference-list.component.spec.ts @@ -8,16 +8,16 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {Location} from '@angular/common'; +import {signal} from '@angular/core'; +import {TextField} from '@angular/docs'; +import {By} from '@angular/platform-browser'; +import {provideRouter} from '@angular/router'; +import {RouterTestingHarness} from '@angular/router/testing'; +import {ApiItem} from '../interfaces/api-item'; +import {ApiItemType} from '../interfaces/api-item-type'; import ApiReferenceList, {ALL_TYPES_KEY, STATUSES} from './api-reference-list.component'; import {ApiReferenceManager} from './api-reference-manager.service'; -import {signal} from '@angular/core'; -import {ApiItemType} from '../interfaces/api-item-type'; -import {RouterTestingHarness} from '@angular/router/testing'; -import {provideRouter} from '@angular/router'; -import {Location} from '@angular/common'; -import {By} from '@angular/platform-browser'; -import {TextField} from '@angular/docs'; -import {ApiItem} from '../interfaces/api-item'; describe('ApiReferenceList', () => { let component: ApiReferenceList; @@ -114,7 +114,7 @@ describe('ApiReferenceList', () => { fixture.componentRef.setInput('query', 'Item1'); await fixture.whenStable(); - expect(component.filteredGroups()![0].items).toEqual([fakeItem1]); + expect(component.filteredGroups()[0].items).toEqual([fakeItem1]); }); it('should display items which match the query by group title', async () => { @@ -122,33 +122,33 @@ describe('ApiReferenceList', () => { await fixture.whenStable(); // Should find all items whose group title contains the query - expect(component.filteredGroups()![0].items.length).toBeGreaterThan(0); - expect(component.filteredGroups()![0].items).toContain(fakeItem1); + expect(component.filteredGroups()[0].items.length).toBeGreaterThan(0); + expect(component.filteredGroups()[0].items).toContain(fakeItem1); }); it('should display only class items when user selects Class in the Type select', async () => { fixture.componentRef.setInput('type', ApiItemType.CLASS); await fixture.whenStable(); - expect(component.type()).toEqual(ApiItemType.CLASS); - expect(component.filteredGroups()![0].items).toEqual([fakeItem2]); + expect(component.form.type().value()).toEqual(ApiItemType.CLASS); + expect(component.filteredGroups()[0].items).toEqual([fakeItem2]); }); it('should set selected type when provided type is different than selected', async () => { - expect(component.type()).toBe(ALL_TYPES_KEY); + expect(component.form.type().value()).toBe(ALL_TYPES_KEY); component.setItemType(ApiItemType.BLOCK); await RouterTestingHarness.create(`/api?type=${ApiItemType.BLOCK}`); - expect(component.type()).toBe(ApiItemType.BLOCK); + expect(component.form.type().value()).toBe(ApiItemType.BLOCK); }); it('should reset selected type when provided type is equal to selected', async () => { component.setItemType(ApiItemType.BLOCK); const harness = await RouterTestingHarness.create(`/api?type=${ApiItemType.BLOCK}`); - expect(component.type()).toBe(ApiItemType.BLOCK); + expect(component.form.type().value()).toBe(ApiItemType.BLOCK); component.setItemType(ApiItemType.BLOCK); harness.navigateByUrl(`/api`); - expect(component.type()).toBe(ALL_TYPES_KEY); + expect(component.form.type().value()).toBe(ALL_TYPES_KEY); }); it('should set the value of the queryParam equal to the query text field', async () => { @@ -164,15 +164,15 @@ describe('ApiReferenceList', () => { const location = TestBed.inject(Location); const textField = fixture.debugElement.query(By.directive(TextField)); - (textField.componentInstance as TextField).setValue('item1'); + fixture.componentInstance.form.query().value.set('item1'); await fixture.whenStable(); expect(location.path()).toBe(`?query=item1`); - component.setItemType(ApiItemType.BLOCK); + fixture.componentInstance.form.type().value.set(ApiItemType.BLOCK); await fixture.whenStable(); expect(location.path()).toBe(`?query=item1&type=${ApiItemType.BLOCK}`); - fixture.componentRef.setInput('status', STATUSES.experimental); + fixture.componentInstance.form.status().value.set(STATUSES.experimental); await fixture.whenStable(); expect(location.path()).toBe( `?query=item1&type=${ApiItemType.BLOCK}&status=${STATUSES.experimental}`, diff --git a/adev/src/app/features/references/api-reference-list/api-reference-list.component.ts b/adev/src/app/features/references/api-reference-list/api-reference-list.component.ts index a997b6c1ebb..6076559a5e7 100644 --- a/adev/src/app/features/references/api-reference-list/api-reference-list.component.ts +++ b/adev/src/app/features/references/api-reference-list/api-reference-list.component.ts @@ -6,33 +6,33 @@ * found in the LICENSE file at https://angular.dev/license */ +import {CdkMenuModule} from '@angular/cdk/menu'; +import {KeyValuePipe} from '@angular/common'; import { ChangeDetectionStrategy, Component, - ElementRef, + EnvironmentInjector, + afterNextRender, computed, + effect, inject, + input, linkedSignal, viewChild, - afterNextRender, - EnvironmentInjector, - effect, - input, } from '@angular/core'; -import ApiItemsSection from '../api-items-section/api-items-section.component'; -import {FormsModule} from '@angular/forms'; -import {TextField} from '@angular/docs'; -import {KeyValuePipe} from '@angular/common'; -import {Params, Router} from '@angular/router'; -import {ApiItemType} from '../interfaces/api-item-type'; -import {ApiReferenceManager} from './api-reference-manager.service'; -import ApiItemLabel from '../api-item-label/api-item-label.component'; -import {ApiLabel} from '../pipes/api-label.pipe'; -import {ApiItemsGroup} from '../interfaces/api-items-group'; -import {CdkMenuModule} from '@angular/cdk/menu'; +import {Select, SelectOption, TextField} from '@angular/docs'; +import {Field, form} from '@angular/forms/signals'; import {MatChipsModule} from '@angular/material/chips'; +import {Params, Router} from '@angular/router'; +import ApiItemLabel from '../api-item-label/api-item-label.component'; +import ApiItemsSection from '../api-items-section/api-items-section.component'; +import {ApiItemType} from '../interfaces/api-item-type'; +import {ApiItemsGroup} from '../interfaces/api-items-group'; +import {ApiLabel} from '../pipes/api-label.pipe'; +import {ApiReferenceManager} from './api-reference-manager.service'; export const ALL_TYPES_KEY = 'All'; +export const ALL_PACKAGES = 'All'; export const STATUSES = { stable: 1, developerPreview: 2, @@ -46,12 +46,13 @@ export const DEFAULT_STATUS = STATUSES.stable | STATUSES.developerPreview | STAT imports: [ ApiItemsSection, ApiItemLabel, - FormsModule, TextField, ApiLabel, CdkMenuModule, MatChipsModule, KeyValuePipe, + Select, + Field, ], templateUrl: './api-reference-list.component.html', styleUrls: ['./api-reference-list.component.scss'], @@ -61,19 +62,33 @@ export default class ApiReferenceList { // services private readonly apiReferenceManager = inject(ApiReferenceManager); private readonly router = inject(Router); - private readonly filterInput = viewChild.required(TextField, {read: ElementRef}); + private readonly filterInput = viewChild.required(TextField); private readonly injector = inject(EnvironmentInjector); // inputs readonly queryInput = input('', {alias: 'query'}); readonly typeInput = input(ALL_TYPES_KEY, {alias: 'type'}); readonly statusInput = input(DEFAULT_STATUS, {alias: 'status'}); + protected selectedPackageInput = input(ALL_PACKAGES, {alias: 'package'}); // inputs are route binded, they can reset to undefined // also we want a writable state, so we use a linked signal - protected query = linkedSignal(() => this.queryInput() ?? ''); - public type = linkedSignal(() => this.typeInput() ?? ALL_TYPES_KEY); - private status = linkedSignal(() => this.statusInput() ?? DEFAULT_STATUS); + public form = form( + linkedSignal(() => ({ + query: this.queryInput() ?? '', + status: this.statusInput() ?? DEFAULT_STATUS, + type: this.typeInput() ?? ALL_TYPES_KEY, + selectedPackage: this.selectedPackageInput() ?? ALL_PACKAGES, + })), + ); + + protected packageOptions = computed(() => [ + {label: 'All Packages', value: ALL_PACKAGES}, + ...this.apiReferenceManager.apiGroups().map((group) => ({ + label: group.title, + value: group.id, + })), + ]); // const state protected readonly itemTypes = Object.values(ApiItemType); @@ -87,9 +102,9 @@ export default class ApiReferenceList { }; readonly filteredGroups = computed((): ApiItemsGroup[] => { - const query = this.query().toLocaleLowerCase(); - const status = this.status(); - const type = this.type(); + const query = this.form.query().value().toLocaleLowerCase(); + const status = this.form.status().value(); + const type = this.form.type().value(); return this.apiReferenceManager .apiGroups() .map((group) => ({ @@ -112,7 +127,12 @@ export default class ApiReferenceList { ); }), })) - .filter((group) => group.items.length > 0); + .filter( + (group) => + group.items.length > 0 && + (this.form.selectedPackage().value() === ALL_PACKAGES || + group.id === this.form.selectedPackage().value()), + ); }); constructor() { @@ -121,10 +141,9 @@ export default class ApiReferenceList { afterNextRender( { write: () => { - // Lord forgive me for I have sinned - // Use the CVA to focus when https://github.com/angular/angular/issues/31133 is implemented + // Why not improve this once https://github.com/orgs/angular/projects/60?pane=issue&itemId=143488813 is done if (matchMedia('(hover: hover) and (pointer:fine)').matches) { - scheduleOnIdle(() => filterInput.nativeElement.querySelector('input').focus()); + scheduleOnIdle(() => filterInput.focus()); } }, }, @@ -135,9 +154,13 @@ export default class ApiReferenceList { effect(() => { // We'll only set the params if we deviate from the default values const params: Params = { - 'query': this.query() || null, - 'type': this.type() === ALL_TYPES_KEY ? null : this.type(), - 'status': this.status() === DEFAULT_STATUS ? null : this.status(), + 'query': this.form.query().value() || null, + 'type': this.form.type().value() === ALL_TYPES_KEY ? null : this.form.type().value(), + 'status': this.form.status().value() === DEFAULT_STATUS ? null : this.form.status().value(), + 'package': + this.form.selectedPackage().value() === ALL_PACKAGES + ? null + : this.form.selectedPackage().value(), }; this.router.navigate([], { @@ -152,11 +175,11 @@ export default class ApiReferenceList { } setItemType(itemType: ApiItemType): void { - this.type.update((type) => (type === itemType ? ALL_TYPES_KEY : itemType)); + this.form.type().value.update((type) => (type === itemType ? ALL_TYPES_KEY : itemType)); } setStatus(status: number): void { - this.status.update((previousStatus) => { + this.form.status().value.update((previousStatus) => { if (this.isStatusSelected(status)) { return previousStatus & ~status; // Clear the bit } else { @@ -166,7 +189,7 @@ export default class ApiReferenceList { } isStatusSelected(status: number): boolean { - return (this.status() & status) === status; + return (this.form.status().value() & status) === status; } }