问题
The current implementation for multi-select doesn't show <mat-select [formControl]="toppings" multiple>
because here for type 'array' and 'enum' it shows 'checkboxes'.
So, I have overridden that behavior in the following way:
myCustomWidgets = {
submit: NoneComponent,
checkboxes: CustomMultiSelectComponent
};
I have created a MaterialSelectComponent file which is a copy of the same file from 'angular6-json-schema-form' and then added the custom widget like below.
<json-schema-form loadExternalAssets="true"
[schema]="formData?.schema"
[form]="formData?.form"
framework="material-design"
[widgets]="myCustomWidgets"
(isValid)="isFormValid($event)"
(onChanges)="onFormChange($event)"
(onSubmit)="onFormSubmit($event)">
</json-schema-form>
I have 4 elements, one text, one date, one single select, and one multi-select like below.
{
"form": [{
"type": "section",
"htmlClass": "row",
"items": [{
"type": "section",
"htmlClass": "col-xs-6 item-padding",
"items": ["my_text"]
}, {
"type": "section",
"htmlClass": "col-xs-6 item-padding",
"items": ["my_date"]
}
]
}, {
"type": "section",
"htmlClass": "row",
"items": [{
"type": "section",
"htmlClass": "col-xs-6 item-padding",
"items": ["my_multi_select"]
}, {
"type": "section",
"htmlClass": "col-xs-6 item-padding",
"items": ["my_single_select"]
}
]
}
],
"schema": {
"schema": "http://json-schema.org/draft-06/schema#",
"type": "object",
"title": "Form Details",
"description": "",
"properties": {
"my_multi_select": {
"titleSource": "my_multi_select",
"fieldDisplay": "Select More",
"title": "Select More",
"type": "array",
"pattern": null,
"description": "Multi Select",
"format": "",
"required": false,
"multiple": true,
"uniqueItems": true,
"items": {
"type": "string",
"enum": ["A", "B", "C", "D"]
},
"readonly": false
},
"my_text": {
"titleSource": "my_text",
"fieldDisplay": "My Text",
"title": "My Text",
"type": "string",
"pattern": "",
"description": "Enter Text",
"format": "",
"required": true,
"readonly": false
},
"my_date": {
"titleSource": "my_date",
"fieldDisplay": "My Date",
"title": "My Date",
"type": "string",
"pattern": "",
"description": "Enter Date",
"format": "date",
"required": true,
"readonly": false
},
"my_single_select": {
"titleSource": "my_single_select",
"fieldDisplay": "My Single Select",
"title": "My Single Select",
"type": "string",
"pattern": "",
"description": "Enter Date",
"format": "date",
"required": true,
"readonly": false,
"enum": [
"One",
"Two",
"Three",
"Four"
]
}
},
"required": ["my_text", "my_date", "my_single_select"]
},
"data": {
"my_text": "",
"my_date": "",
"my_single_select": "",
"my_multi_select" : []
}
}
Now the issue is it's not capturing the data change event in method form-group.functions.ts file only for that "my_multi_select" element. For the rest of the 3 elements any change is getting a callback and the values are getting captured. I have debugged here below json-schema.form.services.ts where all the controls are getting registered for subscription. In my 4 elements, multi-select is of type "FormArray" and rest are "FormControl".
buildFormGroup() {
this.formGroup = <FormGroup>buildFormGroup(this.formGroupTemplate);
if (this.formGroup) {
this.compileAjvSchema();
this.validateData(this.formGroup.value);
// Set up observables to emit data and validation info when form data changes
if (this.formValueSubscription) { this.formValueSubscription.unsubscribe(); }
this.formValueSubscription = this.formGroup.valueChanges
.subscribe(formValue => this.validateData(formValue));
}
}
Is there a known bug with FormArray type subscription or event emitter?
I have also tried to use ViewChild to get the values, but I still only get the values of others except that Multi-Select. I still don't understand that in the UI when I select multiple values it still shows there, which means it's stored somewhere (may be in controlValue) but why there is no way to access that value (without onchange event)?
<json-schema-form #myJsonSchema
loadExternalAssets="true"
[schema]="formData?.schema"
[form]="formData?.form"
framework="material-design"
[widgets]="myCustomWidgets"
(isValid)="isFormValid($event)"
(onChanges)="onFormChange($event)"
(onSubmit)="onFormSubmit($event)">
</json-schema-form>
回答1:
One guy from my team fixed this issue. Please see below the component code. Basically the Checkboxes code was used here to emit the values. selectionChange() method was used for this as shown in the code. We had faced another issue with the data binding as well which we had fixed it adding value
import {AbstractControl} from '@angular/forms';
import {buildTitleMap, isArray, JsonSchemaFormService} from 'angular6-json-schema-form';
import {Component, Inject, Input, OnInit, Optional} from '@angular/core';
import {MAT_LABEL_GLOBAL_OPTIONS} from '@angular/material/core';
import {MAT_FORM_FIELD_DEFAULT_OPTIONS} from '@angular/material/form-field';
@Component({
// tslint:disable-next-line:component-selector
selector: 'material-select-widget',
template: `
<mat-form-field
[appearance]="options?.appearance || matFormFieldDefaultOptions?.appearance || 'standard'"
[class]="options?.htmlClass || ''"
[floatLabel]="options?.floatLabel || matLabelGlobalOptions?.float || (options?.notitle ? 'never' : 'auto')"
[hideRequiredMarker]="options?.hideRequired ? 'true' : 'false'"
[style.width]="'100%'">
<mat-label *ngIf="!options?.notitle">{{options?.title}}</mat-label>
<span matPrefix *ngIf="options?.prefix || options?.fieldAddonLeft"
[innerHTML]="options?.prefix || options?.fieldAddonLeft"></span>
<mat-select *ngIf="boundControl"
[attr.aria-describedby]="'control' + layoutNode?._id + 'Status'"
[attr.name]="controlName"
[id]="'control' + layoutNode?._id"
[multiple]="options?.multiple"
[placeholder]="options?.notitle ? options?.placeholder : options?.title"
[required]="options?.required"
[style.width]="'100%'"
[(value)]="selectedValues"
(selectionChange)="selectionChange($event)"
(blur)="options.showErrors = true">
<ng-template ngFor let-selectItem [ngForOf]="selectList">
<mat-option *ngIf="!isArray(selectItem?.items)"
[value]="selectItem?.value">
<span [innerHTML]="selectItem?.name"></span>
</mat-option>
<mat-optgroup *ngIf="isArray(selectItem?.items)"
[label]="selectItem?.group">
<mat-option *ngFor="let subItem of selectItem.items"
[value]="subItem?.value">
<span [innerHTML]="subItem?.name"></span>
</mat-option>
</mat-optgroup>
</ng-template>
</mat-select>
<mat-select *ngIf="!boundControl"
[attr.aria-describedby]="'control' + layoutNode?._id + 'Status'"
[attr.name]="controlName"
[disabled]="controlDisabled || options?.readonly"
[id]="'control' + layoutNode?._id"
[multiple]="options?.multiple"
[placeholder]="options?.notitle ? options?.placeholder : options?.title"
[required]="options?.required"
[style.width]="'100%'"
[value]="controlValue"
(blur)="options.showErrors = true"
(selectionChange)="selectionChange($event)">
<ng-template ngFor let-selectItem [ngForOf]="selectList">
<mat-option *ngIf="!isArray(selectItem?.items)"
[attr.selected]="selectItem?.value === controlValue"
[value]="selectItem?.value">
<span [innerHTML]="selectItem?.name"></span>
</mat-option>
<mat-optgroup *ngIf="isArray(selectItem?.items)"
[label]="selectItem?.group">
<mat-option *ngFor="let subItem of selectItem.items"
[attr.selected]="subItem?.value === controlValue"
[value]="subItem?.value">
<span [innerHTML]="subItem?.name"></span>
</mat-option>
</mat-optgroup>
</ng-template>
</mat-select>
<span matSuffix *ngIf="options?.suffix || options?.fieldAddonRight"
[innerHTML]="options?.suffix || options?.fieldAddonRight"></span>
<mat-hint *ngIf="options?.description && (!options?.showErrors || !options?.errorMessage)"
align="end" [innerHTML]="options?.description"></mat-hint>
</mat-form-field>
<mat-error *ngIf="options?.showErrors && options?.errorMessage"
[innerHTML]="options?.errorMessage"></mat-error>`,
styles: [`
mat-error {
font-size: 75%;
margin-top: -1rem;
margin-bottom: 0.5rem;
}
::ng-deep json-schema-form mat-form-field .mat-form-field-wrapper .mat-form-field-flex
.mat-form-field-infix {
width: initial;
}
`],
})
export class CustomMultiSelectComponent implements OnInit {
formControl: AbstractControl;
controlName: string;
controlValue: any;
controlDisabled = false;
boundControl = false;
options: any;
selectList: any[] = [];
isArray = isArray;
@Input() layoutNode: any;
@Input() layoutIndex: number[];
@Input() dataIndex: number[];
selectedValues = []
constructor(
@Inject(MAT_FORM_FIELD_DEFAULT_OPTIONS) @Optional() public matFormFieldDefaultOptions,
@Inject(MAT_LABEL_GLOBAL_OPTIONS) @Optional() public matLabelGlobalOptions,
private jsf: JsonSchemaFormService
) {
}
ngOnInit() {
this.options = this.layoutNode.options || {};
this.selectList = buildTitleMap(
this.options.titleMap || this.options.enumNames,
this.options.enum, !!this.options.required, !!this.options.flatList
);
this.jsf.initializeControl(this, !this.options.readonly);
if (!this.options.notitle && !this.options.description && this.options.placeholder) {
this.options.description = this.options.placeholder;
}
if (this.boundControl) {
const formArray = this.jsf.getFormControl(this);
for (const selectItem of this.selectList) {
selectItem.checked = formArray.value.includes(selectItem.value);
}
}
this.selectedValues = this.selectList.filter(item => item.checked)
.map(item => item.value);
}
selectionChange(event) {
this.options.showErrors = true;
this.selectList.map(item => item.checked = this.selectedValues.includes(item.value));
if (this.boundControl) {
this.jsf.updateArrayCheckboxList(this, this.selectList);
}
}
}
I hope this helps you guys. Anyone can use this code. I am going to add a PR to the angular6-json-schema-form project.
来源:https://stackoverflow.com/questions/58036755/angular6-json-schema-form-issue-with-multi-select-form-change-event