I am making a simple code to accept a few values in one Form and display the list of form values on the right side. I have created Modal Class of the Form Fields and on Submit, I am sending data to Service. In the other component i have subscribed to the data. For some unknown Reason, My functions in this second component are getting called Twice. Also I am accepting an image file or Image URL in form and generating a preview of the file selected in form and also in list. To Generate the preview of this file in Form, it works flawlessly but in Second component, the same code goes into infinite loop. Any
my main Form Component html
<div class="row form">
<div class="col-8">
<form [formGroup]="songMetadata" (submit)="onSubmit(songMetadata)">
<div class="row">
<div class="col form-data">
<mat-form-field>
<mat-label>Song Name</mat-label>
<input matInput formControlName="songName" />
</mat-form-field>
<br />
<br />
<mat-form-field>
<mat-label>Artist Name</mat-label>
<input matInput formControlName="artistName" />
</mat-form-field>
<br />
<br />
<mat-form-field>
<mat-label>Album Name</mat-label>
<input matInput formControlName="albumName" />
</mat-form-field>
<br />
<br />
<mat-form-field>
<mat-label>Spotify URL</mat-label>
<input matInput formControlName="url" />
</mat-form-field>
<br />
<br />
<mat-form-field>
<mat-label>Other Description</mat-label>
<textarea
matInput
formControlName="description"
rows="4"
></textarea>
</mat-form-field>
<br />
<br />
<button
type="submit"
class="btn btn-primary"
[disabled]="
songMetadata.invalid ||
(!isImageFileSelected && !isImageURLEntered)
"
>
Submit
</button>
</div>
<div class="col image-upload">
<div
dropZone
class="text-center dropzone"
(hovered)="changeIsHover($event)"
(dropped)="fileDropped($event)"
[class.hovering]="isHovering"
>
<img
*ngIf="isImageFileSelected || isImageURLEntered"
[src]="imagePreview"
alt=""
width="192"
height="190"
/>
<div
class="drop-text"
*ngIf="!isImageFileSelected && !isImageURLEntered"
>
Drag And Drop File Here
</div>
</div>
<input
type="text"
placeHolder="Or Enter URL"
(blur)="loadPreviewFromURL($event)"
/>
<br />
<label for="files" class="btn btn-primary">Or Select Image</label>
<br />
<input
id="files"
style="visibility:hidden;"
type="file"
(change)="fileSelected($event)"
/>
<br />
<!-- <input type="file" /> -->
</div>
</div>
</form>
</div>
<!-- List View -->
<div class="col-4">
<list></list>
</div>
</div>
Main Component TS.
import { DataService } from "./data.service";
import { AngularFireStorageModule } from "@angular/fire/storage";
import { Component, OnInit } from "@angular/core";
import { FormGroup, FormBuilder, Validators } from "@angular/forms";
import { Observable } from "rxjs";
import { ISong } from "./song";
import { Data } from "@angular/router";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.scss"]
})
export class AppComponent implements OnInit {
songMetadata: FormGroup;
selectedFile: File = null;
isImageFileSelected: boolean;
isImageURLEntered: boolean;
isHovering: boolean;
song: ISong;
imagePreview: any;
// // Upload Related Stuff
// task: AngularFireUploadTask;
// snapshot: Observable<any>;
constructor(
private formBuilder: FormBuilder,
private service: DataService // private storage: AngularFireStorage
) {
this.isImageFileSelected = false;
this.isHovering = false;
this.isImageURLEntered = false;
}
ngOnInit() {
this.songMetadata = this.formBuilder.group({
songName: ["xzcvzxv", Validators.required],
artistName: ["xzcvcxzv", Validators.required],
albumName: ["zxcvcxv", Validators.required],
url: ["zxcvvc", Validators.required],
description: ["zxcvzcxv", Validators.required]
});
}
fileSelected(event: any) {
this.isImageFileSelected = true;
this.isImageURLEntered = false;
this.selectedFile = event.target.files[0];
this.loadPreview();
}
fileDropped(event: FileList) {
this.isImageFileSelected = true;
this.isImageURLEntered = false;
this.selectedFile = event.item(0);
this.loadPreview();
}
onSubmit(songForm: FormGroup) {
event.preventDefault();
if (this.isImageFileSelected) {
this.song = {
name: songForm.value.songName,
artist: songForm.value.artistName,
album: songForm.value.albumName,
url: songForm.value.url,
description: songForm.value.description,
imageFile: this.selectedFile,
imageURL: null
};
} else {
this.song = {
name: songForm.value.songName,
artist: songForm.value.artistName,
album: songForm.value.albumName,
url: songForm.value.url,
description: songForm.value.description,
imageFile: null,
imageURL: this.imagePreview
};
}
this.service.addSong(this.song);
}
changeIsHover(isHovering: boolean) {
this.isHovering = isHovering;
}
loadPreview = () => {
let reader = new FileReader();
reader.readAsDataURL(this.selectedFile);
reader.onload = event => {
this.imagePreview = reader.result;
};
console.log("PREVIEW HIT");
};
loadPreviewFromURL(event: any) {
console.log(event.target.value);
this.isImageURLEntered = true;
this.isImageFileSelected = false;
this.imagePreview = event.target.value;
}
}
list component HTML
<div *ngFor="let s of songs; let i = index">
<div class="col-4">
<img
*ngIf="s?.imageURL"
[src]="livePrvw(s?.imageURL)"
width="100"
height="100"
alt="Image URL Preview"
/>
<img
*ngIf="s?.imageFile"
[src]="livePreview(s?.imageFile)"
width="100"
height="100"
alt="Image FILE Preview"
/>
</div>
<div class="col-8">
{{ s.name }}
</div>
</div>
listComponent TS
import { DataService } from "./../data.service";
import { ISong } from "./../song";
import { Component, OnInit, OnChanges } from "@angular/core";
@Component({
selector: "list",
templateUrl: "./list.component.html",
styleUrls: ["./list.component.scss"]
})
export class ListComponent implements OnInit {
songs: ISong[];
// song: ISong;
constructor(private service: DataService) {}
ngOnInit() {
this.service.songsAsObservable.subscribe(data => {
this.songs = data;
});
}
// also getting called twice
livePreview(file: File) {
// Going into infinite loop
// let newReader = new FileReader();
// let imagePreview: any;
// newReader.readAsDataURL(file);
// newReader.onload = event => {
// imagePreview = newReader.result;
// };
console.log(file);
}
// getting called twice somehow
livePrvw(imageURL: string) {
console.log(imageURL);
return imageURL;
}
}
To Reiterate the methods in ListComponent Get Called Twice for no reason and FileReader Code working in Main Component does no work in ListComponent (Goes into infinite Loop)
Any Help Is Much Appreciated
Your problem
You have a form that allows users to upload an image or specify an image URL.
When the form is submitted, you render the uploaded image.
The functions that return the image URL either run multiple times or don't work.
My diagnosis
There are a few issues with your current solution.
You are binding
<img src />to a function that resolves the URL. Every time change detection runs your function will be queried. You should do as little processing as possible in the change detection cycle.In general this would be fixed by setting your component
changeDetectionStrategytoOnPush, injecting aChangeDetectorRef, and manually callingdetectChanges()when you want to trigger change detection. However, I think this would be papering over the cracks in this case, so is not my suggested solution.You are attempting to bind
<img src />to a function that gets the data URL viaFileReader. The file reader executes asynchronously, so you would need to wrap this in some kind of promise / observable and use theasyncpipe to call this.BUT you shouldn't be doing this for the reason stated in point 1.
My approach
I'm going to focus on the specific problem of how to resolve the image URL, as you have a complex form that is mostly out of scope for this problem.
My main refactor here would be to take the URL resolution out of the change detection cycle. I would resolve the URL first, and then add the item to the array with a resolved URL.
My implementation
I am going to abstract your problem into a simple app where you can upload an image and/or specify an image URL. The resolved images will be displayed after the form is submitted.
The component is fairly trivial, so I will only show the service here:
image.service.ts
Notice how the images are only pushed into the array and subject once the URL has been resolved.
I've definitely simplified this a lot compared to your form, but I think you could take some inspiration from this approach to fix your problem.
DEMO: https://stackblitz.com/edit/angular-yhnnwv