我有一个文件上传组件,可以根据用户的文件列表选择在其中拆分文件组件实例的多个实例。
一旦用户已将n个文件加载到上载容器中,他们可以选择为每个文件组件独立添加一些描述文本,我只是将其设置为文件组件内部的字符串属性,并进行双向绑定使用ngModel
从HTML中添加到它。在父组件上,有一个按钮将启动上传过程。我正在尝试遍历已推送到数组的文件组件实例,并且无法从循环内的父级访问description属性。
那是问题之一。问题二是我还想在子组件上设置一个布尔属性(isUploading),以便可以向用户提供有关该特定文件正在进行中的反馈。现在,我只是试图为该子文件组件显示一个ProgressSpinner
。但这不是基于我更新父组件中循环内的引用而自动更新的。
我确定事件或类似事件是我所缺少的,但是我正在努力将它们放在一起,并且找不到适合我的方案的资源。
以下是父项(文件上传组件)ts:
import { Component, OnInit, Input } from '@angular/core';
import { MatFileComponent } from './mat-file.component';
@Component({
selector: 'mat-file-upload',
templateUrl: './mat-file-upload.component.html',
styleUrls: ['./mat-file-upload.component.css']
})
export class MatFileUploadComponent implements OnInit {
constructor() { }
fileList: MatFileComponent[]
@Input() apiEndpoint: string;
@Input() parentContainerId: string;
hasFiles: boolean;
bucketDescription: string;
addFilesToList(files: File[]): void {
this.fileList = [];
for (let file of files) {
// generate the file component here in code then bind them in the loop in the HTML
let fileComponent = new MatFileComponent()
fileComponent.fileData = file;
fileComponent.fileDescription = '';
fileComponent.fileName = file.name;
fileComponent.fileType = file.type;
this.fileList.push(fileComponent);
}
this.hasFiles = true;
}
startUpload(): void {
if (!this.fileList || !this.fileList.length || !this.hasFiles) {
return;
}
for (let fileComponent of this.fileList) {
console.log("desc: " + fileComponent.fileDescription);
fileComponent.isUploading = true;
}
}
ngOnInit() {
}
}
以下是其支持的HTML:
<input type="file" hidden name="addToList" [class]="ng-hide" #file multiple id="addToList" (change)="addFilesToList(file.files)" />
<label for="addToList" class="mat-raised-button">
Select Files To Upload
</label>
<div *ngIf="fileList && fileList.length">
<mat-file *ngFor="let file of fileList"
[fileName]="file.fileName"
[fileData]="file.fileData"
[fileType]="file.fileType"
[projectId]="projectId"></mat-file>
</div>
<mat-card class="card-footer" *ngIf="hasFiles">
<mat-form-field>
<textarea matInput required placeholder="*Required* file bucket description..." [(ngModel)]="bucketDescription"></textarea>
</mat-form-field>
<button class="mat-raised-button submit-form" (click)="startUpload()" [disabled]="!bucketDescription || !bucketDescription.length > 0">
Upload Files
</button>
</mat-card>
以下是子(文件组件)ts:
import { Component, OnInit, Input } from '@angular/core';
import { IFile } from '../Interfaces/IFile';
@Component({
selector: 'mat-file',
templateUrl: './mat-file.component.html',
styleUrls: ['./mat-file.component.css']
})
export class MatFileComponent implements OnInit, IFile {
@Input() fileName: string;
@Input() fileData: File;
@Input() fileType: string;
@Input() projectId: number;
public isUploading: boolean;
fileDescription: string;
imageLocalUrl: any;
componentLoaded: boolean = false
constructor() { }
get isFileImage(): boolean {
return this.fileType.toLowerCase().indexOf('image') > -1;
}
ngOnInit() {
var reader = new FileReader();
reader.readAsDataURL(this.fileData);
reader.onload = (event) => {
this.imageLocalUrl = reader.result;
}
this.componentLoaded = true;
}
}
及其支持的HTML:
<div *ngIf="componentLoaded" class="file-card">
<mat-card class="mat-card-image">
<mat-card-subtitle>{{ fileName }}</mat-card-subtitle>
<mat-card-content>
<div *ngIf="imageLocalUrl && isFileImage" class="image-thumb file-thumb" [style.backgroundImage]="'url(' +imageLocalUrl+ ')'"></div>
<mat-form-field>
<textarea matInput placeholder="*Optional* file description..." [(ngModel)]="fileDescription"></textarea>
</mat-form-field>
</mat-card-content>
<div *ngIf="(isUploading)" class="loading-indicator-shade">
<mat-progress-spinner class="loading-indicator" mode="indeterminate"></mat-progress-spinner>
</div>
</mat-card>
</div>
这是我如何使用tag指令启动文件上传组件:
<mat-file-upload [apiEndpoint]="'/api/ProjectApi/UploadFile'" [parentContainerId]="projectId"></mat-file-upload>
对于所有此代码呕吐我深表歉意,但想描绘出非常清晰的画面。我确定这两个项目都很简单,但是我很困惑。
TIA
答案 0 :(得分:1)
将@Output()添加到您的子组件中,以便它可以将更改回传给父组件。
export class MatFileComponent implements OnInit, IFile {
@Output() uploadComplete = new EventEmitter<boolean>();
...
onComplete() {
this.uploadComplete.next(true);
}
...
}
现在在父组件中监听此事件。
<div *ngIf="fileList && fileList.length">
<mat-file *ngFor="let file of fileList"
[fileName]="file.fileName"
[fileData]="file.fileData"
[fileType]="file.fileType"
[projectId]="projectId">
(onComplete)="handleCompleteEvent($event)"</mat-file>
</div>
并在父组件中引入上述方法来处理子组件 emitted 的事件。
.ts
handleCompleteEvent(status: boolean) {
if (status) {
// do something here...
}
}
答案 1 :(得分:0)
根据尼古拉斯的回答,我想出了这个解决方案。我在初始化的EventEmitter
中触发了ngOnInit()
,并将this
传递回父文件上传组件。在该组件的类中,我只是将对子组件的引用添加到一个数组中,从而可以正确访问其成员。
文件上传组件:
import { Component, OnInit, Input } from '@angular/core';
import { MatFileComponent } from './mat-file.component';
import { FileUploadService } from '../Services/file-upload.service';
import { catchError, map } from 'rxjs/operators';
import { IFile } from '../Interfaces/IFile';
@Component({
selector: 'mat-file-upload',
templateUrl: './mat-file-upload.component.html',
styleUrls: ['./mat-file-upload.component.css']
})
export class MatFileUploadComponent implements OnInit {
constructor(private fileUploadService: FileUploadService) { }
matFileComponents: MatFileComponent[] = [];
fileList: any[];
@Input() apiEndpoint: string;
@Input() parentContainerId: string;
hasFiles: boolean;
bucketDescription: string;
bucketId: string = "0";
uploaded: number = 0;
errorMessage: string;
addFilesToList(files: File[]): void {
this.fileList = [];
for (let file of files) {
// generate the file collectoin here then loop over it to create the children in the HTML
let fileComponent =
{
"fileData": file,
"fileName": file.name,
"fileType": file.type
}
this.fileList.push(fileComponent);
}
this.hasFiles = true;
}
addInstanceToCollection(matFileComponent: MatFileComponent): void {
this.matFileComponents.push(matFileComponent);
}
startUpload(): void {
for (let matFileComponent of this.matFileComponents) {
// just for testing, make sure the isUploading works and log the description to the console to make sure it's here
// hell, let's just log the whole instance ;) The file upload service will be called from here
matFileComponent.isUploading = true;
console.log(matFileComponent.fileDescription);
console.log(matFileComponent);
}
}
ngOnInit() {
}
}
其支持HTML:
<input type="file" hidden name="addToList" [class]="ng-hide" #file multiple id="addToList" (change)="addFilesToList(file.files)" />
<label for="addToList" class="mat-raised-button">
Select Files To Upload
</label>
<div *ngIf="fileList && fileList.length" >
<mat-file *ngFor="let file of fileList"
[fileName]="file.fileName"
[fileData]="file.fileData"
[fileType]="file.fileType"
[projectId]="projectId"
(instanceCreatedEmitter)="addInstanceToCollection($event)"></mat-file>
</div>
<mat-card class="card-footer" *ngIf="hasFiles">
<mat-form-field>
<textarea matInput required placeholder="*Required* file bucket description..." [(ngModel)]="bucketDescription"></textarea>
</mat-form-field>
<button class="mat-raised-button submit-form" (click)="startUpload()" [disabled]="!bucketDescription || !bucketDescription.length > 0">
Upload Files
</button>
</mat-card>
文件组件:
import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
import { IFile } from '../Interfaces/IFile';
@Component({
selector: 'mat-file',
templateUrl: './mat-file.component.html',
styleUrls: ['./mat-file.component.css']
})
export class MatFileComponent implements OnInit, IFile {
@Input() fileName: string;
@Input() fileData: File;
@Input() fileType: string;
@Input() projectId: number;
@Input() isUploading: boolean = false;
@Output() instanceCreatedEmitter = new EventEmitter<MatFileComponent>();
public fileDescription: string;
imageLocalUrl: any;
componentLoaded: boolean = false
constructor() { }
get isFileImage(): boolean {
return this.fileType.toLowerCase().indexOf('image') > -1;
}
ngOnInit() {
var reader = new FileReader();
reader.readAsDataURL(this.fileData);
reader.onload = (event) => {
this.imageLocalUrl = reader.result;
}
this.componentLoaded = true;
// now emit this instance back to the parent so they can add it to their collection
this.instanceCreatedEmitter.emit(this);
}
}
其支持HTML:
<div *ngIf="componentLoaded" class="file-card">
<mat-card class="mat-card-image">
<mat-card-subtitle>{{ fileName }}</mat-card-subtitle>
<mat-card-content>
<div *ngIf="imageLocalUrl && isFileImage" class="image-thumb file-thumb" [style.backgroundImage]="'url(' +imageLocalUrl+ ')'"></div>
<mat-form-field>
<textarea matInput placeholder="*Optional* file description..." [(ngModel)]="fileDescription"></textarea>
</mat-form-field>
</mat-card-content>
<div *ngIf="isUploading" class="loading-indicator-shade">
<mat-progress-spinner class="loading-indicator" mode="indeterminate"></mat-progress-spinner>
</div>
</mat-card>
</div>
这解决了我在OP中的两个问题,因为现在我已经引用了需要链接其属性的实际组件。
我确实读过@ViewChild选项以及尼古拉斯(Nicholas),但是我已经写过了,除非您告诉我该解决方案由于一个或另一个原因很糟糕,否则我将接受“如果它没有失败, ...”路。