Skip to content

Commit

Permalink
Improve formly typing
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilippMDoerner committed Dec 12, 2024
1 parent 0f5c09b commit 7b349b9
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 55 deletions.
39 changes: 20 additions & 19 deletions src/app/_models/formly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export interface FormlyPasswordInterface {
disabled?: boolean;
}

export interface FormlyInterface {
key: string;
export interface FormlyInterface<T> {
key: Exclude<keyof T, symbol>;
label?: string;
required?: boolean;
hide?: boolean;
Expand All @@ -23,13 +23,14 @@ export interface FormlyInterface {
showWrapperLabel?: boolean;
}

export interface FormlyOverviewSelectConfig<T> extends FormlyInterface {
labelProp: keyof T;
valueProp?: keyof T;
sortProp?: keyof T;
export interface FormlyOverviewSelectConfig<Model, Option>
extends FormlyInterface<Model> {
labelProp: keyof Option;
valueProp?: keyof Option;
sortProp?: keyof Option;
sortDirection?: 'asc' | 'desc';
campaign?: string;
options$: Observable<T[]>;
options$: Observable<Option[]>;
}

export type DisabledFunction<T> = (
Expand All @@ -39,16 +40,16 @@ export type DisabledFunction<T> = (
control: AbstractControl,
) => Observable<boolean[]>;

export interface FormlyOverviewDisabledSelectConfig<T>
extends FormlyOverviewSelectConfig<T> {
disabledExpression: DisabledFunction<T>;
export interface FormlyOverviewDisabledSelectConfig<Model, Option>
extends FormlyOverviewSelectConfig<Model, Option> {
disabledExpression: DisabledFunction<Option>;
tooltipMessage: string;
warningMessage: string;
}

export type InputKind = 'NUMBER' | 'STRING' | 'NAME';

export interface FormlyInputConfig extends FormlyInterface {
export interface FormlyInputConfig<T> extends FormlyInterface<T> {
placeholder?: string;
maxLength?: number;
minLength?: number;
Expand All @@ -58,7 +59,7 @@ export interface FormlyInputConfig extends FormlyInterface {

export type FileFieldKind = 'IMAGE' | 'OTHER';

export interface FormlyFileConfig extends FormlyInterface {
export interface FormlyFileConfig<T> extends FormlyInterface<T> {
fileButtonType?: ElementKind;
fileFieldKind?: FileFieldKind;
}
Expand All @@ -68,21 +69,21 @@ export interface StaticOption {
value: String | Number;
}

export interface FormlyCustomStringSelectConfig extends FormlyInterface {
export interface FormlyCustomStringSelectConfig<T> extends FormlyInterface<T> {
options: string[];
}

export interface FormlyCustomSelectConfig extends FormlyInterface {
export interface FormlyCustomSelectConfig<T> extends FormlyInterface<T> {
options: StaticOption[];
}

export interface FormlyCheckboxConfig extends FormlyInterface {
export interface FormlyCheckboxConfig<T> extends FormlyInterface<T> {
defaultValue: boolean;
}

export interface FormlyDatepickerConfig extends FormlyInterface {}
export interface FormlyDatepickerConfig<T> extends FormlyInterface<T> {}

export interface FormlyCustomSessionSelect extends FormlyInterface {}
export interface FormlyCustomSessionSelect<T> extends FormlyInterface<T> {}

export type LoadAutocompleteOptions<T> = (
searchTerm: string,
Expand All @@ -97,5 +98,5 @@ export type CustomAutocompleteProps<T> = {
loadOptions: LoadAutocompleteOptions<T>;
initialValue$?: Observable<T>;
};
export type FormlyAutocompleteConfig<T> = FormlyInterface &
CustomAutocompleteProps<T>;
export type FormlyAutocompleteConfig<Model, Option> = FormlyInterface<Model> &
CustomAutocompleteProps<Option>;
74 changes: 40 additions & 34 deletions src/app/_services/formly/formly-service.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import { capitalize } from 'src/utils/string';
providedIn: 'root',
})
export class FormlyService {
buildOverviewSelectConfig<T>(
config: FormlyOverviewSelectConfig<T>,
buildOverviewSelectConfig<Model, Option>(
config: FormlyOverviewSelectConfig<Model, Option>,
): FormlyFieldConfig {
const isRequiredField = config.required ?? true;

Expand All @@ -43,7 +43,7 @@ export class FormlyService {
wrappers: config.wrappers,
hideExpression: config.hide,
props: {
label: config.label ?? capitalize(config.key),
label: config.label ?? capitalize(`${config.key}`),
labelProp: config.labelProp,
valueProp: config.valueProp ?? 'pk',
options: sortedOptions$,
Expand All @@ -56,8 +56,8 @@ export class FormlyService {
};
}

buildDisableSelectConfig<T>(
config: FormlyOverviewDisabledSelectConfig<T>,
buildDisableSelectConfig<Model, Option>(
config: FormlyOverviewDisabledSelectConfig<Model, Option>,
): FormlyFieldConfig {
const isRequiredField = config.required ?? true;

Expand All @@ -74,7 +74,7 @@ export class FormlyService {
wrappers: config.wrappers,
hideExpression: config.hide ?? false,
props: {
label: config.label ?? capitalize(config.key),
label: config.label ?? capitalize(`${config.key}`),
labelProp: config.labelProp,
valueProp: config.valueProp ?? 'pk',
options: sortedOptions$,
Expand All @@ -92,7 +92,9 @@ export class FormlyService {
} satisfies FormlyFieldConfig;
}

buildStaticSelectConfig(config: FormlyStaticSelectConfig): FormlyFieldConfig {
buildStaticSelectConfig<T>(
config: FormlyStaticSelectConfig<T>,
): FormlyFieldConfig {
const validators = this.getValidators(config);

return {
Expand All @@ -102,7 +104,7 @@ export class FormlyService {
wrappers: config.wrappers,
hideExpression: config.hide ?? false,
props: {
label: config.label ?? capitalize(config.key),
label: config.label ?? capitalize(`${config.key}`),
options: config.options,
required: config.required ?? true,
disabled: config.disabled,
Expand All @@ -113,24 +115,24 @@ export class FormlyService {
};
}

buildStaticStringSelectConfig(
partialConfig: FormlyStaticStringSelectConfig,
buildStaticStringSelectConfig<T>(
partialConfig: FormlyStaticStringSelectConfig<T>,
): FormlyFieldConfig {
const optionStrings: string[] = partialConfig.options;

let options: StaticOption[] = optionStrings.map((str) => {
return { label: str, value: str };
});

const config: FormlyStaticSelectConfig = {
const config: FormlyStaticSelectConfig<T> = {
...partialConfig,
options,
};

return this.buildStaticSelectConfig(config);
}

buildInputConfig(config: FormlyInputConfig): FormlyFieldConfig {
buildInputConfig<T>(config: FormlyInputConfig<T>): FormlyFieldConfig {
const validators = this.getValidators(config);
if (config.inputKind === 'NUMBER') {
validators.push('notInteger');
Expand Down Expand Up @@ -159,7 +161,7 @@ export class FormlyService {
props: {
maxLength: config.maxLength,
minLength: config.minLength,
label: config.label ?? capitalize(config.key),
label: config.label ?? capitalize(`${config.key}`),
type: innerInputType,
required: config.required ?? true,
disabled: !!config.disabled,
Expand All @@ -171,7 +173,7 @@ export class FormlyService {
};
}

buildSinglePasswordConfig(config: FormlyInterface): FormlyFieldConfig {
buildSinglePasswordConfig<T>(config: FormlyInterface<T>): FormlyFieldConfig {
const validators = this.getValidators(config);

return {
Expand Down Expand Up @@ -239,7 +241,7 @@ export class FormlyService {
};
}

buildCheckboxConfig(config: FormlyCheckboxConfig): FormlyFieldConfig {
buildCheckboxConfig<T>(config: FormlyCheckboxConfig<T>): FormlyFieldConfig {
return {
key: config.key,
type: 'checkbox',
Expand All @@ -248,14 +250,16 @@ export class FormlyService {
defaultValue: config.defaultValue,
hideExpression: config.hide,
props: {
label: config.label ?? capitalize(config.key),
label: config.label ?? capitalize(`${config.key}`),
required: config.required ?? true,
disabled: config.disabled,
},
};
}

buildDatepickerConfig(config: FormlyDatepickerConfig): FormlyFieldConfig {
buildDatepickerConfig<T>(
config: FormlyDatepickerConfig<T>,
): FormlyFieldConfig {
const validators = this.getValidators(config);
validators.push('date');

Expand All @@ -266,7 +270,7 @@ export class FormlyService {
wrappers: config.wrappers,
hideExpression: config.hide,
props: {
label: config.label ?? capitalize(config.key),
label: config.label ?? capitalize(`${config.key}`),
required: config.required ?? true,
disabled: config.disabled,
},
Expand All @@ -276,7 +280,7 @@ export class FormlyService {
};
}

buildFileFieldConfig(config: FormlyFileConfig): FormlyFieldConfig {
buildFileFieldConfig<T>(config: FormlyFileConfig<T>): FormlyFieldConfig {
const validators = this.getValidators(config);

return {
Expand All @@ -288,7 +292,7 @@ export class FormlyService {
props: {
buttonType: config.fileButtonType ?? 'SECONDARY',
fileFieldKind: config.fileFieldKind ?? 'IMAGE',
label: config.label ?? capitalize(config.key),
label: config.label ?? capitalize(`${config.key}`),
required: config.required ?? true,
disabled: !!config.disabled,
},
Expand All @@ -298,7 +302,7 @@ export class FormlyService {
};
}

buildEditorConfig(config: FormlyInterface): FormlyFieldConfig {
buildEditorConfig<T>(config: FormlyInterface<T>): FormlyFieldConfig {
const validators = this.getValidators(config);

return {
Expand All @@ -308,7 +312,7 @@ export class FormlyService {
wrappers: config.wrappers,
hideExpression: config.hide,
props: {
label: config.label ?? capitalize(config.key),
label: config.label ?? capitalize(`${config.key}`),
required: config.required ?? true,
disabled: config.disabled,
},
Expand All @@ -318,8 +322,8 @@ export class FormlyService {
};
}

buildAutocompleteConfig<T>(
config: FormlyAutocompleteConfig<T>,
buildAutocompleteConfig<Model, Option>(
config: FormlyAutocompleteConfig<Model, Option>,
): FormlyFieldConfig {
const validators = this.getValidators(config);

Expand All @@ -329,7 +333,7 @@ export class FormlyService {
className: config.className,
wrappers: [...(config.wrappers ?? []), 'form-field'],
props: {
label: config.label ?? capitalize(config.key),
label: config.label ?? capitalize(`${config.key}`),
required: config.required ?? true,
disabled: config.disabled,
additionalProperties: {
Expand All @@ -338,7 +342,7 @@ export class FormlyService {
optionKeyProp: config.optionKeyProp,
loadOptions: config.loadOptions,
initialValue$: config.initialValue$,
} satisfies CustomAutocompleteProps<T>,
} satisfies CustomAutocompleteProps<Option>,
},
validators: {
validation: validators,
Expand All @@ -361,7 +365,7 @@ export class FormlyService {
});
}

private getValidators(config: FormlyInterface) {
private getValidators<T>(config: FormlyInterface<T>) {
const validators = config.validators ?? [];
if (config.required) {
validators.push('required');
Expand All @@ -370,9 +374,9 @@ export class FormlyService {
return validators;
}

private addEmptyOption<T>(
private addEmptyOption<Model, Option>(
list: Observable<any[]> | any[],
config: FormlyOverviewSelectConfig<T>,
config: FormlyOverviewSelectConfig<Model, Option>,
): Observable<any[]> {
if (Array.isArray(list)) {
return of([this.createEmptyOption(config), ...list]);
Expand All @@ -383,7 +387,9 @@ export class FormlyService {
}
}

private createEmptyOption<T>(config: FormlyOverviewSelectConfig<T>) {
private createEmptyOption<Model, Option>(
config: FormlyOverviewSelectConfig<Model, Option>,
) {
const emptyOption: any = {};
emptyOption[config.labelProp] =
FormlySelectDisableFieldComponent.EMPTY_OPTION_LABEL;
Expand All @@ -393,10 +399,10 @@ export class FormlyService {
return emptyOption;
}

private sortOptions<T>(
list$: Observable<T[]>,
config: FormlyOverviewSelectConfig<T>,
): Observable<T[]> {
private sortOptions<Model, Option>(
list$: Observable<Option[]>,
config: FormlyOverviewSelectConfig<Model, Option>,
): Observable<Option[]> {
const { sortProp, sortDirection } = config;
if (!sortProp) return list$;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ export class LocationCreateUpdatePageComponent {

formlyFields = computed<FormlyFieldConfig[]>(() => [
this.formlyService.buildInputConfig({ key: 'name', inputKind: 'NAME' }),
this.formlyService.buildDisableSelectConfig<OverviewItem>({
this.formlyService.buildDisableSelectConfig<
LocationRaw | Location,
OverviewItem
>({
key: 'parent_location',
label: 'Parent Location',
options$: this.campaignLocations$,
Expand Down
5 changes: 4 additions & 1 deletion src/design/organisms/quote-field/quote-field.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ export class QuoteFieldComponent {
labelProp: 'name_full',
valueProp: 'pk',
}),
this.formlyService.buildAutocompleteConfig<OverviewItem>({
this.formlyService.buildAutocompleteConfig<
Quote | QuoteRaw,
OverviewItem
>({
key: 'encounter',
required: false,
loadOptions: (searchTerm) =>
Expand Down

0 comments on commit 7b349b9

Please sign in to comment.