Skip to content
This repository was archived by the owner on Aug 25, 2020. It is now read-only.

Commit 90a6e6e

Browse files
committed
feat: join fields and form models
BREAKING CHANGE: new json config format
1 parent 7a7820b commit 90a6e6e

12 files changed

+153
-239
lines changed

dist/ngx-forms.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { TagInputModule } from 'ngx-chips';
1010
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
1111
import { DynamicFieldService } from './app/services/dynamic-field.service';
1212
import { PreloadService, Components } from './app/services/preload.service';
13-
import { FieldConfigService } from './app/services/field-config.service';
1413

1514
@NgModule({
1615
imports: [
@@ -36,8 +35,7 @@ import { FieldConfigService } from './app/services/field-config.service';
3635
],
3736
providers: [
3837
DynamicFieldService,
39-
PreloadService,
40-
FieldConfigService
38+
PreloadService
4139
],
4240
schemas: [
4341
NO_ERRORS_SCHEMA

src/app/components/dynamic-field/dynamic-field.directive.spec.ts

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ describe('dynamicField', () => {
3030
let componentError: TestComponent;
3131
let fixture: ComponentFixture<TestComponent>;
3232
let fixtureError: ComponentFixture<TestComponent>;
33-
let debugElement: DebugElement;
3433
let directiveEl;
3534

3635
const formBuilder: FormBuilder = new FormBuilder();
@@ -68,6 +67,66 @@ describe('dynamicField', () => {
6867
fixtureError.detectChanges();
6968
}).toThrowError()
7069

71-
})
70+
});
71+
72+
describe('createControl()', () => {
73+
let dir;
74+
75+
beforeEach(() => {
76+
TestBed.compileComponents()
77+
directiveEl = fixture.debugElement.query(By.directive(DynamicFieldDirective));
78+
dir = directiveEl.injector.get(DynamicFieldDirective);
79+
});
80+
81+
let cfg = { name: 'test', type: 'text', disabled: true, required: true, minLength: 5, maxLength: 10, email: true, min: 1, max: 10, pattern: new RegExp('\d'), nullValidator: true, value: 5 };
82+
83+
it('should set pattern validator', () => {
84+
let control = dir.createControl({ name: 'test', type: 'text', pattern: new RegExp('\d'), value: 5 });
85+
const vals = control.validator(control);
86+
expect(vals.pattern).toBeTruthy();
87+
});
88+
89+
it('should set email validator', () => {
90+
let control = dir.createControl({ name: 'test', type: 'text', email: true, value: 5 });
91+
const vals = control.validator(control);
92+
expect(vals.email).toBeTruthy();
93+
});
7294

95+
it('should set min length validator', () => {
96+
let control = dir.createControl({ name: 'test', type: 'text', minLength: 5, maxLength: 10, value: 'test' });
97+
const vals = control.validator(control);
98+
expect(vals.minlength).toBeTruthy();
99+
});
100+
101+
it('should set max length validator', () => {
102+
let control = dir.createControl({ name: 'test', type: 'text', maxLength: 2, value: 'test' });
103+
const vals = control.validator(control);
104+
expect(vals.maxlength).toBeTruthy();
105+
});
106+
107+
it('should set required validator', () => {
108+
let control = dir.createControl({ name: 'test', type: 'text', required: true, value: '' });
109+
const vals = control.validator(control);
110+
expect(vals.required).toBeTruthy();
111+
});
112+
113+
it('should set min value validator', () => {
114+
let control = dir.createControl({ name: 'test', type: 'text', min: 2, value: 1 });
115+
const vals = control.validator(control);
116+
expect(vals.min).toBeTruthy();
117+
});
118+
119+
it('should set max value validator', () => {
120+
let control = dir.createControl({ name: 'test', type: 'text', max: 2, value: 22 });
121+
const vals = control.validator(control);
122+
expect(vals.max).toBeTruthy();
123+
});
124+
125+
it('should set value', () => {
126+
let control = dir.createControl(cfg);
127+
expect(control.value).toEqual(cfg.value);
128+
});
129+
130+
});
73131
});
132+

src/app/components/dynamic-field/dynamic-field.directive.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { ComponentRef, Directive, Input, OnChanges, OnInit, ViewContainerRef, ComponentFactoryResolver, OnDestroy, Optional, Host, SkipSelf } from '@angular/core';
2-
import { FormGroup } from '@angular/forms';
1+
import { ComponentRef, Directive, Input, OnChanges, OnInit, ViewContainerRef, ComponentFactoryResolver, OnDestroy } from '@angular/core';
2+
import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/forms';
33
import { Field } from '../../models/field.interface';
44
import { IFieldConfig } from '../../models/field-config.interface';
55
import { DynamicFieldService } from '../../services/dynamic-field.service';
@@ -18,7 +18,8 @@ export class DynamicFieldDirective implements Field, OnChanges, OnInit, OnDestro
1818
private resolver: ComponentFactoryResolver,
1919
private container: ViewContainerRef,
2020
private dynamicFieldService: DynamicFieldService,
21-
private preloadService: PreloadService
21+
private preloadService: PreloadService,
22+
private fb: FormBuilder,
2223
) { }
2324

2425
ngOnChanges() {
@@ -36,6 +37,26 @@ export class DynamicFieldDirective implements Field, OnChanges, OnInit, OnDestro
3637
this.component.instance.field = this.field;
3738
this.component.instance.group = this.group;
3839
this.component.instance.model = this.model;
40+
41+
this.group.addControl(this.field.name, this.createControl(this.field));
42+
43+
if (this.model && this.model[this.field.name]) {
44+
this.group.get(this.field.name).patchValue(this.model[this.field.name]);
45+
}
46+
}
47+
48+
public createControl(cfg: IFieldConfig): FormControl {
49+
const { disabled, required, minLength, maxLength, email, min, max, pattern, value } = cfg;
50+
const validators = [];
51+
if (required !== undefined && required) { validators.push(Validators.required); }
52+
if (minLength !== undefined) { validators.push(Validators.minLength(minLength)); }
53+
if (maxLength !== undefined) { validators.push(Validators.maxLength(maxLength)); }
54+
if (email !== undefined) { validators.push(Validators.email); }
55+
if (min !== undefined) { validators.push(Validators.min(min)); }
56+
if (max !== undefined) { validators.push(Validators.max(max)); }
57+
if (pattern !== undefined) { validators.push(Validators.pattern(pattern)); }
58+
59+
return this.fb.control({ disabled, value }, validators);
3960
}
4061

4162
ngOnDestroy(): void {

src/app/containers/dynamic-form/dynamic-form.component.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
<form-nav></form-nav>
44
</div>
55
<div class="col-md-10" [formGroup]="form">
6-
<div *ngFor="let group of formConfig.form" [navTab]="group">
6+
<div *ngFor="let panel of formConfig.form" [navTab]="panel">
77

8-
<ng-container *ngIf="group.fields">
9-
<dynamic-panel [panelConfig]="group" [group]="form" [model]="model" [hidden]="group.hidden"></dynamic-panel>
8+
<ng-container *ngIf="panel.fields">
9+
<dynamic-panel [panelConfig]="panel" [group]="form" [model]="model" [hidden]="panel.hidden"></dynamic-panel>
1010
</ng-container>
1111

12-
<ng-container *ngIf="group.panels">
13-
<dynamic-panel *ngFor="let panelConfig of group.panels" [panelConfig]="panelConfig" [group]="form"
14-
[model]="model" [hidden]="group.hidden"></dynamic-panel>
12+
<ng-container *ngIf="panel.panels">
13+
<dynamic-panel *ngFor="let panelConfig of panel.panels" [panelConfig]="panelConfig" [group]="form"
14+
[model]="model" [hidden]="panel.hidden"></dynamic-panel>
1515
</ng-container>
1616
</div>
1717
</div>

src/app/containers/dynamic-form/dynamic-form.component.spec.ts

Lines changed: 27 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { FormSelectComponent } from '../../components/form-select/form-select.co
1212
import { FormInputHiddenComponent } from '../../components/form-hidden/form-hidden.component';
1313
import { By } from '@angular/platform-browser';
1414
import { FormNavModule } from '../../../nav/nav-app';
15-
import { FieldConfigService } from '../../services/field-config.service';
1615

1716
interface IFormConfig {
1817
form: any;
@@ -27,7 +26,7 @@ interface IDynamicForm {
2726
}
2827

2928
@Component({
30-
template: `<dynamic-form [formConfig]="formConfig" #form="dynamicForm" [model]="data" [dataProvider]="dataProvider" [lookups]="lookups"></dynamic-form>`
29+
template: `<dynamic-form [formConfig]="formConfig" #form="dynamicForm" [model]="data" [lookups]="lookups"></dynamic-form>`
3130
})
3231
class TestComponent implements IDynamicForm {
3332
formConfig
@@ -52,20 +51,21 @@ describe('DynamicFormComponent', () => {
5251
TestBed.configureTestingModule({
5352
declarations: [DynamicFieldDirective, TestComponent, DynamicFormComponent, DynamicPanelComponent],
5453
imports: [FormsModule, ReactiveFormsModule, TestModule, FormNavModule],
55-
providers: [DynamicFieldService, PreloadService, FieldConfigService]
54+
providers: [DynamicFieldService, PreloadService]
5655
})
5756
.compileComponents()
5857
.then(() => {
5958
fixture = TestBed.createComponent(TestComponent);
6059
component = fixture.componentInstance;
6160
component.formConfig = {
62-
fields: [
63-
{ type: 'hidden', name: 'id' },
64-
{ type: 'text', label: 'Publication Title:', name: 'title', placeholder: '', required: true },
65-
{ type: 'select', label: 'Publication Type', name: 'activityType', options: ['a', 'b'] }
66-
],
67-
form: [{ label: 'Title and Abstract', panels: [{ label: 'Title and Abstract', fields: ['id', 'title', 'activityType'] }] }]
68-
}
61+
form: [{
62+
fields: [
63+
{ type: 'hidden', name: 'id' },
64+
{ type: 'text', label: 'Publication Title:', name: 'title', placeholder: '', required: true },
65+
{ type: 'select', label: 'Publication Type', name: 'activityType', options: ['a', 'b'] }
66+
]
67+
}]
68+
};
6969

7070
fixture.detectChanges();
7171
});
@@ -110,22 +110,17 @@ describe('DynamicFormComponent Core', () => {
110110
TestBed.configureTestingModule({
111111
declarations: [DynamicFieldDirective, TestComponent, DynamicFormComponent, DynamicPanelComponent],
112112
imports: [FormsModule, ReactiveFormsModule, TestModule, FormNavModule],
113-
providers: [DynamicFieldService, PreloadService, FieldConfigService]
113+
providers: [DynamicFieldService, PreloadService]
114114
}).compileComponents();
115115
});
116116

117117
beforeEach(() => {
118118
fixture = TestBed.createComponent(DynamicFormComponent);
119119
component = fixture.componentInstance;
120120
component.formConfig = {
121-
fields: [
122-
{ type: 'hidden', name: 'id' },
123-
{ type: 'text', name: 'title', required: true },
124-
{ type: 'text', name: 'test', required: true }
125-
],
126121
form: [
127-
{ label: 'fields and panels', panels: [{ label: 'fields', fields: ['title'] }] },
128-
{ label: 'fields, no panels', fields: ['test'] },
122+
{ label: 'fields and panels', panels: [{ label: 'fields', fields: [{ type: 'text', name: 'title', required: true }] }] },
123+
{ label: 'fields, no panels', fields: [{ type: 'text', name: 'test', required: true }] },
129124
{ label: 'no fields with panels', panels: [{ label: 'no fields' }] },
130125
{ label: 'no fields, no panels' }
131126
]
@@ -135,128 +130,50 @@ describe('DynamicFormComponent Core', () => {
135130
});
136131

137132
describe('ngOnInit()', () => {
138-
it('should add fields references to config group', () => {
139-
expect(component.formConfig.form[0].controls.length).toEqual(1);
140-
});
141-
142-
it('should add fields references to config group with no panels', () => {
143-
expect(component.formConfig.form[1].controls.length).toEqual(1);
144-
});
145-
146-
it('should not add fields references to config group with no panels', () => {
147-
expect(component.formConfig.form[2].controls.length).toEqual(0);
148-
});
149-
150-
it('should not add fields references to config group with no panels no fields', () => {
151-
expect(component.formConfig.form[3].controls.length).toEqual(0);
152-
});
153-
154-
it('should patch model', () => {
155-
expect(component.form.value.title).toEqual(model.title);
156-
});
157133

158-
it('should not patch model', () => {
159-
component.model = null;
160-
component.ngOnInit();
161-
expect(component.form.value.title).toBeUndefined();
134+
it('should create group', () => {
135+
expect(component.form).toBeDefined();
162136
});
163137

164138
describe('Lookup Expansion', () => {
165-
it('should not set lookup when there is no lookup in field', () => {
166-
component.lookups = { test: ['a', 'b', 'c'] };
167-
component.formConfig = { fields: [{ type: 'text', name: 'title' }], form: [] };
168-
component.ngOnInit();
169-
expect(component.formConfig.fields[0].options).toBeUndefined();
170-
});
171139

172140
it('should not find lookup when no lookups were passed', () => {
173141
component.lookups = null;
174-
component.formConfig = { fields: [{ type: 'text', name: 'title', lookup: 'test' }], form: [] };
142+
component.formConfig = { form: [{ fields: [{ type: 'text', name: 'title', lookup: 'test' }] }] };
175143
component.ngOnInit();
176-
expect(component.formConfig.fields[0].options).toBeUndefined();
144+
expect(component.formConfig.form[0].fields[0].options).toBeUndefined();
177145
});
178146

179147
it('should not find lookup when no lookups found', () => {
180148
component.lookups = { test: ['a', 'b', 'c'] };;
181-
component.formConfig = { fields: [{ type: 'text', name: 'title', lookup: 'test1' }], form: [] };
149+
component.formConfig = { form: [{ fields: [{ type: 'text', name: 'title' }] }] };
182150
component.ngOnInit();
183-
expect(component.formConfig.fields[0].options).toBeUndefined();
151+
expect(component.formConfig.form[0].fields[0].options).toBeUndefined();
184152
});
185153

186-
it('should copy lookup', () => {
154+
it('should not copy lookup with wrong name', () => {
187155
component.lookups = { test: ['a', 'b', 'c'] };;
188-
component.formConfig = { fields: [{ type: 'text', name: 'title', lookup: 'test' }], form: [] };
156+
component.formConfig = { form: [{ fields: [{ type: 'text', name: 'title', lookup: 'test1' }] }] };
189157
component.ngOnInit();
190-
expect(component.formConfig.fields[0].options).toBeDefined();
158+
expect(component.formConfig.form[0].fields[0].options).toBeUndefined();
191159
});
192160

193161
it('should copy lookup', () => {
194162
component.lookups = { test: ['a', 'b', 'c'] };;
195-
component.formConfig = { fields: [{ type: 'text', name: 'title', lookup: 'test' }], form: [] };
163+
component.formConfig = { form: [{ fields: [{ type: 'text', name: 'title', lookup: 'test' }] }] };
196164
component.ngOnInit();
197-
expect(component.formConfig.fields[0].options).toEqual(['a', 'b', 'c']);
165+
expect(component.formConfig.form[0].fields[0].options).toEqual(['a', 'b', 'c']);
198166
});
199167

200168
it('should extract lookup', () => {
201169
component.lookups = { test: [{ t: 'a' }, { t: 'b' }, { t: 'c' }] };;
202-
component.formConfig = { fields: [{ type: 'text', name: 'title', lookup: 'test', extract: 't' }], form: [] };
170+
component.formConfig = { form: [{ fields: [{ type: 'text', name: 'title', lookup: 'test', extract: 't' }] }] };
203171
component.ngOnInit();
204-
expect(component.formConfig.fields[0].options).toEqual(['a', 'b', 'c']);
172+
expect(component.formConfig.form[0].fields[0].options).toEqual(['a', 'b', 'c']);
205173
});
206174

207-
208-
});
209-
});
210-
211-
describe('createControl()', () => {
212-
let cfg = { name: 'test', type: 'text', disabled: true, required: true, minLength: 5, maxLength: 10, email: true, min: 1, max: 10, pattern: new RegExp('\d'), nullValidator: true, value: 5 };
213-
214-
it('should set pattern validator', () => {
215-
let control = component.createControl({ name: 'test', type: 'text', pattern: new RegExp('\d'), value: 5 });
216-
const vals = control.validator(control);
217-
expect(vals.pattern).toBeTruthy();
218-
});
219-
220-
it('should set email validator', () => {
221-
let control = component.createControl({ name: 'test', type: 'text', email: true, value: 5 });
222-
const vals = control.validator(control);
223-
expect(vals.email).toBeTruthy();
224-
});
225-
226-
it('should set min length validator', () => {
227-
let control = component.createControl({ name: 'test', type: 'text', minLength: 5, maxLength: 10, value: 'test' });
228-
const vals = control.validator(control);
229-
expect(vals.minlength).toBeTruthy();
230-
});
231-
232-
it('should set max length validator', () => {
233-
let control = component.createControl({ name: 'test', type: 'text', maxLength: 2, value: 'test' });
234-
const vals = control.validator(control);
235-
expect(vals.maxlength).toBeTruthy();
236-
});
237-
238-
it('should set required validator', () => {
239-
let control = component.createControl({ name: 'test', type: 'text', required: true, value: '' });
240-
const vals = control.validator(control);
241-
expect(vals.required).toBeTruthy();
242-
});
243-
244-
it('should set min value validator', () => {
245-
let control = component.createControl({ name: 'test', type: 'text', min: 2, value: 1 });
246-
const vals = control.validator(control);
247-
expect(vals.min).toBeTruthy();
248-
});
249-
250-
it('should set max value validator', () => {
251-
let control = component.createControl({ name: 'test', type: 'text', max: 2, value: 22 });
252-
const vals = control.validator(control);
253-
expect(vals.max).toBeTruthy();
254-
});
255-
256-
it('should set value', () => {
257-
let control = component.createControl(cfg);
258-
expect(control.value).toEqual(cfg.value);
259175
});
260176

261177
});
178+
262179
});

0 commit comments

Comments
 (0)