问题
I have a Angular 8 application and I have a form. But I want to detect a change when a user selects a new image. But nothing happened
Googled, following tutorials.
So this is the html template file:
<app-vital10-page [noTopBar]="true">
<div class="settings-account backslab">
<main class="settings-account-main">
<form class="form settings-account-form" *ngIf="profile" #form="ngForm" (ngSubmit)="save(form)" id="form-settings-account">
<h2 class="page-title settings-account-title">Account</h2>
<div *ngIf="formErrors && formErrors.length > 0" class="data-entry" [@errors]>
<div class="error-box">
<div class="error-box-close" (click)="clearErrors()">X</div>
<div *ngFor="let errorField of formErrors">
<h4 class="error-field">{{ getLabel(errorField.header) }}</h4>
<ul class="error-list">
<li class="error-message" *ngFor="let errorMessage of errorField.messages">{{ errorMessage }}</li>
</ul>
</div>
</div>
</div>
<div class="settings-account-body">
<section class="settings-account-form-form">
<div class="row">
<div class="input-group col-4">
<label for="firstName">{{ getLabel('firstName') }}</label>
<input type="text" id="firstName" name="firstName" [(ngModel)]="profile.firstName" (blur)="setInitials()">
</div>
<div class="input-group col-2">
<label for="lastNamePrefix">{{ getLabel('lastNamePrefix') }}</label>
<input type="text" id="lastNamePrefix" name="lastNamePrefix" [(ngModel)]="profile.lastNamePrefix">
</div>
<div class="input-group col-6">
<label for="lastName">{{ getLabel('lastName') }}</label>
<input type="text" id="lastName" name="lastName" [(ngModel)]="profile.lastName" (blur)="setInitials()">
</div>
</div>
<div class="row">
<div class="input-group col-4">
<label for="marriedName">{{ getLabel('marriedName') }}</label>
<input type="text" id="marriedName" name="marriedName" [(ngModel)]="profile.marriedName">
</div>
<div class="input-group col-2">
<label for="initials">{{ getLabel('initials') }}</label>
<input type="text" id="initials" name="initials" [(ngModel)]="profile.initials">
</div>
<div class="input-group col-3">
<label for="gender">{{ getLabel('gender') }}</label>
<select name="gender" id="gender" [(ngModel)]="profile.gender">
<option *ngFor="let type of genders" [value]="type.value">{{ type.text }}</option>
</select>
</div>
<div class="input-group col-3">
<label for="dateOfBirth">{{ getLabel('dateOfBirth') }}</label>
<input type="text" id="dateOfBirth" name="dateOfBirth" [value]="profile.dateOfBirth | date:'dd-MM-yyyy'" readonly tabindex="-1">
</div>
</div>
<div class="row">
<div class="input-group col-8">
<label for="street">{{ getLabel('street') }}</label>
<input type="text" id="street" name="street" [(ngModel)]="profile.street">
</div>
<div class="input-group col-2">
<label for="houseNumber" [attr.abbreviation]="getLabelAbbreviation('houseNumber')">{{ getLabel('houseNumber') }}</label>
<input type="text" id="houseNumber" name="houseNumber" [(ngModel)]="profile.houseNumber">
</div>
<div class="input-group col-2">
<label for="houseExtension" [attr.abbreviation]="getLabelAbbreviation('houseExtension')">{{ getLabel('houseExtension') }}</label>
<input type="text" id="houseExtension" name="houseExtension" [(ngModel)]="profile.houseExtension">
</div>
</div>
<div class="row">
<div class="input-group col-4">
<label for="zipCode">{{ getLabel('zipCode') }}</label>
<input type="text" id="zipCode" name="zipCode" [(ngModel)]="profile.zipCode">
</div>
<div class="input-group col-8">
<label for="city">{{ getLabel('city') }}</label>
<input type="text" id="city" name="city" [(ngModel)]="profile.city">
</div>
</div>
<div class="row">
<div class="input-group col-4">
<label for="email">{{ getLabel('email') }}</label>
<input type="email" id="email" name="email" [ngModel]="profile.email" readonly tabindex="-1">
</div>
<div class="input-group col-4">
<label for="telephoneNr" [attr.abbreviation]="getLabelAbbreviation('telephoneNr')">{{ getLabel('telephoneNr') }}</label>
<input type="tel" id="telephoneNr" name="telephoneNr" [(ngModel)]="profile.telephoneNr">
</div>
<div class="input-group col-4">
<label for="mobileNr" [attr.abbreviation]="getLabelAbbreviation('mobileNr')">{{ getLabel('mobileNr') }}</label>
<input type="tel" id="mobileNr" name="mobileNr" [(ngModel)]="profile.mobileNr">
</div>
</div>
<div class="profile-save">
<button class="button profile-save-button" type="submit" [disabled] = "!form.dirty" >Wijzigingen opslaan</button>
<span *ngIf="saving" class="fa fa-refresh fa-spin fa-2x profile-save-icon"></span>
</div>
</section>
<section class="settings-account-form-photo">
<img [src]="currentPicture()" class="photo-upload" alt="">
<input #fileinput type="file" name="photo" id="photo" class="photo-input" accept="image/*"
(change)="loadImage($event, form)">
<div class="photo-selection">
<span [ngClass]="{'delete-picture': deletePicture}">
</span>
</div>
<div class="form-photo-actions">
<button type="button" class="button button-inline photo-button" (click)="fileinput.click()">Kies foto</button>
<button type="button" class="button button-inline photo-button" (click)="toggleRemove(form)">
{{ !deletePicture ? 'Verwijder foto' : 'Behoud foto' }}
</button>
</div>
<input type="hidden" name="picture" id="picture" ngModel="{{ profile.picture }}">
<input type="hidden" name="uploadPicture" id="uploadPicture" [ngModel]="profile.uploadPicture">
</section>
</div>
</form>
</main>
<footer class="settings-account-footer">
Lees hier nogmaals de
<a href="https://www.vital10.nl/algemene-voorwaarden-deelnemers/" [target]="inApp.getATagBlankTarget()" rel="noopener noreferrer" class="link-clear">algemene voorwaarden</a> en de
<a href="https://www.vital10.nl/privacyverklaring/" [target]="inApp.getATagBlankTarget()" rel="noopener noreferrer" class="link-clear">Privacyverklaring</a> van MijnVital10.
</footer>
</div>
</app-vital10-page>
And this is the javascript file:
import { Component, NgZone, OnDestroy, OnInit, ViewChild, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Subscription } from 'rxjs';
import { AccountProfile } from '../../interfaces/profile/account-profile.interface';
import { errors } from '../../measurement/measurement-form/measurement-form.animations';
import { HealthAPIService } from '../../shared/health-api/health-api.service';
import { InAppService } from 'app/shared/in-app/in-app.service';
import { FormCanDeactivate } from 'app/shared/form/form-can-deactivate/form-can-deactivate';
@Component( {
selector : 'app-settings-account',
templateUrl: './settings-account.component.html',
animations : [ errors ],
} )
export class SettingsAccountComponent extends FormCanDeactivate implements OnInit, OnDestroy {
private profileSubscription: Subscription;
profile: AccountProfile;
currentProfile: AccountProfile;
selectedFileName: string;
deletePicture: boolean;
saving: boolean;
formErrors: { header: string; messages: string[] }[];
innerWidth = window.innerWidth;
@ViewChild( 'form', {static: false} )
form: NgForm;
genders: { value: string; text: string }[];
labels = {
firstName : {
nl: 'Voornaam'
},
lastNamePrefix: {
nl: 'Tussenv.'
},
lastName : {
nl: 'Achternaam'
},
marriedName : {
nl: 'Meisjesnaam'
},
initials : {
nl: 'Initialen'
},
gender : {
nl: 'Geslacht'
},
dateOfBirth : {
nl: 'Geboren'
},
street : {
nl: 'Straat'
},
houseNumber : {
nl: 'Huis nr.',
nl_abbreviation: 'Nr.'
},
houseExtension: {
nl: 'Toevoeging',
nl_abbreviation: 'Ext.'
},
zipCode : {
nl: 'Postcode'
},
city : {
nl: 'Stad'
},
email : {
nl: 'Email'
},
telephoneNr : {
nl: 'Telefoon nr.',
nl_abbreviation: 'Tel. nr.'
},
mobileNr : {
nl: 'Mobiel nr.',
nl_abbreviation: 'Mobiel nr.'
},
photo : {
nl: 'Foto'
}
};
inApp: InAppService;
constructor(
private healthAPI: HealthAPIService,
private ngZone: NgZone,
private cd: ChangeDetectorRef,
private inAppService: InAppService
) {
super();
this.saving = false;
this.genders = [
{
value: 'Unknown',
text : 'Onbekend'
},
{
value: 'Male',
text : 'Man'
},
{
value: 'Female',
text : 'Vrouw'
}
];
this.inApp = inAppService;
this.cd.markForCheck();
}
ngOnInit() {
this.innerWidth = window.innerWidth;
this.profileSubscription = this.healthAPI.getProfile()
.subscribe( ( profile: AccountProfile ) => {
this.profile = profile;
this.deletePicture = false;
if ( this.form ) {
if ( this.form.controls.picture ) {
this.form.controls.picture.setValue( this.profile.picture );
//console.log(this.cd.detectChanges());
}
if ( this.form.controls.uploadPicture ) {
this.form.controls.uploadPicture.setValue( false );
}
}
} );
}
public ngOnDestroy(): void {
if ( this.profileSubscription ) {
this.profileSubscription.unsubscribe();
}
}
currentPicture() {
let pictureValue;
if ( this.form && this.form.controls.picture ) {
pictureValue = this.form.controls.picture.value;
} else {
pictureValue = this.profile.picture;
}
if ( pictureValue ) {
return 'data:image/png;base64,' + pictureValue;
}
return 'assets/img/default_avatar.png';
}
clearErrors() {
this.initErrorLog();
}
initErrorLog() {
this.formErrors = [];
}
// Parses errorlog that comes from the server into a format that's easily used by the template
parseErrorLog( errorLog ) {
// Reset errorlog
this.initErrorLog();
for ( const header in errorLog ) {
if ( Object.prototype.hasOwnProperty.call( errorLog, header ) ) {
const inputError = {
header,
messages: errorLog[ header ]
};
this.formErrors.push( inputError );
}
}
}
save() {
this.saving = true;
const commonFunc = () => {
this.ngZone.runOutsideAngular( () => {
window.setTimeout( () => {
this.ngZone.run( () => {
this.saving = false; // Wait one second for clarity
} );
}, 1000 );
} );
};
this.healthAPI.putProfile( this.form.value )
.subscribe(
res => {
this.formErrors = undefined;
commonFunc();
},
err => {
this.parseErrorLog( JSON.parse( err._body ) );
commonFunc();
}
);
}
getInitials(): string {
return this.profile.firstName.charAt( 0 )
.toUpperCase() + '.' + this.profile.lastName.charAt( 0 )
.toUpperCase() + '.';
}
setInitials() {
if ( this.profile.firstName && this.profile.lastName ) {
this.profile.initials = this.getInitials();
}
}
toggleRemove() {
this.deletePicture = !this.deletePicture;
if ( this.deletePicture ) {
this.selectedFileName = null;
this.form.controls.picture.setValue( null );
this.form.controls.uploadPicture.setValue( true );
} else {
this.form.controls.picture.setValue( this.profile.picture );
this.form.controls.uploadPicture.setValue( false );
}
}
loadImage( event: Event ) {
this.cd.markForCheck();
if (!this.cd['destroyed']) {
console.log(this.cd.detectChanges());
}
const fileInput = event.target as HTMLInputElement;
this.selectedFileName = fileInput.files[ 0 ].name;
if ( fileInput.files[ 0 ] ) {
const fileReader = new FileReader();
//console.log(this.cd.markForCheck());
fileReader.addEventListener( 'load', () => {
const base64Img = (fileReader.result as string).substring((fileReader.result as string).indexOf( ',' ) + 1 );
this.form.controls.picture.setValue( base64Img );
this.form.controls.uploadPicture.setValue( true );
// console.log('image');
} );
fileReader.readAsDataURL( fileInput.files[ 0 ] );
this.cd.markForCheck();
} else {
this.form.controls.picture.setValue( this.profile.picture );
this.form.controls.uploadPicture.setValue( false );
}
}
getLabel( label: string, locale: string = 'nl' ): string {
const normalizedLabel = label.charAt( 0 )
.toLowerCase() + label.slice( 1 );
if ( !this.labels[ normalizedLabel ] ) {
throw new Error( `Unknown label ${normalizedLabel} requested in settings-account component` );
}
return this.labels[ normalizedLabel ][ locale ];
}
getLabelAbbreviation( label: string, locale: string = 'nl' ): string {
const normalizedLabel = label.charAt( 0 )
.toLowerCase() + label.slice( 1 );
if ( !this.labels[ normalizedLabel ] ) {
throw new Error( `Unknown label ${normalizedLabel} requested in settings-account component` );
}
if ( !this.labels[ normalizedLabel ][ locale + '_abbreviation' ] ) {
throw new Error( `Unknown label ${normalizedLabel} requested in settings-account component` );
}
return this.labels[ normalizedLabel ][ locale + '_abbreviation' ];
}
}
And so this is the functionality for uploading new file:
loadImage( event: Event ) {
this.cd.markForCheck();
if (!this.cd['destroyed']) {
console.log(this.cd.detectChanges());
}
const fileInput = event.target as HTMLInputElement;
this.selectedFileName = fileInput.files[ 0 ].name;
if ( fileInput.files[ 0 ] ) {
const fileReader = new FileReader();
//console.log(this.cd.markForCheck());
fileReader.addEventListener( 'load', () => {
const base64Img = (fileReader.result as string).substring((fileReader.result as string).indexOf( ',' ) + 1 );
this.form.controls.picture.setValue( base64Img );
this.form.controls.uploadPicture.setValue( true );
// console.log('image');
} );
fileReader.readAsDataURL( fileInput.files[ 0 ] );
this.cd.markForCheck();
} else {
this.form.controls.picture.setValue( this.profile.picture );
this.form.controls.uploadPicture.setValue( false );
}
}
That with changing file the form will be dirty.
Thank you
You mean like this:
loadImage( event: Event ) {
this.cd.markForCheck();
if (!this.cd['destroyed']) {
// console.log(this.cd.detectChanges());
}
const fileInput = event.target as HTMLInputElement;
this.selectedFileName = fileInput.files[ 0 ].name;
if ( fileInput.files[ 0 ] ) {
const fileReader = new FileReader();
fileReader.onload = _event => {
console.log(this.cd.markForCheck());
};
fileReader.addEventListener( 'load', () => {
const base64Img = (fileReader.result as string).substring((fileReader.result as string).indexOf( ',' ) + 1 );
this.form.controls.picture.setValue( base64Img );
this.form.controls.uploadPicture.setValue( true );
// console.log('image');
} );
fileReader.readAsDataURL( fileInput.files[ 0 ] );
this.cd.markForCheck();
} else {
this.form.controls.picture.setValue( this.profile.picture );
this.form.controls.uploadPicture.setValue( false );
}
}
I also tried like this:
loadImage( event: Event ) {
const fileInput = event.target as HTMLInputElement;
this.selectedFileName = fileInput.files[ 0 ].name;
if ( fileInput.files[ 0 ] ) {
const fileReader = new FileReader();
fileReader.onload = (_event: Event ) => {
const base64Img = (fileReader.result as string).substring((fileReader.result as string).indexOf( ',' ) + 1 );
this.form.controls.picture.setValue( base64Img );
this.form.controls.uploadPicture.setValue( true );
this.cd.markForCheck();
};
fileReader.readAsDataURL( fileInput.files[ 0 ] );
this.cd.markForCheck();
} else {
this.form.controls.picture.setValue( this.profile.picture );
this.form.controls.uploadPicture.setValue( false );
}
}
I have it now like this:
loadImage( event: Event ) {
const fileInput = event.target as HTMLInputElement;
this.selectedFileName = fileInput.files[ 0 ].name;
if ( fileInput.files[ 0 ] ) {
const fileReader = new FileReader();
fileReader.onload = (_event: Event ) => {
const base64Img = (fileReader.result as string).substring((fileReader.result as string).indexOf( ',' ) + 1 );
if (this.form.controls.picture.value !== base64Img) {
this.form.controls.picture.setValue( base64Img );
this.form.controls.uploadPicture.setValue( true );
this.cd.markForCheck();
}
};
fileReader.readAsDataURL( fileInput.files[ 0 ] );
this.cd.markForCheck();
} else {
this.form.controls.picture.setValue( this.profile.picture );
this.form.controls.uploadPicture.setValue( false );
}
}
But nothing changed
回答1:
I think the this.form.controls.picture.setValue( base64Img ); will override your image property in form and mark it as dirty.
Maybe you can prevent this by checking if base64Img is the same like your actual picture value.
(The same with UploadPicture)
回答2:
You might need to use reader.onload event like this
<input
#file
type="file"
accept="image/*"
(change)="previewImage($event)"
/>
Then in your component
public previewImage(event) {
const reader = new FileReader();
const file = event.target.files[0];
reader.readAsDataURL(file);
reader.onload = _event => {
// need to run CD since file load runs outside of zone
// do something else
this.cd.markForCheck();
};
来源:https://stackoverflow.com/questions/57708641/change-detection-doesnt-work-with-file-upload