вложенность компонента и необходимость получить значение свойства parent от указанного компонента и установить другой - PullRequest
1 голос
/ 16 июня 2019

У меня есть компонент загрузки файлов, который я раскручиваю несколько экземпляров экземпляров файловых компонентов внутри на основе выбора списка файлов пользователя.

Как только пользователь загрузил n файлов в контейнер загрузки, у него есть возможность добавить некоторый текст описания для каждого компонента файла независимо, который я просто устанавливаю как строковое свойство внутри компонента файла и двустороннее связывание. к нему из HTML с ngModel. На родительском компоненте есть кнопка, с которой начинается процесс загрузки. Я пытаюсь перебрать экземпляры файловых компонентов, которые я поместил в массив, а свойство 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>

Я прошу прощения за весь этот код рвоты, но хотел нарисовать очень четкую картину. Я уверен, что оба предмета являются чем-то простым, но я в тупике.

ТИА

Ответы [ 2 ]

1 голос
/ 16 июня 2019

Добавьте @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>

и в родительском компоненте введите вышеуказанный метод для обработки события , излучаемого из дочернего компонента.

.ts

handleCompleteEvent(status: boolean) {
   if (status) {
      // do something here...
   }
}
0 голосов
/ 16 июня 2019

основываясь на ответе Николая, я придумал это решение.Я запускаю 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, Николас, но я уже написал это, и если вы не скажете мне, что это решение ужасно по той или иной причине,Я пойду по дороге "если не сломано ...".

...