File upload from <input type="file">

File UploadInputAngular

File Upload Problem Overview


Using angular 2 beta, I cannot seem to get an <input type="file"> to work.

Using diagnostic, I can see two-way binding for other types such as text.

<form>
    {{diagnostic}}
    <div class="form-group">
        <label for="fileupload">Upload</label>
        <input type="file" class="form-control" [(ngModel)]="model.fileupload">
    </div>
</form>

In my TypeScript file, I have the following diagnostic line:

get diagnostic() { return JSON.stringify(this.model); }

Could it be that it is the issue of not being JSON? The value is null.

I cannot really verify the value of the input. Уven though the text next to "Choose file ..." updates, I cannot see differences in the DOM for some reason.

File Upload Solutions


Solution 1 - File Upload

I think that it's not supported. If you have a look at this DefaultValueAccessor directive (see https://github.com/angular/angular/blob/master/modules/angular2/src/common/forms/directives/default_value_accessor.ts#L23). You will see that the value used to update the bound element is $event.target.value.

This doesn't apply in the case of inputs with type file since the file object can be reached $event.srcElement.files instead.

For more details, you can have a look at this plunkr: https://plnkr.co/edit/ozZqbxIorjQW15BrDFrg?p=info:

@Component({
  selector: 'my-app',
  template: `
    <div>
      <input type="file" (change)="onChange($event)"/>
    </div>
  `,
  providers: [ UploadService ]
})
export class AppComponent {
  onChange(event) {
    var files = event.srcElement.files;
    console.log(files);
  }
}

Solution 2 - File Upload

@Component({
  selector: 'my-app',
  template: `
    <div>
      <input name="file" type="file" (change)="onChange($event)"/>
    </div>
  `,
  providers: [ UploadService ]
})
export class AppComponent {
  file: File;
  onChange(event: EventTarget) {
		let eventObj: MSInputMethodContext = <MSInputMethodContext> event;
		let target: HTMLInputElement = <HTMLInputElement> eventObj.target;
		let files: FileList = target.files;
		this.file = files[0];
		console.log(this.file);
	}

   doAnythingWithFile() {
   }

}

Solution 3 - File Upload

There is a slightly better way to access attached files. You could use template reference variable to get an instance of the input element.

Here is an example based on the first answer:

@Component({
  selector: 'my-app',
  template: `
    <div>
      <input type="file" #file (change)="onChange(file.files)"/>
    </div>
  `,
  providers: [ UploadService ]
})

export class AppComponent {
  onChange(files) {
    console.log(files);
  }
}

Here is an example app to demonstrate this in action.

Template reference variables might be useful, e.g. you could access them via @ViewChild directly in the controller.

Solution 4 - File Upload

Another way using template reference variable and ViewChild, as proposed by Frelseren:

import { ViewChild } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <div>
      <input type="file" #fileInput/>
    </div>
  `
})  
export class AppComponent {
  @ViewChild("fileInput") fileInputVariable: any;
  randomMethod() {
    const files = this.fileInputVariable.nativeElement.files;
    console.log(files);
  }
}

Also see https://stackoverflow.com/a/40165524/4361955

Solution 5 - File Upload

Try this small lib, works with Angular 5.0.0

Quickstart example with ng2-file-upload 1.3.0:

User clicks custom button, which triggers upload dialog from hidden input type="file" , uploading started automatically after selecting single file.

app.module.ts:

import {FileUploadModule} from "ng2-file-upload";

your.component.html:

...
  <button mat-button onclick="document.getElementById('myFileInputField').click()" >
    Select and upload file
  </button>
  <input type="file" id="myFileInputField" ng2FileSelect [uploader]="uploader" style="display:none">
...

your.component.ts:

import {FileUploader} from 'ng2-file-upload';
...    
uploader: FileUploader;
...
constructor() {
   this.uploader = new FileUploader({url: "/your-api/some-endpoint"});
   this.uploader.onErrorItem = item => {
       console.error("Failed to upload");
       this.clearUploadField();
   };
   this.uploader.onCompleteItem = (item, response) => {
       console.info("Successfully uploaded");
       this.clearUploadField();

       // (Optional) Parsing of response
       let responseObject = JSON.parse(response) as MyCustomClass;
       
   };

   // Asks uploader to start upload file automatically after selecting file
  this.uploader.onAfterAddingFile = fileItem => this.uploader.uploadAll();
}

private clearUploadField(): void {
    (<HTMLInputElement>window.document.getElementById('myFileInputField'))
    .value = "";
}

Alternative lib, works in Angular 4.2.4, but requires some workarounds to adopt to Angular 5.0.0

Solution 6 - File Upload

If you have a complex form with multiple files and other inputs here is a solution that plays nice with ngModel.

It consists of a file input component that wraps a simple file input and implements the ControlValueAccessor interface to make it consumable by ngModel. The component exposes the FileList object to ngModel.

This solution is based on this article.

The component is used like this:

<file-input name="file" inputId="file" [(ngModel)]="user.photo"></file-input>
<label for="file"> Select file </label>

Here's the component code:

import { Component, Input, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';

const noop = () => {
};

export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => FileInputComponent),
    multi: true
};

@Component({
  selector: 'file-input',
  templateUrl: './file-input.component.html',
  providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class FileInputComponent {

  @Input()
  public name:string;

  @Input()
  public inputId:string;

  private innerValue:any;

  constructor() { }

  get value(): FileList {
    return this.innerValue;
  };

  private onTouchedCallback: () => void = noop;
  private onChangeCallback: (_: FileList) => void = noop;

  set value(v: FileList) {
    if (v !== this.innerValue) {
      this.innerValue = v;
      this.onChangeCallback(v);
    }
  }

  onBlur() {
    this.onTouchedCallback();
  }

  writeValue(value: FileList) {
    if (value !== this.innerValue) {
      this.innerValue = value;
    }
  }

  registerOnChange(fn: any) {
    this.onChangeCallback = fn;
  }

  registerOnTouched(fn: any) {
    this.onTouchedCallback = fn;
  }

  changeFile(event) {
    this.value = event.target.files;
  }
}

And here's the component template:

<input type="file" name="{{ name }}" id="{{ inputId }}" multiple="multiple" (change)="changeFile($event)"/>

Solution 7 - File Upload

just try (onclick)="this.value = null"

in your html page add onclick method to remove previous value so user can select same file again.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPascalVKootenView Question on Stackoverflow
Solution 1 - File UploadThierry TemplierView Answer on Stackoverflow
Solution 2 - File UploadAshish DoneriyaView Answer on Stackoverflow
Solution 3 - File UploadFrelserenView Answer on Stackoverflow
Solution 4 - File UploadjhujhulView Answer on Stackoverflow
Solution 5 - File UploadYurii BratchukView Answer on Stackoverflow
Solution 6 - File UploadJensView Answer on Stackoverflow
Solution 7 - File Uploadyogesh chavanView Answer on Stackoverflow