Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2, custom control is never marked as dirty

I created a custom control to which I provide the value programmatically because it an image-editor based on cropper.js and the user "uploads" an image via an normal fileinput and I then use the File API to assign the image to my custom control.

here's the code (I removed some cropper.js specific code for readability)

import {
   Component,
   OnInit,
   ViewChild,
   ElementRef,
   ViewEncapsulation,
   forwardRef
} from '@angular/core';

import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import * as Cropper from 'cropperjs';
import { Photo } from '../entities/photo';

@Component({
   selector: 'image-edit',
   templateUrl: 'image-edit.component.html',
   styleUrls: [
       'image-edit.component.scss',
       '../../../node_modules/cropperjs/dist/cropper.min.css'
],
encapsulation: ViewEncapsulation.None,
providers: [
    {
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => ImageEditComponent),
        multi: true
    }
   ]
})

export class ImageEditComponent implements OnInit, ControlValueAccessor {

    get value() {
        return this.cropperImage.nativeElement.src;
    }

    set value(v: string) {
        this.cropperImage.nativeElement.src = v;
    }

    public onTouch: () => void;
    public onChange: (_: any) => void;

    @ViewChild('cropperImage')
    private cropperImage: ElementRef;

    private isDisabled: boolean;

    // cropper.js
    private cropper: Cropper;
    private cropperOptions: Cropper.CropperOptions = {};

    public ngOnInit(): void {

        // init cropper.js
    }

    public writeValue(obj: any): void {

        if (obj) {
            this.value = obj;
            this.initializeCropperOrReplaceImage();
        }
    }

    public registerOnChange(fn: any): void {
        this.onChange = fn;
    }

    public registerOnTouched(fn: any): void {
        this.onTouch = fn;
    }

    public setDisabledState(isDisabled: boolean): void {
       this.isDisabled = isDisabled;
    }

    private initializeCropperOrReplaceImage(): void {

        if (!this.isCropperInitialized) {
            this.cropper = new Cropper(this.cropperImage.nativeElement, this.cropperOptions);
            this.isCropperInitialized = true;
        }
        else {
            this.cropper.replace(this.cropperImage.nativeElement.src);
        }

        URL.revokeObjectURL(this.cropperImage.nativeElement.src);
    }
}

here's the template

<image-edit [hidden]="!photo.data" #photoData="ngModel" name="photoData" [(ngModel)]="photo.data" validateMinImageDimensions minWidth="300" minHeight="400">
 </image-edit>

Unfortunately my control is never dirty or touched. I tried to extend AbstractForm and set dirty myself but it didnt work. It remains undirty and untouched.

Any ideas what I'm doing wrong?

EDIT

I removed the call to onTouch and onChange in writeValue because according to @n00dl3 answer and comment (see there) it is not a good solution.

like image 549
Arikael Avatar asked Apr 05 '17 07:04

Arikael


1 Answers

You never call this.onTouch() in your component, so your input cannot be marked as touched neither dirty....

So to clarify, you need to manually call this.onTouch() when you consider the data has been changed by the user (not by the model), so you should avoid calling onTouch inside the writeValue method.

Instead you should listen to the eventual events that your user may trigger when dealing with (without necessarily changing) the value you are handling (maybe just after a focus on a regular input element).

I guess there is a way to know that the user has cropped the image inside your Cropper library like passing a complete callback, in the initialization options. You need to call the onTouch method from this callback or whenever you think your input should be marked as touched (maybe clicking on the image, or even just hovering it).

like image 121
n00dl3 Avatar answered Oct 15 '22 21:10

n00dl3