嵌套一个组件,需要从该组件获取父属性值,然后设置另一个

时间:2019-06-16 06:52:17

标签: angular angular-directive angular-components

我有一个文件上传组件,可以根据用户的文件列表选择在其中拆分文件组件实例的多个实例。

一旦用户已将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

2 个答案:

答案 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),但是我已经写过了,除非您告诉我该解决方案由于一个或另一个原因很糟糕,否则我将接受“如果它没有失败, ...”路。