Skip to content

Commit b42eb13

Browse files
author
Maxime Lafarie
authored
Merge pull request #1 from maximelafarie/feature/add-iterable-field-option
Start implementing iterable fields
2 parents 156d34a + 2d5f07d commit b42eb13

15 files changed

+224
-106
lines changed
Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,60 @@
11
<div [formGroup]="form">
2-
<label [attr.for]="question.key">{{ question.label }}</label>
3-
4-
<div [ngSwitch]="question.controlType">
5-
6-
<input *ngSwitchCase="'textbox'"
7-
[class]="isValid ? config.validClass : config.invalidClass"
8-
[formControlName]="question.key"
9-
[placeholder]="question.placeholder"
10-
[id]="question.key"
11-
[type]="question['type']">
12-
13-
<select [id]="question.key"
14-
*ngSwitchCase="'dropdown'"
15-
[class]="isValid ? config.validClass : config.invalidClass"
16-
[formControlName]="question.key">
17-
<option value=""
18-
disabled
19-
*ngIf="!!question.placeholder"
20-
selected>{{ question.placeholder }}</option>
21-
<option *ngFor="let opt of question['options']"
22-
[value]="opt.key">{{ opt.value }}</option>
23-
</select>
24-
25-
<textarea *ngSwitchCase="'textarea'"
26-
[formControlName]="question.key"
27-
[id]="question.key"
28-
[class]="isValid ? config.validClass : config.invalidClass"
29-
[cols]="question['cols']"
30-
[rows]="question['rows']"
31-
[maxlength]="question['maxlength']"
32-
[minlength]="question['minlength']"
33-
[placeholder]="question.placeholder"></textarea>
34-
35-
<button *ngIf="question.iterable"
36-
type="button"
37-
(click)="addItem(question)">+</button>
2+
3+
<ng-template #formTmpl
4+
let-index="index">
5+
<label [attr.for]="questionId(index)">{{ questionLabel(index) }}</label>
6+
7+
<div [ngSwitch]="question.controlType">
8+
9+
<input *ngSwitchCase="'textbox'"
10+
[formControl]="questionControl(index)"
11+
[placeholder]="question.placeholder"
12+
[attr.min]="question['min']"
13+
[attr.max]="question['max']"
14+
[attr.pattern]="question['pattern']"
15+
[id]="questionId(index)"
16+
[type]="question['type']">
17+
18+
<select [id]="question.key"
19+
*ngSwitchCase="'dropdown'"
20+
[formControl]="questionControl(index)">
21+
<option value=""
22+
disabled
23+
*ngIf="!!question.placeholder"
24+
selected>{{ question.placeholder }}</option>
25+
<option *ngFor="let opt of question['options']"
26+
[value]="opt.key">{{ opt.value }}</option>
27+
</select>
28+
29+
<textarea *ngSwitchCase="'textarea'"
30+
[formControl]="questionControl(index)"
31+
[id]="question.key"
32+
[cols]="question['cols']"
33+
[rows]="question['rows']"
34+
[maxlength]="question['maxlength']"
35+
[minlength]="question['minlength']"
36+
[placeholder]="question.placeholder"></textarea>
37+
</div>
38+
39+
<div class="errorMessage"
40+
*ngIf="!isValid">{{ question.label }} is required</div>
41+
42+
</ng-template>
43+
44+
<div *ngIf="question.iterable; else formTmpl">
45+
<div *ngFor="let field of questionArray.controls; let i=index; first as isFirst last as isLast">
46+
<ng-container [ngTemplateOutlet]="formTmpl"
47+
[ngTemplateOutletContext]="{index: i}"></ng-container>
48+
49+
<button *ngIf="question.iterable && questionArray.controls.length > 1"
50+
(click)="removeQuestion(i)"
51+
type="button">-</button>
52+
53+
<button *ngIf="question.iterable && isLast"
54+
(click)="addQuestion()"
55+
type="button">+</button>
56+
57+
</div>
3858
</div>
3959

40-
<div class="errorMessage"
41-
*ngIf="!isValid">{{ question.label }} is required</div>
4260
</div>
Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Component, OnInit, Input } from '@angular/core';
2-
import { FormGroup, FormArray, FormControl } from '@angular/forms';
2+
import { FormGroup, FormArray, FormBuilder, AbstractControl } from '@angular/forms';
33

4-
import { QuestionBase, QuestionConfig } from '@app/models';
4+
import { QuestionBase } from '@app/models';
55

66
@Component({
77
selector: 'app-question',
@@ -12,22 +12,37 @@ export class DynamicFormQuestionComponent implements OnInit {
1212

1313
@Input() question: QuestionBase<any>;
1414
@Input() form: FormGroup;
15-
@Input() config: QuestionConfig;
1615
get isValid() { return this.form.controls[this.question.key].valid; }
1716

18-
constructor() {
17+
constructor(private fb: FormBuilder) { }
18+
19+
ngOnInit() { }
20+
21+
private asFormArray(ctrl: AbstractControl): FormArray {
22+
return ctrl as FormArray;
23+
}
24+
25+
public addQuestion(): void {
26+
this.questionArray.push(this.fb.control(''));
1927
}
2028

21-
ngOnInit() {
29+
public removeQuestion(index: number): void {
30+
this.questionArray.removeAt(index);
31+
}
32+
33+
public get questionArray(): FormArray {
34+
return this.form.get(this.question.key) as FormArray;
35+
}
2236

23-
if (this.question.iterable) {
24-
console.log(this.question);
25-
}
37+
public questionControl(index?: number): AbstractControl {
38+
return this.question.iterable ? this.asFormArray(this.form.get(this.question.key)).controls[index] : this.form.get(this.question.key);
2639
}
2740

28-
public addItem(question: QuestionBase<any>): void {
29-
(<FormArray> this.form.get(question.key)).push(new FormControl(''));
30-
console.log(question);
41+
public questionId(index?: number): string {
42+
return this.question.iterable ? `${this.question.key}-${index}` : this.question.key;
3143
}
3244

45+
public questionLabel(index?: number): string {
46+
return this.question.iterable ? `${this.question.label}${index + 1}` : this.question.label;
47+
}
3348
}

src/app/_components/common/dynamic-form/dynamic-form.component.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
<div *ngFor="let question of questions"
66
class="form-row">
77
<app-question [question]="question"
8-
[config]="config"
98
[form]="form"></app-question>
109
</div>
1110

src/app/_components/common/dynamic-form/dynamic-form.component.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { Component, OnInit, Input, Inject } from '@angular/core';
1+
import { Component, OnInit, Input } from '@angular/core';
22
import { FormGroup } from '@angular/forms';
33

4-
import { QuestionBase, QuestionConfig } from '@app/models';
4+
import { QuestionBase } from '@app/models';
55
import { QuestionControlService } from '@app/services';
66

77
@Component({
@@ -15,10 +15,7 @@ export class DynamicFormComponent implements OnInit {
1515
form: FormGroup;
1616
payLoad = '';
1717

18-
constructor(
19-
@Inject('questionConfig') public config: QuestionConfig,
20-
private qcs: QuestionControlService
21-
) { }
18+
constructor(private qcs: QuestionControlService) { }
2219

2320
ngOnInit() {
2421
this.form = this.qcs.toFormGroup(this.questions);
Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { NgModule, ModuleWithProviders } from '@angular/core';
1+
import { NgModule } from '@angular/core';
22
import { CommonModule } from '@angular/common';
33
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
44

55
import { DynamicFormComponent } from './dynamic-form.component';
66
import { DynamicFormQuestionModule } from '../dynamic-form-question/dynamic-form-question.module';
77

8-
import { QuestionConfig } from '@app/models';
98
import { QuestionControlService } from '@app/services';
109

1110
@NgModule({
@@ -20,29 +19,4 @@ import { QuestionControlService } from '@app/services';
2019
exports: [DynamicFormComponent]
2120
})
2221
export class DynamicFormModule {
23-
24-
/**
25-
* Use in AppModule: new instance of NgxSmartModal.
26-
*/
27-
public static forRoot(config?: QuestionConfig): ModuleWithProviders {
28-
if (!config) {
29-
config = { validClass: 'valid', invalidClass: 'invalid' };
30-
}
31-
32-
return {
33-
ngModule: DynamicFormModule,
34-
providers: [QuestionControlService, { provide: 'questionConfig', useValue: config }]
35-
};
36-
}
37-
38-
/**
39-
* Use in features modules with lazy loading: new instance of NgxSmartModal.
40-
*/
41-
public static forChild(): ModuleWithProviders {
42-
return {
43-
ngModule: DynamicFormModule,
44-
providers: [QuestionControlService]
45-
};
46-
}
47-
4822
}

src/app/_models/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
export * from './question-config';
21
export * from './question-base';
32
export * from './question-dropdown';
43
export * from './question-textbox';

src/app/_models/question-config.ts

Lines changed: 0 additions & 4 deletions
This file was deleted.

src/app/_models/question-textbox.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@ import { QuestionBase } from './question-base';
33
export class TextboxQuestion extends QuestionBase<string> {
44
controlType = 'textbox';
55
type: string;
6+
min: number | string;
7+
max: number | string;
8+
pattern: string;
69

710
constructor(options: {} = {}) {
811
super(options);
912
this.type = options['type'] || 'text';
13+
this.min = options['min'];
14+
this.max = options['max'];
15+
this.pattern = options['pattern'];
1016
}
1117
}

src/app/_services/question-control.service.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ import { FormGroup, FormControl, Validators, FormArray } from '@angular/forms';
33

44
import { QuestionBase } from '@app/models';
55

6-
@Injectable({
7-
providedIn: 'root'
8-
})
6+
@Injectable()
97
export class QuestionControlService {
108

119
constructor() { }
@@ -17,8 +15,21 @@ export class QuestionControlService {
1715

1816
if (question.iterable) {
1917

20-
group[question.key] = question.required ? new FormArray([new FormControl(question.value || '')
21-
]) : new FormArray([new FormControl(question.value || '')], Validators.required);
18+
if (!Array.isArray(question.value)) {
19+
question.value = !!question.value ? [question.value] : [''];
20+
}
21+
22+
const tmpArray: FormArray = question.required ? new FormArray([]) : new FormArray([], Validators.required);
23+
24+
if (!question.value || !question.value.length) {
25+
tmpArray.push(new FormControl(''));
26+
} else {
27+
question.value.forEach(val => {
28+
tmpArray.push(new FormControl(val));
29+
});
30+
}
31+
32+
group[question.key] = tmpArray;
2233

2334
} else {
2435

src/app/_services/question.service.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ import {
77
TextareaQuestion
88
} from '@app/models';
99

10-
@Injectable({
11-
providedIn: 'root'
12-
})
10+
@Injectable()
1311
export class QuestionService {
1412

1513
// TODO: get from a remote source of question metadata
@@ -42,11 +40,21 @@ export class QuestionService {
4240
new TextboxQuestion({
4341
key: 'jobs',
4442
label: 'Jobs',
45-
value: '',
43+
value: ['toto'],
4644
iterable: true,
4745
order: 5
4846
}),
4947

48+
new TextboxQuestion({
49+
key: 'level',
50+
label: 'Level',
51+
type: 'range',
52+
value: 70,
53+
min: 20,
54+
max: 200,
55+
order: 6
56+
}),
57+
5058
new TextboxQuestion({
5159
key: 'emailAddress',
5260
label: 'Email',

0 commit comments

Comments
 (0)