Search code examples
angulartypescriptvalidationarraylistfile-upload

How to display error message for a list item in angular


I've a file upload screen where users can click Choose File button and the files are supposed to display as a list. But if the file is too big or doesn't meet file type requirement then that file should display in a light red color with error message.

I've validation code in my .ts file but the error message shows for all the files even if the file doesn't have any error.

Here's my .ts file:

 public validationError: { messages: string[] } = null;
    public validateFiles(attachedFiles: AttachedFile[]) {
        const extensions = this.accept && this.accept
          .split(',')
          .map(x => x.toLowerCase().trim())
          .filter(x => !!x);
    
        const maxSize = this.maxFileSizeMB * 1024 * 1024;
        const errors = attachedFiles.map(file => {
          if (extensions && !extensions.some(x => file.name.toLowerCase().endsWith(x))) {
           
          
            return `File Type is not accepted`;
          }
          if (file.size > maxSize) {
            
            return `Exceeds maximum file size of ${this.maxFileSizeMB}MB.`
          }
          return null;
        }).filter(x => !!x);
    
        if (errors.length) {
          return {
            messages: errors
          };
        }
    
        return null;
      }

And this is my html file. I know there's something I should do with validation.messages but how do I assign it to a particular file?

<ul *ngIf="attachedFiles && validationError" class="title-attached-files__list-with-errors">
    <li *ngFor="let file of attachedFiles; let i = index" class="c-file-with-errors" >
        <ng-container *ngIf="validationError">
            <span class="c-title-attached-files__validation-error" *ngFor="let message of validationError.messages">
                {{ message }}
            </span>
        </ng-container>
        <mat-icon class="c-file__icon" color="primary">upload_file</mat-icon>
        <span class="c-file__name">{{file.name}}</span>
        <span class="c-file__size">{{file.size | fileSize }}</span>
        <span *ngFor="let message of validationError.messages">
            {{ message }}
        </span>
        <button *ngIf='!readonly' class="c-file__delete" type="button"
            (click)="removeFile(i)"
            mat-icon-button color="primary"
            aria-label="Delete Added File">
                <mat-icon>close</mat-icon>
        </button>
    </li>
</ul>

Solution

  • As this validationError instance is shared among each file in attachedFiles array, you will see the error message is applied to each file.

    Would suggest that you should make the validationError as an array (validationErrors), and store the error instance for the file individually.

    1. Create validationErrors instance with the type of array.
    public validationErrors: { messages: string[] }[] = null;
    
    1. Modify the validateFiles method to return the value of an array. If the file contains an error, the array will store an object in its position, otherwise it stores null.
    public validateFiles(attachedFiles: AttachedFile[]) {
      const extensions =
        this.accept &&
        this.accept
          .split(',')
          .map((x) => x.toLowerCase().trim())
          .filter((x) => !!x);
    
      const maxSize = this.maxFileSizeMB * 1024 * 1024;
    
      return attachedFiles.map((file) => {
        let errors: string[] = [];
        if (
          extensions &&
          !extensions.some((x) => file.name.toLowerCase().endsWith(x))
        ) {
          errors.push(`File Type is not accepted`);
        }
    
        if (file.size > maxSize) {
          errors.push(`Exceeds maximum file size of ${this.maxFileSizeMB}MB.`);
        }
    
        if (errors.length > 0) {
          return {
            messages: errors,
          };
        }
    
        return null;
      });
    }
    
    1. Display the error instance for each file with the indexer (validationErrors[i]). With optional chaining ?. to handle when the validationErrors[i] is null.
    <ul
      *ngIf="attachedFiles && validationErrors"
      class="title-attached-files__list-with-errors"
    >
      <li
        *ngFor="let file of attachedFiles; let i = index"
        class="c-file-with-errors"
      >
        <ng-container *ngIf="validationErrors">
          <span
            class="c-title-attached-files__validation-error"
            *ngFor="let message of validationErrors[i]?.messages"
          >
            {{ message }}
          </span>
        </ng-container>
        <mat-icon class="c-file__icon" color="primary">upload_file</mat-icon>
        <span class="c-file__name">{{file.name}}</span>&nbsp;
        <!--<span class="c-file__size">{{file.size | fileSize }}</span>-->
        <span class="c-file__size">{{file.size }}</span>
        <span *ngFor="let message of validationErrors[i]?.messages">
          {{ message }}
        </span>
        <button
          *ngIf="!readonly"
          class="c-file__delete"
          type="button"
          (click)="removeFile(i)"
          mat-icon-button
          color="primary"
          aria-label="Delete Added File"
        >
          <mat-icon>close</mat-icon>
        </button>
      </li>
    </ul>
    

    Demo @ StacBlitz