Chorehancement: update to Angular v22, 'zoneless' / 'reactive' (#13114)

This commit is contained in:
shamoon
2026-07-10 00:42:16 -07:00
committed by GitHub
parent f244442c65
commit 106b41a15c
213 changed files with 5363 additions and 5842 deletions
@@ -1,4 +1,5 @@
import {
ChangeDetectorRef,
Directive,
ElementRef,
EventEmitter,
@@ -6,12 +7,15 @@ import {
OnInit,
Output,
ViewChild,
inject,
} from '@angular/core'
import { ControlValueAccessor } from '@angular/forms'
import { v4 as uuidv4 } from 'uuid'
@Directive()
export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
protected readonly changeDetector = inject(ChangeDetectorRef)
@ViewChild('inputField')
inputField: ElementRef
@@ -23,6 +27,7 @@ export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
writeValue(newValue: any): void {
this.value = newValue
this.changeDetector.markForCheck()
}
registerOnChange(fn: any): void {
this.onChange = fn
@@ -32,6 +37,7 @@ export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled
this.changeDetector.markForCheck()
}
focus() {
@@ -3,7 +3,7 @@
<ng-select name="inputId" [(ngModel)]="value"
[disabled]="disabled"
clearable="true"
[items]="groups"
[items]="groups()"
multiple="true"
bindLabel="name"
bindValue="id"
@@ -1,11 +1,12 @@
import { Component, forwardRef, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import {
FormsModule,
NG_VALUE_ACCESSOR,
ReactiveFormsModule,
} from '@angular/forms'
import { NgSelectComponent } from '@ng-select/ng-select'
import { first } from 'rxjs/operators'
import { map } from 'rxjs/operators'
import { Group } from 'src/app/data/group'
import { GroupService } from 'src/app/services/rest/group.service'
import { AbstractInputComponent } from '../../abstract-input'
@@ -24,15 +25,9 @@ import { AbstractInputComponent } from '../../abstract-input'
imports: [NgSelectComponent, FormsModule, ReactiveFormsModule],
})
export class PermissionsGroupComponent extends AbstractInputComponent<Group> {
groups: Group[]
constructor() {
const groupService = inject(GroupService)
super()
groupService
.listAll()
.pipe(first())
.subscribe((result) => (this.groups = result.results))
}
private readonly groupService = inject(GroupService)
readonly groups = toSignal(
this.groupService.listAll().pipe(map((result) => result.results)),
{ initialValue: undefined as Group[] }
)
}
@@ -3,7 +3,7 @@
<ng-select name="inputId" [(ngModel)]="value"
[disabled]="disabled"
clearable="true"
[items]="users"
[items]="users()"
multiple="true"
bindLabel="username"
bindValue="id"
@@ -1,11 +1,12 @@
import { Component, forwardRef, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import {
FormsModule,
NG_VALUE_ACCESSOR,
ReactiveFormsModule,
} from '@angular/forms'
import { NgSelectComponent } from '@ng-select/ng-select'
import { first } from 'rxjs/operators'
import { map } from 'rxjs/operators'
import { User } from 'src/app/data/user'
import { UserService } from 'src/app/services/rest/user.service'
import { AbstractInputComponent } from '../../abstract-input'
@@ -24,15 +25,9 @@ import { AbstractInputComponent } from '../../abstract-input'
imports: [NgSelectComponent, FormsModule, ReactiveFormsModule],
})
export class PermissionsUserComponent extends AbstractInputComponent<User[]> {
users: User[]
constructor() {
const userService = inject(UserService)
super()
userService
.listAll()
.pipe(first())
.subscribe((result) => (this.users = result.results))
}
private readonly userService = inject(UserService)
readonly users = toSignal(
this.userService.listAll().pipe(map((result) => result.results)),
{ initialValue: undefined as User[] }
)
}
@@ -1,9 +1,4 @@
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from '@angular/core/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import {
FormsModule,
NG_VALUE_ACCESSOR,
@@ -121,12 +116,14 @@ describe('SelectComponent', () => {
).toBeFalsy()
})
it('should clear search term on blur after delay', fakeAsync(() => {
it('should clear search term on blur after delay', () => {
jest.useFakeTimers()
const clearSpy = jest.spyOn(component, 'clearLastSearchTerm')
component.onBlur()
tick(3000)
jest.advanceTimersByTime(3000)
expect(clearSpy).toHaveBeenCalled()
}))
jest.useRealTimers()
})
it('should emit filtered documents', () => {
component.value = 10
@@ -106,7 +106,7 @@ describe('TagsComponent', () => {
modalService = TestBed.inject(NgbModal)
settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 1 }
settingsService.currentUser.set({ id: 1 } as any)
fixture = TestBed.createComponent(TagsComponent)
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
component = fixture.componentInstance
@@ -136,20 +136,13 @@ describe('TagsComponent', () => {
})
it('should support create new using last search term and open a modal', () => {
settingsService.currentUser = { id: 1 }
settingsService.currentUser.set({ id: 1 })
let activeInstances: NgbModalRef[]
modalService.activeInstances.subscribe((v) => (activeInstances = v))
component.select.filter('foobar')
component.createTag()
expect(modalService.hasOpenModals()).toBeTruthy()
expect(activeInstances[0].componentInstance.object.name).toEqual('foobar')
const editDialog = activeInstances[0]
.componentInstance as TagEditDialogComponent
editDialog.save() // create is mocked
fixture.detectChanges()
fixture.whenStable().then(() => {
expect(fixture.debugElement.nativeElement.textContent).toContain('foobar')
})
})
it('support remove tags', () => {
@@ -1,4 +1,5 @@
import {
ChangeDetectorRef,
Component,
EventEmitter,
forwardRef,
@@ -49,6 +50,7 @@ import { TagComponent } from '../../tag/tag.component'
export class TagsComponent implements OnInit, ControlValueAccessor {
private tagService = inject(TagService)
private modalService = inject(NgbModal)
private readonly changeDetector = inject(ChangeDetectorRef)
constructor() {
this.createTagRef = this.createTag.bind(this)
@@ -60,6 +62,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
writeValue(newValue: number[]): void {
this.value = newValue
this.changeDetector.markForCheck()
}
registerOnChange(fn: any): void {
this.onChange = fn
@@ -69,11 +72,13 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled
this.changeDetector.markForCheck()
}
ngOnInit(): void {
this.tagService.listAll().subscribe((result) => {
this.tags = result.results
this.changeDetector.markForCheck()
})
}
@@ -176,7 +181,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
var modal = this.modalService.open(TagEditDialogComponent, {
backdrop: 'static',
})
modal.componentInstance.dialogMode = EditDialogMode.CREATE
modal.componentInstance.dialogMode.set(EditDialogMode.CREATE)
if (name) modal.componentInstance.object = { name: name }
else if (this.select.searchTerm)
modal.componentInstance.object = { name: this.select.searchTerm }