Change detection doesnt work with file upload

孤者浪人 提交于 2019-12-13 03:48:58

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!