Skip to content

Commit 099eea2

Browse files
DafnikRobby Rabbitmanrobert hasemann
authored
feat(control-error): accept control name (#663)
Co-authored-by: Robby Rabbitman <robby.rabbitman+ngxtension@gmail.com> Co-authored-by: robert hasemann <robert.hasemann@hdi.de>
1 parent 273ef77 commit 099eea2

File tree

5 files changed

+246
-24
lines changed

5 files changed

+246
-24
lines changed

apps/test-app/src/app/control-error/control-error.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import { NgxControlError } from 'ngxtension/control-error';
1616
1717
<label>
1818
<b>Name</b>
19-
<input type="text" [formControl]="form.controls.name" />
20-
<strong *ngxControlError="form.controls.name; track: 'required'">
19+
<input type="text" formControlName="name" />
20+
<strong *ngxControlError="'name'; track: 'required'">
2121
Name is required.
2222
</strong>
2323
</label>

docs/src/content/docs/es/utilities/Forms/control-error.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,20 @@ Sin `NgxControlError`:
4040
</label>
4141
```
4242

43+
En un formulario también puedes pasar el nombre del control en lugar de la instancia.
44+
45+
```html
46+
<form [formGroup]="form">
47+
<label>
48+
<b>Nombre</b>
49+
<input type="text" formControlName="name" />
50+
<strong *ngxControlError="'name'; track: 'required'">
51+
Se requiere el nombre.
52+
</strong>
53+
</label>
54+
</form>
55+
```
56+
4357
## Configuración
4458

4559
Un `StateMatcher` define cuándo el control proporcionado está en un _estado de error_.

docs/src/content/docs/utilities/Forms/control-error.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ without `NgxControlError`:
3838
</label>
3939
```
4040

41+
In a form you can also pass the name of the control instead of the instance.
42+
43+
```html
44+
<form [formGroup]="form">
45+
<label>
46+
<b>Name</b>
47+
<input type="text" formControlName="name" />
48+
<strong *ngxControlError="'name'; track: 'required'">
49+
Name is required.
50+
</strong>
51+
</label>
52+
</form>
53+
```
54+
4155
## Configuration
4256

4357
A `StateMatcher` defines when the provided control is in an _error state_.

libs/ngxtension/control-error/src/control-error.spec.ts

Lines changed: 169 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import {
88
import { TestBed } from '@angular/core/testing';
99
import {
1010
FormControl,
11+
FormGroup,
1112
FormGroupDirective,
1213
NgForm,
14+
ReactiveFormsModule,
1315
Validators,
1416
} from '@angular/forms';
1517
import { By } from '@angular/platform-browser';
@@ -22,7 +24,7 @@ import {
2224
} from './control-error';
2325

2426
describe('NgxControlError', () => {
25-
const unitTest = (test: () => void | Promise<void>) => async () => {
27+
const isolatedTest = (test: () => void | Promise<void>) => async () => {
2628
TestBed.overrideProvider(TemplateRef, { useValue: undefined });
2729
TestBed.overrideProvider(ViewContainerRef, {
2830
useValue: {
@@ -39,13 +41,13 @@ describe('NgxControlError', () => {
3941
await TestBed.runInInjectionContext(test);
4042
};
4143

42-
const render = (
44+
const render = <TInputs>(
4345
template: string,
44-
inputs?: Partial<NgxControlError> | undefined,
46+
inputs?: Partial<TInputs> | undefined,
4547
providers?: Provider[],
4648
) => {
4749
@Component({
48-
imports: [CommonModule, NgxControlError],
50+
imports: [CommonModule, ReactiveFormsModule, NgxControlError],
4951
standalone: true,
5052
template,
5153
providers,
@@ -70,12 +72,12 @@ describe('NgxControlError', () => {
7072

7173
it(
7274
'should be created',
73-
unitTest(() => expect(new NgxControlError()).toBeTruthy()),
75+
isolatedTest(() => expect(new NgxControlError()).toBeTruthy()),
7476
);
7577

7678
it(
7779
'should have a context guard',
78-
unitTest(() =>
80+
isolatedTest(() =>
7981
expect(
8082
NgxControlError.ngTemplateContextGuard(new NgxControlError(), {}),
8183
).toBe(true),
@@ -85,7 +87,7 @@ describe('NgxControlError', () => {
8587
describe('should have an error when the control includes the tracked error and the control is in an error state', () => {
8688
it(
8789
'respecting track changes',
88-
unitTest(() => {
90+
isolatedTest(() => {
8991
const instance = new NgxControlError();
9092

9193
instance.track$.set('required');
@@ -104,7 +106,7 @@ describe('NgxControlError', () => {
104106

105107
it(
106108
'when it has at least 1 tracked error ',
107-
unitTest(() => {
109+
isolatedTest(() => {
108110
const instance = new NgxControlError();
109111
const control = new FormControl('42', [
110112
Validators.minLength(3),
@@ -128,7 +130,7 @@ describe('NgxControlError', () => {
128130

129131
it(
130132
'respecting error state matcher changes',
131-
unitTest(() => {
133+
isolatedTest(() => {
132134
const instance = new NgxControlError();
133135

134136
instance.track$.set('required');
@@ -147,7 +149,7 @@ describe('NgxControlError', () => {
147149

148150
it(
149151
'respecting control instance changes',
150-
unitTest(() => {
152+
isolatedTest(() => {
151153
const instance = new NgxControlError();
152154

153155
instance.track$.set('required');
@@ -167,7 +169,7 @@ describe('NgxControlError', () => {
167169

168170
it(
169171
'DEFAULT_ERROR_STATE_MATCHER should match when the control is: 1. invalid 2. touched or its parent is submitted',
170-
unitTest(() => {
172+
isolatedTest(() => {
171173
const instance = new NgxControlError();
172174
const control = new FormControl('', Validators.required);
173175

@@ -241,17 +243,30 @@ describe('NgxControlError', () => {
241243
it('should have an injectable error state matcher', () => {
242244
const errorStateMatcher = jest.fn();
243245

246+
const params = {
247+
error: 'required',
248+
control: new FormControl('', Validators.required),
249+
};
250+
244251
const [, controlError] = render(
245-
'<ng-template ngxControlError />',
246-
undefined,
252+
'<span *ngxControlError="control; track: error">42</span>',
253+
params,
247254
provideNgxControlError({ errorStateMatcher: () => errorStateMatcher }),
248255
);
249256

250257
expect(controlError.errorStateMatcher$()).toBe(errorStateMatcher);
251258
});
252259

253260
it('should use the default error state matcher as default', () => {
254-
const [, controlError] = render('<ng-template ngxControlError />');
261+
const params = {
262+
error: 'required',
263+
control: new FormControl('', Validators.required),
264+
};
265+
266+
const [, controlError] = render(
267+
'<span *ngxControlError="control; track: error">42</span>',
268+
params,
269+
);
255270

256271
expect(controlError.errorStateMatcher$()).toBe(
257272
NGX_DEFAULT_CONTROL_ERROR_STATE_MATCHER,
@@ -299,4 +314,144 @@ describe('NgxControlError', () => {
299314
'INVALID - true',
300315
);
301316
});
317+
318+
it('should resolve a control by its name', () => {
319+
const params = {
320+
error: 'required',
321+
form: new FormGroup({
322+
name: new FormControl('', Validators.required),
323+
}),
324+
stateMatcher: () => of(true),
325+
};
326+
327+
const [fixture, controlError] = render(
328+
`<form [formGroup]="form">
329+
<label>
330+
<input formControlName="name" />
331+
<span *ngxControlError="'name'; track: error, errorStateMatcher: stateMatcher">42</span>
332+
</label>
333+
</form>
334+
`,
335+
params,
336+
);
337+
338+
fixture.detectChanges();
339+
340+
expect(fixture.debugElement.nativeElement.textContent).toBe('42');
341+
342+
controlError.errorStateMatcher = () => of(false);
343+
344+
fixture.detectChanges();
345+
346+
expect(fixture.debugElement.nativeElement.textContent).toBe('');
347+
});
348+
349+
it('should throw when a control cannot be found because there is no parent control', () => {
350+
expect(() =>
351+
render(`
352+
<span *ngxControlError="'name'; track: 'required'">42</span>
353+
`),
354+
).toThrow(
355+
'[NgxControlError]: A control name cannot be specified without a parent FormGroup.',
356+
);
357+
});
358+
359+
it('should throw when a control cannot be found in the parent form group', () => {
360+
const params = {
361+
form: new FormGroup({
362+
name: new FormControl('', Validators.required),
363+
}),
364+
};
365+
366+
expect(() =>
367+
render(
368+
`<form [formGroup]="form">
369+
<span *ngxControlError="'nonExistentControlname'; track: 'required'">42</span>
370+
</form>
371+
`,
372+
params,
373+
),
374+
).toThrow(
375+
`[NgxControlError]: Cannot find control with name 'nonExistentControlname'.`,
376+
);
377+
});
378+
379+
it('should throw when a control cannot be found in a nested parent form group', () => {
380+
expect(() =>
381+
render(
382+
`<form [formGroup]="form">
383+
<div formGroupName="nested">
384+
<span *ngxControlError="'nonExistentControlname'; track: 'required'">42</span>
385+
</div>
386+
</form>
387+
`,
388+
{
389+
form: new FormGroup({
390+
nested: new FormGroup({
391+
name: new FormControl('', Validators.required),
392+
}),
393+
}),
394+
},
395+
),
396+
).toThrow(
397+
`[NgxControlError]: Cannot find control with name 'nonExistentControlname'.`,
398+
);
399+
400+
expect(() =>
401+
render(
402+
`<form [formGroup]="form">
403+
<div formGroupName="nested">
404+
<span *ngxControlError="'name1'; track: 'required'">42</span>
405+
</div>
406+
</form>
407+
`,
408+
{
409+
form: new FormGroup({
410+
name1: new FormControl('', Validators.required),
411+
nested: new FormGroup({
412+
name2: new FormControl('', Validators.required),
413+
}),
414+
}),
415+
},
416+
),
417+
).toThrow(`[NgxControlError]: Cannot find control with name 'name1'.`);
418+
});
419+
420+
it('should resolve a nested control by its name', () => {
421+
const params = {
422+
error: 'required',
423+
form: new FormGroup({
424+
nested: new FormGroup({
425+
name: new FormControl('', Validators.required),
426+
}),
427+
}),
428+
stateMatcher: () => of(true),
429+
};
430+
431+
const [fixture, controlError] = render(
432+
`
433+
<form [formGroup]="form">
434+
<div formGroupName="nested">
435+
<label>
436+
<input formControlName="name" />
437+
<span
438+
*ngxControlError="'name'; track: error, errorStateMatcher: stateMatcher"
439+
>42</span>
440+
</label>
441+
</div>
442+
</form>
443+
`,
444+
params,
445+
);
446+
447+
fixture.detectChanges();
448+
449+
expect(fixture.debugElement.nativeElement.textContent).toBe('42');
450+
451+
controlError.errorStateMatcher = () => of(false);
452+
453+
fixture.detectChanges();
454+
455+
expect(fixture.debugElement.nativeElement.textContent).toBe('');
456+
});
302457
});

0 commit comments

Comments
 (0)