2

How can I repopulate my file with input type=file within a reactive form?

I am working on a project where I have a form to submit data (workouts) which builds out a list according to the form. From there, you can go in to view the workout submitted, and edit it.

When you click "Edit Workout", the same submission form is opened up, this time populated with all the data. This is done by linking the "Edit Workout" to the EditComponent , and then ngOnInit checks the route params. If the route params have an id, then it initializes the form with the workout data.

This is all working just fine.

The problem I am having relates specifically to the repopulation of the <input type="file"...>.

Here is my Form Initialization when I go to edit First, I set all variables blank on initForm(), then if the editMode is true (if I am editing), then it will fill each of those empty variables with the data coming from the server (or dummy data).


private initForm() {

// set all values to empty
  let workoutImageUrl = "";
  let workoutTitle = "";
  let workoutPhase = [];
  let workoutType = "";
  let workoutDuration = "";
  let workoutSpecialty = [];
  let workoutDescription = "";
  let workoutZwo = "";

// check if in edit mode, and if so, make values equal to database data
 if (this.editMode) {
    const workout = this.workoutService.getWorkout(this.id);
    workoutTitle = workout.title;
    workoutImageUrl = workout.imageUrl;
    workoutPhase = workout.phase;
    workoutType = workout.type;
    workoutDuration = workout.duration;
    workoutSpecialty = workout.specialty;
    workoutZwo = workout.zwo;
    workoutDescription = workout.description;
   this.imgSrc = workout.imageUrl;
    console.log("workout that should be here: ", workout);
  }

// Populate form either with empty data or editable data
 this.workoutForm = new FormGroup({
    title: new FormControl(workoutTitle, Validators.required),
    imageUrl: new FormControl(workoutImageUrl),
    phase: new FormControl(workoutPhase, Validators.required),
    duration: new FormControl(workoutDuration, Validators.required),
    type: new FormControl(workoutType, Validators.required),
    specialty: new FormControl(workoutSpecialty, Validators.required),
    zwo: new FormControl(workoutZwo, Validators.required),
    description: new FormControl(workoutDescription, Validators.required)
  });
}

When I do this for a new workout (standard form), there is no problem. The problem comes when I am in EditMode and trying to repopulate data within the <input type="file"...> like mentioned above.

Right now, my workoutImageUrl or workout.imageUrl is coming from a Url given from my FireBase Storage so I can see why there is an issue here with the input type. The same happens if I use a local file, it cannot be re-populated to the input type of file.

I imagine this is due to security issues, but this is causing my form to get the following error: from Chrome: "ERROR DOMException: Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string." from FF: "ERROR DOMException: "An attempt was made to use an object that is not, or is no longer, usable"

Is there any way I can "repopulate" my image for when I am editing a workout? So the file appears as the current item within the workout?

Like mentioned above, I have tried using local image files which gives the same result. I have also tried removing the imageUrl from the form group completely, but this doesn't help as I want it to be in the form group. I have also tried changing the workoutImageUrl to be the same as the imgSrc coming from Firebase. This works if I want to show the current workout image, but it does not work when I submit because I get an error that the current image is null.

Here is a StackBlitz for this project (a little bit simplified). If you click on a workout in the list, you can then select "manage workout" and "edit workout" to get to the edit page with the filled form (currently not working with the image upload).

The WorkoutEditComponent can be found through src / app / features / workouts-page / workout-edit . You can see in here the logic of the image upload, but I don't think this matters to this specific issue. The database storage is not working on the StackBlitz, but the uploading of the images is working (it just isn't saving). I put in a sample API to get this working minimally for this StackBlitz.

The WorkoutsListComponent is where the list of workouts submitted is being displayed.

Is there any way to retain this workout image file when I view it while editing a workout? And at the same time, not have to re-upload the image while editing a form? I had the image set to display in the bottom of the edit page, but if it displays here, it still does not re-upload or use the same image from the server. I would have to re-upload the image every time.

Please let me know if there is anything I can explain or expound on.

2 Answers 2

1

What you need is re-creating the Object file from image url then assign it to input file.

I came up with this idea :

@ViewChild('fileInput', { static : false}) fileInput : ElementRef; //declaration

private initForm() {
    let workoutImageUrl = "";
    let workoutTitle = "";
    let workoutPhase = [];
    let workoutType = "";
    let workoutDuration = "";
    let workoutSpecialty = [];
    let workoutDescription = "";
    let workoutZwo = "";

    if (this.editMode) {
      const workout = this.workoutService.getWorkout(this.id);
      workoutTitle = workout.title;
      workoutImageUrl = workout.imageUrl;

      workoutPhase = workout.phase;
      workoutType = workout.type;
      workoutDuration = workout.duration;
      workoutSpecialty = workout.specialty;
      workoutZwo = workout.zwo;
      workoutDescription = workout.description;
    }

    this.workoutForm = new FormGroup({
      title: new FormControl(workoutTitle, Validators.required),
      imageUrl: new FormControl(''),
      phase: new FormControl(workoutPhase, Validators.required),
      duration: new FormControl(workoutDuration, Validators.required),
      type: new FormControl(workoutType, Validators.required),
      specialty: new FormControl(workoutSpecialty, Validators.required),
      zwo: new FormControl(workoutZwo, Validators.required),
      description: new FormControl(workoutDescription, Validators.required)
    });
    let image_name = workoutImageUrl.split("/").pop();
    image_name = image_name.split('?')[0];
    fetch(workoutImageUrl, { mode: 'no-cors'})
      .then(res => res.blob())
      .then(blob => {
        const data = new ClipboardEvent('').clipboardData || new DataTransfer();
        data.items.add(new File([blob], image_name));
        this.fileInput.nativeElement.files = data.files;
        this.fileInput.nativeElement.value = data.files[0];
    });
}

PS : I have try it on your stackblitz Example and it's working i hope this helps. Good luck!

Another way to fix cors problem is to use this :

const proxyUrl = 'https://cors-anywhere.herokuapp.com/';
fetch(proxyUrl  + workoutImageUrl)
Sign up to request clarification or add additional context in comments.

8 Comments

Hello, this works great for the images from the local files, but what about for images that are hosted on the web? For example, I have updated the image files in this stackblitz to show images from firebase storage, but the code does not work to keep these in the input while in view mode. Thanks again stackblitz.com/edit/github-x1tx9w-g3xjfx
It's a cors policy problem. I have updated my answer to fix it.
i apologize for another question. The url is appearing with colons : instead of slashes / on the url. This makes it invalid. Is there a way around this? i haven't seen anything looking around.
Yes it's normal because workoutImageUrl have the url from firebase storage and we use it directly to create the file object
Because of the colon, the file input is returned as null, this unfortunately does not work
|
0

I suggest create a function that return a FormGroup

initForm(data:any)
{
  data=data || {
     workoutImageUrl:'';
     workoutTitle:"";
     workoutPhase:[];
     workoutType:"";
     workoutDuration:"";
     workoutSpecialty:[];
     workoutDescription:"";
     workoutZwo:"";
  return new FormGroup({
      title: new FormControl(data.workoutTitle, Validators.required),
      imageUrl: new FormControl(''),
      phase: new FormControl(data.workoutPhase, Validators.required),
      duration: new FormControl(data.workoutDuration, Validators.required),
      type: new FormControl(data.workoutType, Validators.required),
      specialty: new FormControl(data.workoutSpecialty, Validators.required),
      zwo: new FormControl(data.workoutZwo, Validators.required),
      description: new FormControl(data.workoutDescription, Validators.required)
    });
}

you use

this.form=this.initForm(null) //<--an empty form
this.form=this.initForm(workout ) //<--a form with the data

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.