多个 file-input 和 Angular 动态表单
Multiple file-input with Angular Dynamic Form
TLDR:如何将 FormArray 或 FormGroup 与多个 file-input 字段一起使用?
大家好,
我想用 Angular 构建一个事件管理器。每个活动都有标题、描述、图像和传单。图片是 png 或 jpg file-input,传单是 pdf 格式。
我需要一个执行以下操作的工具栏:
- add 应该在没有图像或 pdf 的情况下将新事件附加到列表中
- 删除应该删除所有选定的事件
- 保存应该将事件更新到数据库并加载两个 file-inputs 文件。
事件列表如下:
但我很难实施表格。
如何将多个 file-input 与 angular 一起使用到动态表单中?
这是我尝试做的(事件 = 促销):
HTML:
<form [formGroup]="promotionForm" *ngFor="let promotion of promotions; let i = index;">
<div class="card" *ngIf="promotion.displayed" (click)="promotion.selected = !promotion.selected;">
<div>
<h2>Title</h2>
<input matInput [(ngModel)]="promotion.title" [ngModelOptions]="{standalone: true}"/>
</div>
<div>
<h2>Subtitle</h2>
<input matInput [(ngModel)]="promotion.subtitle" [ngModelOptions]="{standalone: true}"/>
</div>
<div>
<h2>Language</h2>
<mat-select [(value)]="promotion.language"></mat-select>
</div>
<div class="description">
<h2>Description</h2>
<textarea matInput cdkAutosizeMinRows="5" [(ngModel)]="promotion.description"></textarea>
</div>
<div class="img">
<div class="container">
<h2>Image</h2>
<mat-form-field>
<ngx-mat-file-input formControlName="image" [multiple]="false" accept="image/webp, image/jpeg, image/png">
<mat-icon ngxMatFileInputIcon>folder</mat-icon>
</ngx-mat-file-input>
</mat-form-field>
</div>
<img src="{{promotion.image}}">
</div>
<div class="pdf">
<h2>PDF</h2>
<mat-form-field>
<ngx-mat-file-input formControlName="pdf" [multiple]="false" accept="application/pdf">
<mat-icon ngxMatFileInputIcon>folder</mat-icon>
</ngx-mat-file-input>
</mat-form-field>
</div>
<mat-checkbox class="checkbox" [checked]="promotion.selected"></mat-checkbox>
</div>
</form>
TS:
promotions: Promotion[] = [];
maxSize: number = 2; //Mo
promotionForm: FormGroup;
images: any[];
pdfs: any[];
constructor(private promotionService: PromotionService, private fb: FormBuilder) { }
ngOnInit(): void {
this.promotionForm = this.fb.group({
image: [MaxSizeValidator(this.maxSize * 1024 * 1024)],
pdf: [MaxSizeValidator(this.maxSize * 1024 * 1024)]
})
this.getAllPromotions();
}
getAllPromotions() {
this.promotionService.getAllPromotions()
.subscribe(promotions => {
this.promotions = promotions.reverse();
this.promotions.map(p => {
p.selected = false;
p.displayed = true;
})
})
}
deletePromotions() {
let toDelete$ = this.promotions.filter(p => p.selected).map(p => { return this.promotionService.deletePromotion(p._id) });
forkJoin(toDelete$).subscribe(() => this.getAllPromotions());
}
updatePromotions() {
let toUpdate$ = this.promotions.filter(p => p.selected).map(p => { return this.promotionService.updatePromotion(p) });
forkJoin(toUpdate$).subscribe(() => this.getAllPromotions());
}
selectAll() {
if (this.promotions.filter(p => p.displayed).every(p => p.selected)) {
this.promotions.map(p => p.selected = false)
} else {
this.promotions.filter(p => p.displayed).map(p => p.selected = true)
}
}
此解决方案的问题是为每个事件获取一个唯一的文件。我希望每个事件都有一个唯一的 file-input,如果我使用 FormGroup,我无法将 n file-input 关联到每个事件。我应该使用 FormGroup 的 FormArrays 并在每个组中使用图像和 pdf 吗?
我就是这样实现的。
而不是使用 formArray。我为每个 objects 添加了一个自定义控件。这是我的 html
<ng-container [formGroup]="promotion.filesForm">
<div class="img">
<div class="container">
<h2>Image</h2>
<mat-form-field>
<ngx-mat-file-input formControlName="image" [multiple]="false" accept="image/webp, image/jpeg, image/png" (change)="promotion.image=''">
<mat-icon ngxMatFileInputIcon>folder</mat-icon>
</ngx-mat-file-input>
</mat-form-field>
</div>
<img src="{{promotion.image}}">
</div>
<div class="pdf">
<h2>PDF</h2>
<mat-form-field>
<ngx-mat-file-input formControlName="pdf" [multiple]="false" accept="application/pdf">
<mat-icon ngxMatFileInputIcon>folder</mat-icon>
</ngx-mat-file-input>
</mat-form-field>
<a href="{{promotion.pdf}}">Flyer</a>
</div>
</ng-container>
还有我的老师
addPromotionFileForm() {
const fileForm = new FormGroup({
image: new FormControl('', [MaxSizeValidator(this.maxSize * 1024 * 1024)]),
pdf: new FormControl('', [MaxSizeValidator(this.maxSize * 1024 * 1024)])
});
return fileForm as FormGroup;
}
getAllPromotions() {
this.promotionService.getAllPromotions()
.subscribe(promotions => {
this.promotions = promotions.reverse();
this.promotions.map(p => {
p.selected = false;
p.displayed = true;
p.filesForm = this.addPromotionFileForm();
})
})
}
当我想上传它们时,我对我的事件数组进行了深度复制,并将文件放入图像和 pdf 属性中。
updatePromotions() {
let payload = cloneDeep(this.promotions);
payload.map(p => {
p.image = p.filesForm.value.image;
p.pdf = p.filesForm.value.pdf;
delete p.filesForm;
});
let toUpdate$ = payload.filter(p => p.selected).map(p => { return this.promotionService.updatePromotion(p) });
forkJoin(toUpdate$).subscribe(() => this.getAllPromotions());
}
然后我用我的 http 服务发送它们,确保以 FormData 方式传递文件并提供良好的 http headers! (您必须控制您的 api 接受 header enctype
updatePromotion(promotion: any) {
const formData = new FormData();
Object.keys(promotion).forEach(key => formData.append(key, promotion[key]));
const httpOptions = {
headers: new HttpHeaders({
'enctype': 'multipart/form-data',
'Authorization': this.auth
})
};
return this.http.put<any>(this.apiUrl, formData, httpOptions);
}
最后,我的后端 api 负责数据库存储:
首先是路线
const auth = require('../middleware/auth');
const multer = require('../middleware/multer');
const promotionController = require('../controller/promotion-controller');
const promotionUpload = [{
name: 'image',
maxCount: 1
},
{
name: 'pdf',
maxCount: 1
}
];
router.put('/', auth, multer.fields(promotionUpload), promotionController.updatePromotion);
然后控制器
exports.updatePromotion = (req, res) => {
let payload = {
...req.body
};
// If req contains files
if (req.files) {
if (req.files['pdf']) {
payload.pdf = req.protocol + "://" + req.get('host') + "/" + req.files['pdf'][0].path;
}
if (req.files['image']) {
payload.image = req.protocol + "://" + req.get('host') + "/" + req.files['image'][0].path;
}
}
// Update database
Promotion.findByIdAndUpdate(payload._id, payload)
.then(() => res.status(200).json("Success"))
.catch((error) => res.status(500).json("Failure: " + error))
}
就是它了!我用了几天时间来解决这个难题,我很自豪地向您展示了一个可行的解决方案。不要犹豫,问问题。我很乐意提供帮助
TLDR:如何将 FormArray 或 FormGroup 与多个 file-input 字段一起使用?
大家好,
我想用 Angular 构建一个事件管理器。每个活动都有标题、描述、图像和传单。图片是 png 或 jpg file-input,传单是 pdf 格式。
我需要一个执行以下操作的工具栏:
- add 应该在没有图像或 pdf 的情况下将新事件附加到列表中
- 删除应该删除所有选定的事件
- 保存应该将事件更新到数据库并加载两个 file-inputs 文件。
事件列表如下:
但我很难实施表格。
如何将多个 file-input 与 angular 一起使用到动态表单中?
这是我尝试做的(事件 = 促销):
HTML:
<form [formGroup]="promotionForm" *ngFor="let promotion of promotions; let i = index;">
<div class="card" *ngIf="promotion.displayed" (click)="promotion.selected = !promotion.selected;">
<div>
<h2>Title</h2>
<input matInput [(ngModel)]="promotion.title" [ngModelOptions]="{standalone: true}"/>
</div>
<div>
<h2>Subtitle</h2>
<input matInput [(ngModel)]="promotion.subtitle" [ngModelOptions]="{standalone: true}"/>
</div>
<div>
<h2>Language</h2>
<mat-select [(value)]="promotion.language"></mat-select>
</div>
<div class="description">
<h2>Description</h2>
<textarea matInput cdkAutosizeMinRows="5" [(ngModel)]="promotion.description"></textarea>
</div>
<div class="img">
<div class="container">
<h2>Image</h2>
<mat-form-field>
<ngx-mat-file-input formControlName="image" [multiple]="false" accept="image/webp, image/jpeg, image/png">
<mat-icon ngxMatFileInputIcon>folder</mat-icon>
</ngx-mat-file-input>
</mat-form-field>
</div>
<img src="{{promotion.image}}">
</div>
<div class="pdf">
<h2>PDF</h2>
<mat-form-field>
<ngx-mat-file-input formControlName="pdf" [multiple]="false" accept="application/pdf">
<mat-icon ngxMatFileInputIcon>folder</mat-icon>
</ngx-mat-file-input>
</mat-form-field>
</div>
<mat-checkbox class="checkbox" [checked]="promotion.selected"></mat-checkbox>
</div>
</form>
TS:
promotions: Promotion[] = [];
maxSize: number = 2; //Mo
promotionForm: FormGroup;
images: any[];
pdfs: any[];
constructor(private promotionService: PromotionService, private fb: FormBuilder) { }
ngOnInit(): void {
this.promotionForm = this.fb.group({
image: [MaxSizeValidator(this.maxSize * 1024 * 1024)],
pdf: [MaxSizeValidator(this.maxSize * 1024 * 1024)]
})
this.getAllPromotions();
}
getAllPromotions() {
this.promotionService.getAllPromotions()
.subscribe(promotions => {
this.promotions = promotions.reverse();
this.promotions.map(p => {
p.selected = false;
p.displayed = true;
})
})
}
deletePromotions() {
let toDelete$ = this.promotions.filter(p => p.selected).map(p => { return this.promotionService.deletePromotion(p._id) });
forkJoin(toDelete$).subscribe(() => this.getAllPromotions());
}
updatePromotions() {
let toUpdate$ = this.promotions.filter(p => p.selected).map(p => { return this.promotionService.updatePromotion(p) });
forkJoin(toUpdate$).subscribe(() => this.getAllPromotions());
}
selectAll() {
if (this.promotions.filter(p => p.displayed).every(p => p.selected)) {
this.promotions.map(p => p.selected = false)
} else {
this.promotions.filter(p => p.displayed).map(p => p.selected = true)
}
}
此解决方案的问题是为每个事件获取一个唯一的文件。我希望每个事件都有一个唯一的 file-input,如果我使用 FormGroup,我无法将 n file-input 关联到每个事件。我应该使用 FormGroup 的 FormArrays 并在每个组中使用图像和 pdf 吗?
我就是这样实现的。
而不是使用 formArray。我为每个 objects 添加了一个自定义控件。这是我的 html
<ng-container [formGroup]="promotion.filesForm">
<div class="img">
<div class="container">
<h2>Image</h2>
<mat-form-field>
<ngx-mat-file-input formControlName="image" [multiple]="false" accept="image/webp, image/jpeg, image/png" (change)="promotion.image=''">
<mat-icon ngxMatFileInputIcon>folder</mat-icon>
</ngx-mat-file-input>
</mat-form-field>
</div>
<img src="{{promotion.image}}">
</div>
<div class="pdf">
<h2>PDF</h2>
<mat-form-field>
<ngx-mat-file-input formControlName="pdf" [multiple]="false" accept="application/pdf">
<mat-icon ngxMatFileInputIcon>folder</mat-icon>
</ngx-mat-file-input>
</mat-form-field>
<a href="{{promotion.pdf}}">Flyer</a>
</div>
</ng-container>
还有我的老师
addPromotionFileForm() {
const fileForm = new FormGroup({
image: new FormControl('', [MaxSizeValidator(this.maxSize * 1024 * 1024)]),
pdf: new FormControl('', [MaxSizeValidator(this.maxSize * 1024 * 1024)])
});
return fileForm as FormGroup;
}
getAllPromotions() {
this.promotionService.getAllPromotions()
.subscribe(promotions => {
this.promotions = promotions.reverse();
this.promotions.map(p => {
p.selected = false;
p.displayed = true;
p.filesForm = this.addPromotionFileForm();
})
})
}
当我想上传它们时,我对我的事件数组进行了深度复制,并将文件放入图像和 pdf 属性中。
updatePromotions() {
let payload = cloneDeep(this.promotions);
payload.map(p => {
p.image = p.filesForm.value.image;
p.pdf = p.filesForm.value.pdf;
delete p.filesForm;
});
let toUpdate$ = payload.filter(p => p.selected).map(p => { return this.promotionService.updatePromotion(p) });
forkJoin(toUpdate$).subscribe(() => this.getAllPromotions());
}
然后我用我的 http 服务发送它们,确保以 FormData 方式传递文件并提供良好的 http headers! (您必须控制您的 api 接受 header enctype
updatePromotion(promotion: any) {
const formData = new FormData();
Object.keys(promotion).forEach(key => formData.append(key, promotion[key]));
const httpOptions = {
headers: new HttpHeaders({
'enctype': 'multipart/form-data',
'Authorization': this.auth
})
};
return this.http.put<any>(this.apiUrl, formData, httpOptions);
}
最后,我的后端 api 负责数据库存储:
首先是路线
const auth = require('../middleware/auth');
const multer = require('../middleware/multer');
const promotionController = require('../controller/promotion-controller');
const promotionUpload = [{
name: 'image',
maxCount: 1
},
{
name: 'pdf',
maxCount: 1
}
];
router.put('/', auth, multer.fields(promotionUpload), promotionController.updatePromotion);
然后控制器
exports.updatePromotion = (req, res) => {
let payload = {
...req.body
};
// If req contains files
if (req.files) {
if (req.files['pdf']) {
payload.pdf = req.protocol + "://" + req.get('host') + "/" + req.files['pdf'][0].path;
}
if (req.files['image']) {
payload.image = req.protocol + "://" + req.get('host') + "/" + req.files['image'][0].path;
}
}
// Update database
Promotion.findByIdAndUpdate(payload._id, payload)
.then(() => res.status(200).json("Success"))
.catch((error) => res.status(500).json("Failure: " + error))
}
就是它了!我用了几天时间来解决这个难题,我很自豪地向您展示了一个可行的解决方案。不要犹豫,问问题。我很乐意提供帮助