Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to trigger change() in a angular form by a custom control without an input

I do want to create a custom control which does not include any input. Whenever the control changes, I do want to save the complete form.

Our current approach uses the form-changed-event like this:

 <form #demoForm="ngForm" (change)="onChange()">
      <custom-input name="someValue" [(ngModel)]="dataModel">
      </custom-input>
 </form>

As you can see, we use the "change"-event to react to any change in the form. This works fine as long as we have inputs, checkboxes, ... as controls.

But our custom control does only exist out of a simple div we can click on. Whenever I click on the div the value of the control is increased by 1. But the "change"-event of the form is not fired. Do I somehow have to link my custom control to the form? Or are there any events which need to be fired?

import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';

@Component({
    selector: 'custom-input',
    template: `<div (click)="update()">Click</div>`,
    providers: [{
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => CustomInputComponent),
        multi: true
    }]
})
export class CustomInputComponent implements ControlValueAccessor {

    private onTouchedCallback: () => void = () => {};
    private onChangeCallback: (_: any) => void = () => {};

    update(){
      this.value++;
    }

    get value(): any {
        return this.innerValue;
    };

    set value(v: any) {
        console.log("Change to");
        if (v !== this.innerValue) {
            this.innerValue = v;
            this.onChangeCallback(v);
        }
    }

    writeValue(value: any) {
        if (value !== this.innerValue) {
            this.innerValue = value;
        }
    }

    registerOnChange(fn: any) {
        this.onChangeCallback = fn;
    }

    registerOnTouched(fn: any) {
        this.onTouchedCallback = fn;
    }
}

I've created a plunker to demonstrate the problem: https://plnkr.co/edit/ushMfJfcmIlfP2U1EW6A

Whenever you click on "Click" the model-value is increased, but there is no output on the console, as the change-event is not fired... (There is a console.log linked to the change-event)

like image 942
Stefan Avatar asked Feb 21 '17 15:02

Stefan


People also ask

How does Angular determine changes in form control?

In Angular we have observables which subscribe to form value changes. what you have to do is, subscribe to the form instance and detect any changes to the form. Suppose you don't want to include save button on your form. whenever user change any thing in the form save data to the server.

How do you use Selectcontrolvalueaccessor?

How to use select controls with form directives. To use a select in a template-driven form, simply add an ngModel and a name attribute to the main <select> tag. In reactive forms, you'll also want to add your form directive ( formControlName or formControl ) on the main <select> tag.

How is MatFormFieldControl implemented?

Implementing MatFormFieldControl We then add a provider to our component so that the form field will be able to inject it as a MatFormFieldControl (as we did before with ControlValueAccessor ). ]export class FormRichEditorComponent implements OnInit, ControlValueAccessor, MatFormFieldControl<any>) {...


2 Answers

Thanks for your replies.

Finally I found the following solution to this problem: As Claies mentioned in the comment, my custom component does not fire the change event. Therfore the form does never know about the change. This has nothing todo with angular, but as said is the expected behaviour of a input/form.

The easiest solution is to fire the change-event in the customcontrol when a change happens:

constructor(private element: ElementRef, private renderer: Renderer) {
}

public triggerChanged(){
    let event = new CustomEvent('change', {bubbles: true});
    this.renderer.invokeElementMethod(this.element.nativeElement, 'dispatchEvent', [event]);
}

That's it, whenever I called "onControlChange(..)" in my custom component, then I fire this event afterward.

Be aware, that you need the Custom-Event-Polyfill to support IE! https://www.npmjs.com/package/custom-event-polyfill

like image 141
Stefan Avatar answered Sep 21 '22 09:09

Stefan


You need to emit the click event of div to its parent. so that you can handle the event.

Plunker Link

Parent component:

import { Component, forwardRef, Output, EventEmitter } from '@angular/core'; // add output and eventEmitter
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';

@Component({
 selector: 'custom-input',
 template: `<div (click)="update($event)">Click</div>`,
 providers: [{
 provide: NG_VALUE_ACCESSOR,
 useExisting: forwardRef(() => CustomInputComponent),
 multi: true
}]
})

export class CustomInputComponent implements ControlValueAccessor {

  private onTouchedCallback: () => void = () => {};
  private onChangeCallback: (_: any) => void = () => {};
  @Output() clickEvent = new EventEmitter(); // add this

  update(event){
    this.value++;
    this.clickEvent.emit(event); // emit the event on click event
  }

  get value(): any {
      return this.innerValue;
  };
}

child component:

//our root app component
import {Component} from '@angular/core'

@Component({
    selector: 'demo-app',
    template: `
        <p><span class="boldspan">Model data:</span> {{dataModel}}</p>
        <form #demoForm="ngForm">
          <custom-input name="someValue" 
                      [(ngModel)]="dataModel" (clickEvent) = onChange()> // handling emitted event here
            Write in this wrapper control:
          </custom-input>
        </form>`
})
export class AppComponent {
    dataModel: string = '';

    public onChange(){
      console.log("onChangeCalled");
    }
}
like image 41
amansoni211 Avatar answered Sep 21 '22 09:09

amansoni211