Skip to content

Commit f0fb1d8

Browse files
leonsenftcrisbeto
authored andcommitted
fix(forms): allow custom controls to require hidden input
* Allow custom controls to make `hidden` a required input * Refactor test for `hidden` input to be consistent with other control properties * Test that `hidden` inputs are reset when the field binding changes (cherry picked from commit 82edf18)
1 parent 9748b0d commit f0fb1d8

2 files changed

Lines changed: 59 additions & 28 deletions

File tree

packages/compiler-cli/src/ngtsc/typecheck/src/ops/signal_forms.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const formControlInputFields = [
4040
// Should be kept in sync with the `FormUiControl` bindings,
4141
// defined in `packages/forms/signals/src/api/control.ts`.
4242
'errors',
43+
'hidden',
4344
'invalid',
4445
'disabled',
4546
'disabledReasons',

packages/forms/signals/test/web/field_directive.spec.ts

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,64 @@ describe('field directive', () => {
236236
});
237237
});
238238

239+
describe('hidden', () => {
240+
it('should bind to a custom control', () => {
241+
@Component({selector: 'custom-control', template: ``})
242+
class CustomControl implements FormValueControl<string> {
243+
readonly value = model.required<string>();
244+
readonly hidden = input.required<boolean>();
245+
}
246+
247+
const visible = signal(false);
248+
249+
@Component({
250+
imports: [Field, CustomControl],
251+
template: `<custom-control [field]="field()" />`,
252+
})
253+
class TestCmp {
254+
readonly f = form(signal(''), (p) => {
255+
hidden(p, () => !visible());
256+
});
257+
readonly field = signal(this.f);
258+
readonly customControl = viewChild.required(CustomControl);
259+
}
260+
261+
const fixture = act(() => TestBed.createComponent(TestCmp));
262+
const component = fixture.componentInstance;
263+
expect(component.customControl().hidden()).toBe(true);
264+
265+
act(() => visible.set(true));
266+
expect(component.customControl().hidden()).toBe(false);
267+
});
268+
269+
it('should be reset when field changes on custom control', () => {
270+
@Component({selector: 'custom-control', template: ``})
271+
class CustomControl implements FormValueControl<string> {
272+
readonly value = model.required<string>();
273+
readonly hidden = input.required<boolean>();
274+
}
275+
276+
@Component({
277+
imports: [Field, CustomControl],
278+
template: `<custom-control [field]="field()" />`,
279+
})
280+
class TestCmp {
281+
readonly f = form(signal({x: 'a', y: 'b'}), (p) => {
282+
hidden(p.x, () => true);
283+
});
284+
readonly field = signal(this.f.x);
285+
readonly customControl = viewChild.required(CustomControl);
286+
}
287+
288+
const fixture = act(() => TestBed.createComponent(TestCmp));
289+
const component = fixture.componentInstance;
290+
expect(component.customControl().hidden()).toBe(true);
291+
292+
act(() => component.field.set(component.f.y));
293+
expect(component.customControl().hidden()).toBe(false);
294+
});
295+
});
296+
239297
describe('name', () => {
240298
it('should bind to native control', () => {
241299
@Component({
@@ -2036,34 +2094,6 @@ describe('field directive', () => {
20362094
expect(comp.myInput().invalid()).toBe(false);
20372095
});
20382096

2039-
it('should synchronize hidden status', () => {
2040-
@Component({
2041-
selector: 'my-input',
2042-
template: '<input #i [value]="value()" (input)="value.set(i.value)" />',
2043-
})
2044-
class CustomInput implements FormValueControl<string> {
2045-
value = model('');
2046-
hidden = input(false);
2047-
}
2048-
2049-
@Component({
2050-
template: ` <my-input [field]="f" /> `,
2051-
imports: [CustomInput, Field],
2052-
})
2053-
class HiddenTestCmp {
2054-
myInput = viewChild.required<CustomInput>(CustomInput);
2055-
data = signal('');
2056-
f = form(this.data, (p) => {
2057-
hidden(p, ({value}) => value() === '');
2058-
});
2059-
}
2060-
2061-
const comp = act(() => TestBed.createComponent(HiddenTestCmp)).componentInstance;
2062-
expect(comp.myInput().hidden()).toBe(true);
2063-
act(() => comp.f().value.set('visible'));
2064-
expect(comp.myInput().hidden()).toBe(false);
2065-
});
2066-
20672097
it('should synchronize dirty status', () => {
20682098
@Component({
20692099
selector: 'my-input',

0 commit comments

Comments
 (0)