如何提交具有输入类型的响应式表单作为包含该文件数据的文件?
How to submit reactive form with input type as a File with data for that file?
我需要将 CSV 文件的内容保存在我的响应式表单中作为表单控件的输入值。目前默认只选择文件名,我需要为该表单控件保存文件数据,而不仅仅是文件名。
我尝试了这里提到的一种方法:
它说用文件数据修补表单控件值。当我尝试采用这种方法时,出现以下错误:
ERROR DOMException: Failed to set the 'value' property on
'HTMLInputElement': This input element accepts a filename, which may
only be programmatically set to the empty string.
at EmulatedEncapsulationDomRenderer2.push../node_modules/@angular/platform-browser/fesm5/platform-browser.js.DefaultDomRenderer2.setProperty
(http://localhost:4200/vendor.js:134583:18)
at BaseAnimationRenderer.push../node_modules/@angular/platform-browser/fesm5/animations.js.BaseAnimationRenderer.setProperty
(http://localhost:4200/vendor.js:133181:27)
at DebugRenderer2.push../node_modules/@angular/core/fesm5/core.js.DebugRenderer2.setProperty
(http://localhost:4200/vendor.js:85257:23)
at DefaultValueAccessor.push../node_modules/@angular/forms/fesm5/forms.js.DefaultValueAccessor.writeValue (http://localhost:4200/vendor.js:86345:24)
at http://localhost:4200/vendor.js:87606:27
at http://localhost:4200/vendor.js:88761:65
at Array.forEach ()
at FormControl.push../node_modules/@angular/forms/fesm5/forms.js.FormControl.setValue
(http://localhost:4200/vendor.js:88761:28)
at FormControl.push../node_modules/@angular/forms/fesm5/forms.js.FormControl.patchValue
(http://localhost:4200/vendor.js:88776:14)
at http://localhost:4200/vendor.js:89118:38
onFileChange(event, formCotrolKey: string) {
if (event.target.files && event.target.files.length) {
const [file] = event.target.files;
this.formGroup.patchValue({
[formCotrolKey]: file
});
// need to run CD since file load runs outside of zone
this.changeDetectorRef.markForCheck();
}
}
此实现完全符合我使用 ControlValueAccessor
的需要。为此,您只需创建一个实现 ControlValueAccessor
接口的指令。
使用下面的代码:
import { ControlValueAccessor } from '@angular/forms';
import { Directive } from '@angular/core';
import { ElementRef } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ChangeDetectorRef } from '@angular/core';
let noop = () => {
};
@Directive({
selector: 'input[type=file][observeFiles]',
host: {
'(blur)': 'onTouchedCallback()',
'(change)': 'handleChange( $event.target.files )'
},
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: FileInputValueAccessorDirective,
multi: true
}
]
})
export class FileInputValueAccessorDirective implements ControlValueAccessor {
private elementRef: ElementRef;
private onChangeCallback: Function;
private onTouchedCallback: Function;
// I initialize the file-input value accessor service.
constructor(elementRef: ElementRef,
private changeDetectorRef: ChangeDetectorRef) {
this.elementRef = elementRef;
this.onChangeCallback = noop;
this.onTouchedCallback = noop;
}
public handleChange(files: FileList): void {
if (this.elementRef.nativeElement.multiple) {
this.onChangeCallback(Array.from(files));
} else {
const reader = new FileReader();
reader.readAsDataURL(files[0]);
reader.onload = () => {
this.onChangeCallback(files.length ? reader.result.toString().split(',')[1] : null);
this.changeDetectorRef.markForCheck();
};
}
}
public registerOnChange(callback: Function): void {
this.onChangeCallback = callback;
}
public registerOnTouched(callback: Function): void {
this.onTouchedCallback = callback;
}
// I set the disabled property of the file input element.
public setDisabledState(isDisabled: boolean): void {
this.elementRef.nativeElement.disabled = isDisabled;
}
public writeValue(value: any): void {
if (value instanceof FileList) {
this.elementRef.nativeElement.files = value;
} else if (Array.isArray(value) && !value.length) {
this.elementRef.nativeElement.files = null;
} else if (value === null) {
this.elementRef.nativeElement.files = null;
} else {
if (console && console.warn && console.log) {
console.log('Ignoring attempt to assign non-FileList to input[type=file].');
console.log('Value:', value);
}
}
}
}
现在将此指令包含在声明数组下的模块文件中:
// Your Directive location
import { FileInputValueAccessorDirective } from 'app/forms/accessors/file-input.accessor';
@NgModule({
...
declarations: [
...
FileInputValueAccessorDirective
]
})
最后在您的组件模板中使用:
<input observeFiles [(ngModel)]="fileContent" type="file" />
确保您的组件中有变量 fileContent
来保存数据。这就是所有需要的。数据将以 base 64 格式保存在变量 fileContent
.
中
如果不需要 base 64 编码,您可以在指令中替换以下行:
this.onChangeCallback(files.length ? reader.result.toString().split(',')[1] : null);` inside `reader.onload
此行的方法:
this.onChangeCallback(files.length ? atob( reader.result.toString().split(',')[1] ) : null);
我需要将 CSV 文件的内容保存在我的响应式表单中作为表单控件的输入值。目前默认只选择文件名,我需要为该表单控件保存文件数据,而不仅仅是文件名。
我尝试了这里提到的一种方法:
它说用文件数据修补表单控件值。当我尝试采用这种方法时,出现以下错误:
ERROR DOMException: Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string. at EmulatedEncapsulationDomRenderer2.push../node_modules/@angular/platform-browser/fesm5/platform-browser.js.DefaultDomRenderer2.setProperty (http://localhost:4200/vendor.js:134583:18) at BaseAnimationRenderer.push../node_modules/@angular/platform-browser/fesm5/animations.js.BaseAnimationRenderer.setProperty (http://localhost:4200/vendor.js:133181:27) at DebugRenderer2.push../node_modules/@angular/core/fesm5/core.js.DebugRenderer2.setProperty (http://localhost:4200/vendor.js:85257:23) at DefaultValueAccessor.push../node_modules/@angular/forms/fesm5/forms.js.DefaultValueAccessor.writeValue (http://localhost:4200/vendor.js:86345:24) at http://localhost:4200/vendor.js:87606:27 at http://localhost:4200/vendor.js:88761:65 at Array.forEach () at FormControl.push../node_modules/@angular/forms/fesm5/forms.js.FormControl.setValue (http://localhost:4200/vendor.js:88761:28) at FormControl.push../node_modules/@angular/forms/fesm5/forms.js.FormControl.patchValue (http://localhost:4200/vendor.js:88776:14) at http://localhost:4200/vendor.js:89118:38
onFileChange(event, formCotrolKey: string) {
if (event.target.files && event.target.files.length) {
const [file] = event.target.files;
this.formGroup.patchValue({
[formCotrolKey]: file
});
// need to run CD since file load runs outside of zone
this.changeDetectorRef.markForCheck();
}
}
此实现完全符合我使用 ControlValueAccessor
的需要。为此,您只需创建一个实现 ControlValueAccessor
接口的指令。
使用下面的代码:
import { ControlValueAccessor } from '@angular/forms';
import { Directive } from '@angular/core';
import { ElementRef } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ChangeDetectorRef } from '@angular/core';
let noop = () => {
};
@Directive({
selector: 'input[type=file][observeFiles]',
host: {
'(blur)': 'onTouchedCallback()',
'(change)': 'handleChange( $event.target.files )'
},
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: FileInputValueAccessorDirective,
multi: true
}
]
})
export class FileInputValueAccessorDirective implements ControlValueAccessor {
private elementRef: ElementRef;
private onChangeCallback: Function;
private onTouchedCallback: Function;
// I initialize the file-input value accessor service.
constructor(elementRef: ElementRef,
private changeDetectorRef: ChangeDetectorRef) {
this.elementRef = elementRef;
this.onChangeCallback = noop;
this.onTouchedCallback = noop;
}
public handleChange(files: FileList): void {
if (this.elementRef.nativeElement.multiple) {
this.onChangeCallback(Array.from(files));
} else {
const reader = new FileReader();
reader.readAsDataURL(files[0]);
reader.onload = () => {
this.onChangeCallback(files.length ? reader.result.toString().split(',')[1] : null);
this.changeDetectorRef.markForCheck();
};
}
}
public registerOnChange(callback: Function): void {
this.onChangeCallback = callback;
}
public registerOnTouched(callback: Function): void {
this.onTouchedCallback = callback;
}
// I set the disabled property of the file input element.
public setDisabledState(isDisabled: boolean): void {
this.elementRef.nativeElement.disabled = isDisabled;
}
public writeValue(value: any): void {
if (value instanceof FileList) {
this.elementRef.nativeElement.files = value;
} else if (Array.isArray(value) && !value.length) {
this.elementRef.nativeElement.files = null;
} else if (value === null) {
this.elementRef.nativeElement.files = null;
} else {
if (console && console.warn && console.log) {
console.log('Ignoring attempt to assign non-FileList to input[type=file].');
console.log('Value:', value);
}
}
}
}
现在将此指令包含在声明数组下的模块文件中:
// Your Directive location
import { FileInputValueAccessorDirective } from 'app/forms/accessors/file-input.accessor';
@NgModule({
...
declarations: [
...
FileInputValueAccessorDirective
]
})
最后在您的组件模板中使用:
<input observeFiles [(ngModel)]="fileContent" type="file" />
确保您的组件中有变量 fileContent
来保存数据。这就是所有需要的。数据将以 base 64 格式保存在变量 fileContent
.
如果不需要 base 64 编码,您可以在指令中替换以下行:
this.onChangeCallback(files.length ? reader.result.toString().split(',')[1] : null);` inside `reader.onload
此行的方法:
this.onChangeCallback(files.length ? atob( reader.result.toString().split(',')[1] ) : null);