最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

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

programmeradmin3浏览0评论

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:

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)

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)

Share Improve this question edited Dec 20, 2018 at 8:35 Dimanoid 7,2897 gold badges47 silver badges60 bronze badges asked Feb 21, 2017 at 15:58 StefanStefan 8871 gold badge12 silver badges32 bronze badges 2
  • 1 Please pay attention to the tag excerpts that show when you're adding tags. angularjs is for AngularJS 1.x. angular2 is for Angular 2+. – Heretic Monkey Commented Feb 21, 2017 at 16:52
  • 1 HTML5 forms have a very specific defined list of elements that are considered to be part of the form tree; elements not on this list, including div, span, table, and others, including custom elements, will not be handled as part of the HTML5 form. you might be able to accomplish this if your component used output rather than div for it's template, but that may not work as you expect either. Basically, this isn't built into the spec. – Claies Commented Feb 21, 2017 at 16:55
Add a comment  | 

4 Answers 4

Reset to default 8

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

Thanks Stefan for pointing me in the right direction.

Unfortuantely Renderer (which has invokeElementMethod()) has recently been deprecated in favor or Renderer2 (which does not have that method)

So the following worked for me

this.elementRef.nativeElement.dispatchEvent(new CustomEvent('change', { bubbles: true }));

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");
    }
}

It seems that change event is not fired on form when you call ControlValueAccessor onChange callback (callback passed in registerOnChange function), but valueChanges observable (on the whole form) is triggered.

Instead of:

 ...
 <form (change)="onChange()">
 ...

you can try to use:

this.form.valueChanges
  .subscribe((formValues) => {
    ...
  });

Of course, you must get proper form reference in your component.

发布评论

评论列表(0)

  1. 暂无评论