Skip to content

Commit 3624fad

Browse files
author
Joe Gilreath
committed
feat: only emit values is status valid
1 parent d60d618 commit 3624fad

File tree

7 files changed

+111
-2
lines changed

7 files changed

+111
-2
lines changed

libs/reactive-forms/src/lib/core.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AbstractControl, ValidationErrors } from '@angular/forms';
22
import { defer, merge, Observable, of, Subscription } from 'rxjs';
3-
import { distinctUntilChanged, map } from 'rxjs/operators';
3+
import { distinctUntilChanged, filter, map } from 'rxjs/operators';
44

55
export function selectControlValue$<T, R>(
66
control: any,
@@ -21,6 +21,15 @@ export function controlValueChanges$<T>(
2121
) as Observable<T>;
2222
}
2323

24+
export function controlValidValueChanges$<T>(
25+
control: AbstractControl & { getRawValue: () => T }
26+
): Observable<T> {
27+
return merge(
28+
defer(() => of(control.getRawValue())),
29+
control.valueChanges.pipe(filter(() => control.valid), map(() => control.getRawValue()))
30+
) as Observable<T>;
31+
}
32+
2433
export type ControlState = 'VALID' | 'INVALID' | 'PENDING' | 'DISABLED';
2534

2635
export function controlStatus$<

libs/reactive-forms/src/lib/form-array.spec.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Validators } from '@angular/forms';
22
import { expectTypeOf } from 'expect-type';
33
import { Observable, of, Subject, Subscription } from 'rxjs';
4-
import {ControlsOf, FormControl, FormGroup, ValuesOf} from '..';
4+
import {ControlsOf, FormControl, FormGroup} from '..';
55
import { ControlState } from './core';
66
import { FormArray } from './form-array';
77

@@ -224,6 +224,13 @@ const createArray = (withError = false) => {
224224
);
225225
};
226226

227+
const createInvalidArray = (withError = false) => {
228+
return new FormArray<string | null>(
229+
[new FormControl(null, Validators.required), new FormControl(null, Validators.required)],
230+
withError ? errorFn : []
231+
);
232+
};
233+
227234
describe('FormArray Functionality', () => {
228235
it('should valueChanges$', () => {
229236
const control = createArray();
@@ -240,6 +247,25 @@ describe('FormArray Functionality', () => {
240247
expect(spy).toHaveBeenCalledWith(['1', '3', '']);
241248
});
242249

250+
it('should validValueChanges$', () => {
251+
const control = createInvalidArray();
252+
const spy = jest.fn();
253+
control.validValue$.subscribe(spy);
254+
expect(spy).toHaveBeenCalledTimes(1);
255+
control.patchValue(['1', '2']);
256+
expect(spy).toHaveBeenCalledTimes(2);
257+
expect(spy).toHaveBeenCalledWith(['1', '2']);
258+
control.push(new FormControl('3', Validators.required));
259+
expect(spy).toHaveBeenCalledTimes(3);
260+
expect(spy).toHaveBeenCalledWith(['1', '2', '3']);
261+
control.push(new FormControl(null, Validators.required));
262+
expect(spy).toHaveBeenCalledTimes(3);
263+
expect(spy).not.toHaveReturnedWith(['1', '2', '3', null]);
264+
control.removeAt(3);
265+
expect(spy).toHaveBeenCalledTimes(4);
266+
expect(spy).toHaveBeenCalledWith(['1', '2', '3']);
267+
});
268+
243269
it('should disabledChanges$', () => {
244270
const control = createArray();
245271
const spy = jest.fn();

libs/reactive-forms/src/lib/form-array.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
disableControl,
1919
enableControl,
2020
markAllDirty,
21+
controlValidValueChanges$,
2122
} from './core';
2223
import { DeepPartial } from './types';
2324

@@ -46,6 +47,7 @@ export class FormArray<
4647
.asObservable()
4748
.pipe(distinctUntilChanged());
4849
readonly value$ = controlValueChanges$<Array<ValueOfControl<Control>>>(this);
50+
readonly validValue$ = controlValidValueChanges$<Array<ValueOfControl<Control>>>(this);
4951
readonly disabled$ = controlStatus$(this, 'disabled');
5052
readonly enabled$ = controlStatus$(this, 'enabled');
5153
readonly invalid$ = controlStatus$(this, 'invalid');

libs/reactive-forms/src/lib/form-control.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@ describe('FormControl Functionality', () => {
1414
expect(spy).toHaveBeenCalledWith('patched');
1515
});
1616

17+
it('should validValueChanges$', () => {
18+
const control = new FormControl<string | null>(null, Validators.required);
19+
const spy = jest.fn();
20+
control.validValue$.subscribe(spy);
21+
expect(spy).toHaveBeenCalledTimes(1);
22+
control.patchValue('patched');
23+
expect(spy).toHaveBeenCalledTimes(2);
24+
expect(spy).toHaveBeenCalledWith('patched');
25+
control.patchValue('');
26+
expect(spy).toHaveBeenCalledTimes(2);
27+
expect(spy).not.toHaveBeenCalledWith('');
28+
});
29+
1730
it('should disabledChanges$', () => {
1831
const control = new FormControl<string>();
1932
const spy = jest.fn();

libs/reactive-forms/src/lib/form-control.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
removeError,
1717
hasErrorAnd,
1818
controlErrorChanges$,
19+
controlValidValueChanges$,
1920
} from './core';
2021
import { BoxedValue } from './types';
2122

@@ -34,6 +35,7 @@ export class FormControl<T> extends UntypedFormControl {
3435
.asObservable()
3536
.pipe(distinctUntilChanged());
3637
readonly value$ = controlValueChanges$<T>(this);
38+
readonly validValue$ = controlValidValueChanges$<T>(this);
3739
readonly disabled$ = controlStatus$(this, 'disabled');
3840
readonly enabled$ = controlStatus$(this, 'enabled');
3941
readonly invalid$ = controlStatus$(this, 'invalid');

libs/reactive-forms/src/lib/form-group.spec.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ const createGroup = () => {
2525
);
2626
};
2727

28+
const createInvalidGroup = () => {
29+
return new FormGroup(
30+
{
31+
name: new FormControl<string | null>(null, Validators.required),
32+
phone: new FormGroup({
33+
num: new FormControl<number | null>(null, Validators.required),
34+
prefix: new FormControl<number | null>(null, Validators.required),
35+
}),
36+
}
37+
);
38+
};
39+
2840
describe('FormGroup Functionality', () => {
2941
it('should valueChanges$', () => {
3042
const control = createGroup();
@@ -46,6 +58,49 @@ describe('FormGroup Functionality', () => {
4658
});
4759
});
4860

61+
it('should validValueChanges$', () => {
62+
const control = createInvalidGroup();
63+
const spy = jest.fn();
64+
control.validValue$.subscribe(spy);
65+
66+
expect(spy).toHaveBeenCalledTimes(1);
67+
control.patchValue({
68+
name: 'jim',
69+
phone: {
70+
num: 0,
71+
prefix: 1
72+
}
73+
});
74+
75+
expect(spy).toHaveBeenCalledTimes(2);
76+
expect(spy).toHaveBeenCalledWith({
77+
name: 'jim',
78+
phone: { num: 0, prefix: 1 },
79+
});
80+
81+
control.patchValue({
82+
name: 'changed',
83+
});
84+
85+
expect(spy).toHaveBeenCalledTimes(3);
86+
expect(spy).toHaveBeenCalledWith({
87+
name: 'changed',
88+
phone: { num: 0, prefix: 1 },
89+
});
90+
91+
control.patchValue({
92+
phone: {
93+
num: null
94+
}
95+
});
96+
97+
expect(spy).toHaveBeenCalledTimes(3);
98+
expect(spy).not.toHaveBeenCalledWith({
99+
name: 'changed',
100+
phone: { num: null, prefix: 1 },
101+
});
102+
});
103+
49104
it('should disabledChanges$', () => {
50105
const control = createGroup();
51106
const spy = jest.fn();

libs/reactive-forms/src/lib/form-group.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
controlEnabledWhile,
1111
controlErrorChanges$,
1212
controlStatus$,
13+
controlValidValueChanges$,
1314
controlValueChanges$,
1415
disableControl,
1516
enableControl,
@@ -36,6 +37,7 @@ export class FormGroup<T extends Record<string, any>> extends UntypedFormGroup {
3637
.asObservable()
3738
.pipe(distinctUntilChanged());
3839
readonly value$ = controlValueChanges$<ValuesOf<T>>(this);
40+
readonly validValue$ = controlValidValueChanges$<ValuesOf<T>>(this);
3941
readonly disabled$ = controlStatus$(this, 'disabled');
4042
readonly enabled$ = controlStatus$(this, 'enabled');
4143
readonly invalid$ = controlStatus$(this, 'invalid');

0 commit comments

Comments
 (0)