Skip to content

Instantly share code, notes, and snippets.

@flensrocker
Last active July 31, 2024 14:04
Show Gist options
  • Select an option

  • Save flensrocker/2fe684c91f63e404a671a657b873bfe2 to your computer and use it in GitHub Desktop.

Select an option

Save flensrocker/2fe684c91f63e404a671a657b873bfe2 to your computer and use it in GitHub Desktop.
Angular: declarative handle submitting a form
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())
);
};
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;
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,
}
);
};
@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