From d3c0e95eca3750deaeba5dd933f56dd0f2817ac0 Mon Sep 17 00:00:00 2001 From: Younes Jaaidi Date: Mon, 9 Mar 2026 14:58:40 +0100 Subject: [PATCH] =?UTF-8?q?refactor:=20=F0=9F=9B=A0=EF=B8=8F=20use=20signa?= =?UTF-8?q?l=20forms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/recipe/recipe-filter.ng.ts | 69 ++++++++----- .../src/app/recipe/recipe-filter.ng.ts | 2 +- .../src/app/recipe/recipe-filter.ng.ts | 69 ++++++++----- .../src/app/recipe/recipe-filter.ng.ts | 69 ++++++++----- .../src/app/recipe/recipe-filter.ng.ts | 69 ++++++++----- .../src/app/recipe/recipe-filter.ng.ts | 69 ++++++++----- .../src/app/recipe/recipe-filter.ng.ts | 98 +++++++++++-------- .../src/app/recipe/recipe-filter.ng.ts | 69 ++++++++----- tools/downgrade-to-jest.ts | 9 +- tools/tsconfig.tools.json | 7 +- 10 files changed, 335 insertions(+), 195 deletions(-) diff --git a/apps/401-recipe-filter-solution/src/app/recipe/recipe-filter.ng.ts b/apps/401-recipe-filter-solution/src/app/recipe/recipe-filter.ng.ts index 3af28c49..45954968 100644 --- a/apps/401-recipe-filter-solution/src/app/recipe/recipe-filter.ng.ts +++ b/apps/401-recipe-filter-solution/src/app/recipe/recipe-filter.ng.ts @@ -1,53 +1,72 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { outputFromObservable } from '@angular/core/rxjs-interop'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; -import { map } from 'rxjs/operators'; -import { createRecipeFilterCriteria } from './recipe-filter-criteria'; +import { + ChangeDetectionStrategy, + Component, + effect, + model, + output, + signal, +} from '@angular/core'; +import { form, FormField, FormRoot } from '@angular/forms/signals'; +import { RecipeFilterCriteria } from './recipe-filter-criteria'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'wm-recipe-filter', - imports: [ReactiveFormsModule], + imports: [FormField, FormRoot], template: ` -
+
`, - styles: [ - ` - :host { - text-align: center; - } - `, - ], + styles: ` + :host { + text-align: center; + } + `, }) export class RecipeFilter { - protected filterFormGroup = new FormGroup({ - keywords: new FormControl(), - maxIngredientCount: new FormControl(), - maxStepCount: new FormControl(), - }); + filterChange = output(); - filterChange = outputFromObservable( - this.filterFormGroup.valueChanges.pipe( - map((value) => createRecipeFilterCriteria(value)), - ), + filterForm = form( + signal<{ + keywords: string; + maxIngredientCount: number | null; + maxStepCount: number | null; + }>({ + keywords: '', + maxIngredientCount: null, + maxStepCount: null, + }), ); + + constructor() { + effect(() => { + const { keywords, maxIngredientCount, maxStepCount } = + this.filterForm().value(); + this.filterChange.emit({ + keywords: keywords.length > 0 ? keywords : undefined, + maxIngredientCount: + maxIngredientCount != null ? maxIngredientCount : undefined, + maxStepCount: maxStepCount != null ? maxStepCount : undefined, + }); + }); + } } diff --git a/apps/401-recipe-filter-starter/src/app/recipe/recipe-filter.ng.ts b/apps/401-recipe-filter-starter/src/app/recipe/recipe-filter.ng.ts index 25af471b..9a793683 100644 --- a/apps/401-recipe-filter-starter/src/app/recipe/recipe-filter.ng.ts +++ b/apps/401-recipe-filter-starter/src/app/recipe/recipe-filter.ng.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, output } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; -import { RecipeFilterCriteria } from './recipe-filter'; +import { RecipeFilterCriteria } from './recipe-filter-criteria'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/apps/402-recipe-search-filter-interaction-solution/src/app/recipe/recipe-filter.ng.ts b/apps/402-recipe-search-filter-interaction-solution/src/app/recipe/recipe-filter.ng.ts index 3af28c49..45954968 100644 --- a/apps/402-recipe-search-filter-interaction-solution/src/app/recipe/recipe-filter.ng.ts +++ b/apps/402-recipe-search-filter-interaction-solution/src/app/recipe/recipe-filter.ng.ts @@ -1,53 +1,72 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { outputFromObservable } from '@angular/core/rxjs-interop'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; -import { map } from 'rxjs/operators'; -import { createRecipeFilterCriteria } from './recipe-filter-criteria'; +import { + ChangeDetectionStrategy, + Component, + effect, + model, + output, + signal, +} from '@angular/core'; +import { form, FormField, FormRoot } from '@angular/forms/signals'; +import { RecipeFilterCriteria } from './recipe-filter-criteria'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'wm-recipe-filter', - imports: [ReactiveFormsModule], + imports: [FormField, FormRoot], template: ` -
+
`, - styles: [ - ` - :host { - text-align: center; - } - `, - ], + styles: ` + :host { + text-align: center; + } + `, }) export class RecipeFilter { - protected filterFormGroup = new FormGroup({ - keywords: new FormControl(), - maxIngredientCount: new FormControl(), - maxStepCount: new FormControl(), - }); + filterChange = output(); - filterChange = outputFromObservable( - this.filterFormGroup.valueChanges.pipe( - map((value) => createRecipeFilterCriteria(value)), - ), + filterForm = form( + signal<{ + keywords: string; + maxIngredientCount: number | null; + maxStepCount: number | null; + }>({ + keywords: '', + maxIngredientCount: null, + maxStepCount: null, + }), ); + + constructor() { + effect(() => { + const { keywords, maxIngredientCount, maxStepCount } = + this.filterForm().value(); + this.filterChange.emit({ + keywords: keywords.length > 0 ? keywords : undefined, + maxIngredientCount: + maxIngredientCount != null ? maxIngredientCount : undefined, + maxStepCount: maxStepCount != null ? maxStepCount : undefined, + }); + }); + } } diff --git a/apps/402-recipe-search-filter-interaction-starter/src/app/recipe/recipe-filter.ng.ts b/apps/402-recipe-search-filter-interaction-starter/src/app/recipe/recipe-filter.ng.ts index 3af28c49..45954968 100644 --- a/apps/402-recipe-search-filter-interaction-starter/src/app/recipe/recipe-filter.ng.ts +++ b/apps/402-recipe-search-filter-interaction-starter/src/app/recipe/recipe-filter.ng.ts @@ -1,53 +1,72 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { outputFromObservable } from '@angular/core/rxjs-interop'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; -import { map } from 'rxjs/operators'; -import { createRecipeFilterCriteria } from './recipe-filter-criteria'; +import { + ChangeDetectionStrategy, + Component, + effect, + model, + output, + signal, +} from '@angular/core'; +import { form, FormField, FormRoot } from '@angular/forms/signals'; +import { RecipeFilterCriteria } from './recipe-filter-criteria'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'wm-recipe-filter', - imports: [ReactiveFormsModule], + imports: [FormField, FormRoot], template: ` -
+
`, - styles: [ - ` - :host { - text-align: center; - } - `, - ], + styles: ` + :host { + text-align: center; + } + `, }) export class RecipeFilter { - protected filterFormGroup = new FormGroup({ - keywords: new FormControl(), - maxIngredientCount: new FormControl(), - maxStepCount: new FormControl(), - }); + filterChange = output(); - filterChange = outputFromObservable( - this.filterFormGroup.valueChanges.pipe( - map((value) => createRecipeFilterCriteria(value)), - ), + filterForm = form( + signal<{ + keywords: string; + maxIngredientCount: number | null; + maxStepCount: number | null; + }>({ + keywords: '', + maxIngredientCount: null, + maxStepCount: null, + }), ); + + constructor() { + effect(() => { + const { keywords, maxIngredientCount, maxStepCount } = + this.filterForm().value(); + this.filterChange.emit({ + keywords: keywords.length > 0 ? keywords : undefined, + maxIngredientCount: + maxIngredientCount != null ? maxIngredientCount : undefined, + maxStepCount: maxStepCount != null ? maxStepCount : undefined, + }); + }); + } } diff --git a/apps/403-recipe-search-add-button-solution/src/app/recipe/recipe-filter.ng.ts b/apps/403-recipe-search-add-button-solution/src/app/recipe/recipe-filter.ng.ts index 3af28c49..45954968 100644 --- a/apps/403-recipe-search-add-button-solution/src/app/recipe/recipe-filter.ng.ts +++ b/apps/403-recipe-search-add-button-solution/src/app/recipe/recipe-filter.ng.ts @@ -1,53 +1,72 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { outputFromObservable } from '@angular/core/rxjs-interop'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; -import { map } from 'rxjs/operators'; -import { createRecipeFilterCriteria } from './recipe-filter-criteria'; +import { + ChangeDetectionStrategy, + Component, + effect, + model, + output, + signal, +} from '@angular/core'; +import { form, FormField, FormRoot } from '@angular/forms/signals'; +import { RecipeFilterCriteria } from './recipe-filter-criteria'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'wm-recipe-filter', - imports: [ReactiveFormsModule], + imports: [FormField, FormRoot], template: ` -
+
`, - styles: [ - ` - :host { - text-align: center; - } - `, - ], + styles: ` + :host { + text-align: center; + } + `, }) export class RecipeFilter { - protected filterFormGroup = new FormGroup({ - keywords: new FormControl(), - maxIngredientCount: new FormControl(), - maxStepCount: new FormControl(), - }); + filterChange = output(); - filterChange = outputFromObservable( - this.filterFormGroup.valueChanges.pipe( - map((value) => createRecipeFilterCriteria(value)), - ), + filterForm = form( + signal<{ + keywords: string; + maxIngredientCount: number | null; + maxStepCount: number | null; + }>({ + keywords: '', + maxIngredientCount: null, + maxStepCount: null, + }), ); + + constructor() { + effect(() => { + const { keywords, maxIngredientCount, maxStepCount } = + this.filterForm().value(); + this.filterChange.emit({ + keywords: keywords.length > 0 ? keywords : undefined, + maxIngredientCount: + maxIngredientCount != null ? maxIngredientCount : undefined, + maxStepCount: maxStepCount != null ? maxStepCount : undefined, + }); + }); + } } diff --git a/apps/403-recipe-search-add-button-starter/src/app/recipe/recipe-filter.ng.ts b/apps/403-recipe-search-add-button-starter/src/app/recipe/recipe-filter.ng.ts index 3af28c49..45954968 100644 --- a/apps/403-recipe-search-add-button-starter/src/app/recipe/recipe-filter.ng.ts +++ b/apps/403-recipe-search-add-button-starter/src/app/recipe/recipe-filter.ng.ts @@ -1,53 +1,72 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { outputFromObservable } from '@angular/core/rxjs-interop'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; -import { map } from 'rxjs/operators'; -import { createRecipeFilterCriteria } from './recipe-filter-criteria'; +import { + ChangeDetectionStrategy, + Component, + effect, + model, + output, + signal, +} from '@angular/core'; +import { form, FormField, FormRoot } from '@angular/forms/signals'; +import { RecipeFilterCriteria } from './recipe-filter-criteria'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'wm-recipe-filter', - imports: [ReactiveFormsModule], + imports: [FormField, FormRoot], template: ` -
+
`, - styles: [ - ` - :host { - text-align: center; - } - `, - ], + styles: ` + :host { + text-align: center; + } + `, }) export class RecipeFilter { - protected filterFormGroup = new FormGroup({ - keywords: new FormControl(), - maxIngredientCount: new FormControl(), - maxStepCount: new FormControl(), - }); + filterChange = output(); - filterChange = outputFromObservable( - this.filterFormGroup.valueChanges.pipe( - map((value) => createRecipeFilterCriteria(value)), - ), + filterForm = form( + signal<{ + keywords: string; + maxIngredientCount: number | null; + maxStepCount: number | null; + }>({ + keywords: '', + maxIngredientCount: null, + maxStepCount: null, + }), ); + + constructor() { + effect(() => { + const { keywords, maxIngredientCount, maxStepCount } = + this.filterForm().value(); + this.filterChange.emit({ + keywords: keywords.length > 0 ? keywords : undefined, + maxIngredientCount: + maxIngredientCount != null ? maxIngredientCount : undefined, + maxStepCount: maxStepCount != null ? maxStepCount : undefined, + }); + }); + } } diff --git a/apps/404-recipe-filter-material-solution/src/app/recipe/recipe-filter.ng.ts b/apps/404-recipe-filter-material-solution/src/app/recipe/recipe-filter.ng.ts index 3e0c05c1..8a224e54 100644 --- a/apps/404-recipe-filter-material-solution/src/app/recipe/recipe-filter.ng.ts +++ b/apps/404-recipe-filter-material-solution/src/app/recipe/recipe-filter.ng.ts @@ -1,51 +1,71 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { outputFromObservable } from '@angular/core/rxjs-interop'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; -import { map } from 'rxjs/operators'; -import { createRecipeFilterCriteria } from './recipe-filter-criteria'; +import { + ChangeDetectionStrategy, + Component, + signal, + output, + effect, +} from '@angular/core'; +import { form, FormField, FormRoot } from '@angular/forms/signals'; +import { RecipeFilterCriteria } from './recipe-filter-criteria'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'wm-recipe-filter', - imports: [MatFormFieldModule, MatInputModule, ReactiveFormsModule], + imports: [FormField, FormRoot], template: ` -
- - Keywords - - - - - Max Ingredients - - - - - Max Steps - - + + + +
`, - styles: [ - ` - :host { - text-align: center; - } - `, - ], + styles: ` + :host { + text-align: center; + } + `, }) export class RecipeFilter { - protected filterFormGroup = new FormGroup({ - keywords: new FormControl(), - maxIngredientCount: new FormControl(), - maxStepCount: new FormControl(), - }); + filterChange = output(); - filterChange = outputFromObservable( - this.filterFormGroup.valueChanges.pipe( - map((value) => createRecipeFilterCriteria(value)), - ), + filterForm = form( + signal<{ + keywords: string; + maxIngredientCount: number | null; + maxStepCount: number | null; + }>({ + keywords: '', + maxIngredientCount: null, + maxStepCount: null, + }), ); + + constructor() { + effect(() => { + const { keywords, maxIngredientCount, maxStepCount } = + this.filterForm().value(); + this.filterChange.emit({ + keywords: keywords.length > 0 ? keywords : undefined, + maxIngredientCount: + maxIngredientCount != null ? maxIngredientCount : undefined, + maxStepCount: maxStepCount != null ? maxStepCount : undefined, + }); + }); + } } diff --git a/apps/404-recipe-filter-material-starter/src/app/recipe/recipe-filter.ng.ts b/apps/404-recipe-filter-material-starter/src/app/recipe/recipe-filter.ng.ts index 3af28c49..45954968 100644 --- a/apps/404-recipe-filter-material-starter/src/app/recipe/recipe-filter.ng.ts +++ b/apps/404-recipe-filter-material-starter/src/app/recipe/recipe-filter.ng.ts @@ -1,53 +1,72 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { outputFromObservable } from '@angular/core/rxjs-interop'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; -import { map } from 'rxjs/operators'; -import { createRecipeFilterCriteria } from './recipe-filter-criteria'; +import { + ChangeDetectionStrategy, + Component, + effect, + model, + output, + signal, +} from '@angular/core'; +import { form, FormField, FormRoot } from '@angular/forms/signals'; +import { RecipeFilterCriteria } from './recipe-filter-criteria'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'wm-recipe-filter', - imports: [ReactiveFormsModule], + imports: [FormField, FormRoot], template: ` -
+
`, - styles: [ - ` - :host { - text-align: center; - } - `, - ], + styles: ` + :host { + text-align: center; + } + `, }) export class RecipeFilter { - protected filterFormGroup = new FormGroup({ - keywords: new FormControl(), - maxIngredientCount: new FormControl(), - maxStepCount: new FormControl(), - }); + filterChange = output(); - filterChange = outputFromObservable( - this.filterFormGroup.valueChanges.pipe( - map((value) => createRecipeFilterCriteria(value)), - ), + filterForm = form( + signal<{ + keywords: string; + maxIngredientCount: number | null; + maxStepCount: number | null; + }>({ + keywords: '', + maxIngredientCount: null, + maxStepCount: null, + }), ); + + constructor() { + effect(() => { + const { keywords, maxIngredientCount, maxStepCount } = + this.filterForm().value(); + this.filterChange.emit({ + keywords: keywords.length > 0 ? keywords : undefined, + maxIngredientCount: + maxIngredientCount != null ? maxIngredientCount : undefined, + maxStepCount: maxStepCount != null ? maxStepCount : undefined, + }); + }); + } } diff --git a/tools/downgrade-to-jest.ts b/tools/downgrade-to-jest.ts index 1930f754..5bdeca7e 100644 --- a/tools/downgrade-to-jest.ts +++ b/tools/downgrade-to-jest.ts @@ -36,12 +36,17 @@ function donwgradeNxJsonUpdater(nxJson: NxJsonConfiguration) { return plugin; } + const options = (plugin.options ?? {}) as Record; + if (options && typeof options !== 'object') { + throw new Error(`Invalid plugin options: ${JSON.stringify(options)}`); + } + switch (plugin.plugin) { case '@nx/jest/plugin': return { ...plugin, options: { - ...(plugin.options ?? {}), + ...options, targetName: 'test', }, }; @@ -49,7 +54,7 @@ function donwgradeNxJsonUpdater(nxJson: NxJsonConfiguration) { return { ...plugin, options: { - ...(plugin.options ?? {}), + ...options, testTargetName: 'vitest', }, }; diff --git a/tools/tsconfig.tools.json b/tools/tsconfig.tools.json index 982c47fa..a90f9565 100644 --- a/tools/tsconfig.tools.json +++ b/tools/tsconfig.tools.json @@ -1,13 +1,14 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "outDir": "../dist/out-tsc/tools", - "rootDir": ".", + "allowImportingTsExtensions": true, + "importHelpers": false, "module": "esnext", "moduleResolution": "bundler", + "outDir": "../dist/out-tsc/tools", + "rootDir": ".", "target": "esnext", "types": ["node"], - "importHelpers": false }, "include": ["**/*.ts"] }