Skip to content

Commit 92fa38e

Browse files
zak-cloudncmaxime1992
authored andcommitted
feat(Array Handling): Use interface to manage array handling methods.
1 parent 18cdc74 commit 92fa38e

File tree

5 files changed

+60
-35
lines changed

5 files changed

+60
-35
lines changed

projects/ngx-sub-form/src/lib/ngx-sub-form-utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export type KeysWithType<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[
3030

3131
export type ArrayPropertyOf<T> = KeysWithType<T, Array<any>>;
3232

33+
export type ArrayTypeOfPropertyOf<T, K extends keyof T = keyof T> = T[K] extends Array<infer U> ? U : never;
34+
3335
export function subformComponentProviders(
3436
component: any,
3537
): {

projects/ngx-sub-form/src/lib/ngx-sub-form.component.spec.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
MissingFormControlsError,
99
NGX_SUB_FORM_HANDLE_VALUE_CHANGES_RATE_STRATEGIES,
1010
Controls,
11-
ArrayPropertyOf,
11+
ArrayPropertyOf, ArrayTypeOfPropertyOf, NgxFormWithArrayControls,
1212
} from '../public_api';
1313
import { Observable } from 'rxjs';
1414

@@ -74,9 +74,7 @@ describe(`NgxSubFormComponent`, () => {
7474

7575
// we have to call `updateValueAndValidity` within the constructor in an async way
7676
// and here we need to wait for it to run
77-
setTimeout(() => {
78-
done();
79-
}, 0);
77+
setTimeout(done);
8078
});
8179

8280
describe(`created`, () => {
@@ -588,7 +586,7 @@ interface VehiclesArrayForm {
588586
vehicles: Vehicle[];
589587
}
590588

591-
class SubArrayComponent extends NgxSubFormRemapComponent<Vehicle[], VehiclesArrayForm> {
589+
class SubArrayComponent extends NgxSubFormRemapComponent<Vehicle[], VehiclesArrayForm> implements NgxFormWithArrayControls<VehiclesArrayForm> {
592590
protected getFormControls(): Controls<VehiclesArrayForm> {
593591
return {
594592
vehicles: new FormArray([]),
@@ -605,22 +603,20 @@ class SubArrayComponent extends NgxSubFormRemapComponent<Vehicle[], VehiclesArra
605603
return formValue.vehicles;
606604
}
607605

608-
public createFormArrayControl(key: ArrayPropertyOf<VehiclesArrayForm> | undefined): FormControl {
609-
return new FormControl(null, [Validators.required]);
606+
public createFormArrayControl(key: ArrayPropertyOf<VehiclesArrayForm> | undefined, initialValue: ArrayTypeOfPropertyOf<VehiclesArrayForm>): FormControl {
607+
return new FormControl(initialValue, [Validators.required]);
610608
}
611609
}
612610

613-
describe(`NgxSubFormArrayComponent`, () => {
611+
describe(`SubArrayComponent`, () => {
614612
let subArrayComponent: SubArrayComponent;
615613

616614
beforeEach((done: () => void) => {
617615
subArrayComponent = new SubArrayComponent();
618616

619617
// we have to call `updateValueAndValidity` within the constructor in an async way
620618
// and here we need to wait for it to run
621-
setTimeout(() => {
622-
done();
623-
}, 0);
619+
setTimeout(done);
624620
});
625621

626622
it(`should have the correct values within the 'FormArray'`, () => {
@@ -663,14 +659,17 @@ describe(`NgxSubFormArrayComponent`, () => {
663659

664660
subArrayComponent.writeValue([...values, newValue]);
665661

662+
// check the form controls are the exact same instances
666663
expect(subArrayComponent.formGroupControls.vehicles.at(0)).toBe(fc1);
667664
expect(subArrayComponent.formGroupControls.vehicles.at(1)).toBe(fc2);
665+
666+
// check the values are unchanged
667+
expect(subArrayComponent.formGroupControls.vehicles.at(0).value).toBe(values[0]);
668+
expect(subArrayComponent.formGroupControls.vehicles.at(1).value).toBe(values[1]);
669+
expect(subArrayComponent.formGroupControls.vehicles.at(2).value).toBe(newValue);
668670
});
669671

670672
it(`should be possible to create a FormControl from the 'createFormArrayControl' hook based on the current property`, () => {
671-
const onChangeSpy = jasmine.createSpy('onChangeSpy');
672-
673-
subArrayComponent.registerOnChange(onChangeSpy);
674673

675674
const createFormArrayControl = spyOn(subArrayComponent, 'createFormArrayControl').and.callThrough();
676675

@@ -682,6 +681,11 @@ describe(`NgxSubFormArrayComponent`, () => {
682681
subArrayComponent.writeValue(values);
683682

684683
expect(createFormArrayControl).toHaveBeenCalledTimes(2);
685-
expect(createFormArrayControl).toHaveBeenCalledWith('vehicles');
684+
// check values
685+
expect(createFormArrayControl).toHaveBeenCalledWith('vehicles', values[0]);
686+
expect(createFormArrayControl).toHaveBeenCalledWith('vehicles', values[1]);
687+
// check non-default control was created
688+
expect(subArrayComponent.formGroupControls.vehicles.at(0).validator).not.toBe(null);
686689
});
690+
687691
});

projects/ngx-sub-form/src/lib/ngx-sub-form.component.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ import {
2020
FormErrors,
2121
isNullOrUndefined,
2222
ControlsType,
23-
ArrayPropertyOf,
23+
ArrayPropertyOf, ArrayTypeOfPropertyOf,
2424
} from './ngx-sub-form-utils';
25-
import { FormGroupOptions, OnFormUpdate, TypedFormGroup } from './ngx-sub-form.types';
25+
import { FormGroupOptions, NgxFormWithArrayControls, OnFormUpdate, TypedFormGroup } from './ngx-sub-form.types';
2626

2727
type MapControlFunction<FormInterface, MapValue> = (ctrl: AbstractControl, key: keyof FormInterface) => MapValue;
2828
type FilterControlFunction<FormInterface> = (ctrl: AbstractControl, key: keyof FormInterface) => boolean;
@@ -228,13 +228,24 @@ export abstract class NgxSubFormComponent<ControlInterface, FormInterface = Cont
228228
formArray.removeAt(formArray.length - 1);
229229
}
230230

231-
while (formArray.length < value.length) {
232-
formArray.push(this.createFormArrayControl(key as ArrayPropertyOf<FormInterface>));
231+
for (let i = formArray.length; i < value.length; i++) {
232+
233+
if (this.formIsFormWithArrayControls()) {
234+
formArray.insert(i, this.createFormArrayControl(key as ArrayPropertyOf<FormInterface>, value[i]));
235+
} else {
236+
formArray.insert(i, new FormControl(value[i]));
237+
}
238+
233239
}
240+
234241
}
235242
});
236243
}
237244

245+
private formIsFormWithArrayControls(): this is NgxFormWithArrayControls<FormInterface> {
246+
return typeof ((this as unknown) as NgxFormWithArrayControls<FormInterface>).createFormArrayControl === 'function';
247+
}
248+
238249
private getMissingKeys(transformedValue: FormInterface | null) {
239250
// `controlKeys` can be an empty array, empty forms are allowed
240251
const missingKeys: (keyof FormInterface)[] = this.controlKeys.reduce(
@@ -251,13 +262,6 @@ export abstract class NgxSubFormComponent<ControlInterface, FormInterface = Cont
251262
return missingKeys;
252263
}
253264

254-
// override this hook to customize the creation of a FormControl within a FormArray
255-
// the undefined is required here as otherwise classes that are not overriding that hook
256-
// and do not have an array in the `FormInterface` would error as the type of key would be undefined
257-
protected createFormArrayControl(key: ArrayPropertyOf<FormInterface> | undefined): FormControl {
258-
return new FormControl();
259-
}
260-
261265
// when customizing the emission rate of your sub form component, remember not to **mutate** the stream
262266
// it is safe to throttle, debounce, delay, etc but using skip, first, last or mutating data inside
263267
// the stream will cause issues!

projects/ngx-sub-form/src/lib/ngx-sub-form.types.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { FormGroup, ValidationErrors } from '@angular/forms';
1+
import { FormControl, FormGroup, ValidationErrors } from '@angular/forms';
22
import { Observable } from 'rxjs';
3-
import { Controls, FormUpdate } from './ngx-sub-form-utils';
3+
import { ArrayPropertyOf, ArrayTypeOfPropertyOf, Controls, FormUpdate } from './ngx-sub-form-utils';
44

55
export interface OnFormUpdate<FormInterface> {
66
onFormUpdate?: (formUpdate: FormUpdate<FormInterface>) => void;
@@ -40,3 +40,9 @@ export interface FormGroupOptions<T> {
4040
*/
4141
updateOn?: 'change' | 'blur' | 'submit';
4242
}
43+
44+
// Unfortunately due to https://github.com/microsoft/TypeScript/issues/13995#issuecomment-504664533 the initial value
45+
// cannot be fully type narrowed to the exact type that will be passed.
46+
export interface NgxFormWithArrayControls<T> {
47+
createFormArrayControl(key: ArrayPropertyOf<T>, initialValue: ArrayTypeOfPropertyOf<T>): FormControl;
48+
}

src/app/main/listing/listing-form/vehicle-listing/crew-members/crew-members.component.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { Component } from '@angular/core';
22
import { FormArray, FormControl, Validators } from '@angular/forms';
3-
import { Controls, NgxSubFormRemapComponent, subformComponentProviders, ArrayPropertyOf } from 'ngx-sub-form';
3+
import {
4+
Controls,
5+
NgxSubFormRemapComponent,
6+
subformComponentProviders,
7+
ArrayPropertyOf,
8+
ArrayTypeOfPropertyOf, NgxFormWithArrayControls,
9+
} from 'ngx-sub-form';
410
import { CrewMember } from '../../../../../interfaces/crew-member.interface';
511

612
interface CrewMembersForm {
@@ -13,7 +19,7 @@ interface CrewMembersForm {
1319
styleUrls: ['./crew-members.component.scss'],
1420
providers: subformComponentProviders(CrewMembersComponent),
1521
})
16-
export class CrewMembersComponent extends NgxSubFormRemapComponent<CrewMember[], CrewMembersForm> {
22+
export class CrewMembersComponent extends NgxSubFormRemapComponent<CrewMember[], CrewMembersForm> implements NgxFormWithArrayControls<CrewMembersForm> {
1723
protected getFormControls(): Controls<CrewMembersForm> {
1824
return {
1925
crewMembers: new FormArray([]),
@@ -35,20 +41,23 @@ export class CrewMembersComponent extends NgxSubFormRemapComponent<CrewMember[],
3541
}
3642

3743
public addCrewMember(): void {
38-
this.formGroupControls.crewMembers.push(new FormControl());
44+
this.formGroupControls.crewMembers.push(this.createFormArrayControl('crewMembers', {
45+
firstName: '',
46+
lastName: '',
47+
}));
3948
}
4049

4150
// following method is not required and return by default a simple FormControl
4251
// if needed, you can use the `createFormArrayControl` hook to customize the creation
4352
// of your `FormControl`s that will be added to the `FormArray`
44-
protected createFormArrayControl(key: ArrayPropertyOf<CrewMembersForm>): FormControl {
53+
public createFormArrayControl(key: ArrayPropertyOf<CrewMembersForm> | undefined, initialValue: ArrayTypeOfPropertyOf<CrewMembersForm>): FormControl {
54+
4555
switch (key) {
4656
// note: the following string is type safe based on your form properties!
4757
case 'crewMembers':
48-
return new FormControl(null, [Validators.required]);
49-
58+
return new FormControl(initialValue, [Validators.required]);
5059
default:
51-
return new FormControl(null);
60+
return new FormControl(initialValue);
5261
}
5362
}
5463
}

0 commit comments

Comments
 (0)