Last active
July 31, 2024 14:04
-
-
Save flensrocker/2fe684c91f63e404a671a657b873bfe2 to your computer and use it in GitHub Desktop.
Angular: declarative handle submitting a form
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { AbstractControl, FormGroup, FormSubmittedEvent } from "@angular/forms"; | |
| import { Observable, filter, map } from "rxjs"; | |
| import { BsFormValueOf } from "./bs-form-value-of"; | |
| export const bsFormSubmitted = < | |
| TControl extends { | |
| [K in keyof TControl]: AbstractControl<unknown>; | |
| }, | |
| >( | |
| form: FormGroup<TControl> | |
| ): Observable<BsFormValueOf<FormGroup<TControl>>> => { | |
| return form.events.pipe( | |
| filter(controlEvent => controlEvent instanceof FormSubmittedEvent && controlEvent.source.status === "VALID"), | |
| map(submittedEvent => submittedEvent.source.getRawValue()) | |
| ); | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { AbstractControl, FormArray, FormControl, FormGroup } from "@angular/forms"; | |
| export type BaseFormControlType = symbol | boolean | string | number | bigint | Date | null; | |
| export type BaseFormGroupType = object; | |
| export type BaseFormArrayType<T> = readonly T[] | T[]; | |
| export type BaseFormType = BaseFormControlType | BaseFormGroupType | BaseFormArrayType<unknown>; | |
| export type BsFormControlOf<T extends BaseFormControlType> = FormControl<T>; | |
| export type BsFormGroupOf<T extends BaseFormGroupType> = FormGroup<BsFormOf<T>>; | |
| export type BsFormArrayOf<T extends BaseFormType> = T extends AbstractControl | |
| ? FormArray<T> | |
| : T extends BaseFormControlType | |
| ? FormArray<BsFormControlOf<T>> | |
| : T extends BaseFormArrayType<infer E> | |
| ? E extends BaseFormType | |
| ? FormArray<BsFormArrayOf<E>> | |
| : never | |
| : T extends BaseFormGroupType | |
| ? FormArray<BsFormGroupOf<T>> | |
| : never; | |
| export type BsFormOf<T extends BaseFormGroupType> = { | |
| [K in keyof T]: T[K] extends AbstractControl | |
| ? T[K] | |
| : T[K] extends BaseFormControlType | |
| ? BsFormControlOf<T[K]> | |
| : T[K] extends BaseFormArrayType<infer E> | |
| ? E extends BaseFormType | |
| ? BsFormArrayOf<E> | |
| : never | |
| : T[K] extends BaseFormGroupType | |
| ? BsFormGroupOf<T[K]> | |
| : never; | |
| }; | |
| export type BsFormValueOf<T extends AbstractControl> = | |
| T extends FormControl<infer TControl> | |
| ? TControl | |
| : T extends FormGroup<infer TGroup> | |
| ? { [K in keyof TGroup]: BsFormValueOf<TGroup[K]> } | |
| : T extends FormArray<infer TElement> | |
| ? BsFormValueOf<TElement>[] | |
| : T; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Signal } from "@angular/core"; | |
| import { toSignal } from "@angular/core/rxjs-interop"; | |
| import { Observable, catchError, map, of, startWith, switchMap } from "rxjs"; | |
| export type IdleServiceState = { | |
| readonly type: "IDLE"; | |
| }; | |
| const idleServiceState: IdleServiceState = { | |
| type: "IDLE", | |
| }; | |
| export type BusyServiceState<TRequest> = { | |
| readonly type: "BUSY"; | |
| readonly request: TRequest; | |
| }; | |
| export type SuccessServiceState<TRequest, TResponse> = { | |
| readonly type: "SUCCESS"; | |
| readonly request: TRequest; | |
| readonly response: TResponse; | |
| }; | |
| export type ErrorServiceState<TRequest> = { | |
| readonly type: "ERROR"; | |
| readonly request: TRequest; | |
| readonly error: unknown; | |
| }; | |
| export type ServiceState<TRequest, TResponse> = | |
| | IdleServiceState | |
| | BusyServiceState<TRequest> | |
| | SuccessServiceState<TRequest, TResponse> | |
| | ErrorServiceState<TRequest>; | |
| export const bsServiceState2 = <TRequest, TResponse>( | |
| request$: Observable<TRequest>, | |
| response$: (request: TRequest) => Observable<TResponse> | |
| ): Signal<ServiceState<TRequest, TResponse>> => { | |
| return toSignal( | |
| request$.pipe( | |
| // use concatMap if needed | |
| switchMap(request => | |
| response$(request).pipe( | |
| map((response): ServiceState<TRequest, TResponse> => ({ type: "SUCCESS", request, response })), | |
| catchError(error => of<ServiceState<TRequest, TResponse>>({ type: "ERROR", request, error })), | |
| startWith<ServiceState<TRequest, TResponse>>({ type: "BUSY", request }) | |
| ) | |
| ) | |
| ), | |
| { | |
| initialValue: idleServiceState, | |
| } | |
| ); | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @Component({...}) | |
| export class ExampleComponent { | |
| readonly #service = inject(SomeService); | |
| readonly form = new FormGroup({...}); | |
| readonly request$ = bsFormSubmitted(this.form).pipe( | |
| map((formValue): SomeRequest => ({...})) | |
| ); | |
| readonly state = bsServiceState(this.request$, request => this.#service.doSomething(request)); | |
| // derive computed slices of this.state() as needed | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment