David Anthony Acosta
David Anthony Acosta

Reputation: 4890

ControlValueAccessor not detecting changes - Angular 2

I've implemented ControlValueAccessor as seen in a tutorial I am following. However, it appears that the component where I am implementing the controlvalueaccessor is not detecting any changes to the ngModel. Did I miss something?

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

@Component({
    selector: 'app-counter',
    templateUrl: './counter.component.html',
    styleUrls: ['./counter.component.css'],
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => CounterComponent),
            multi: true
        }
    ]
})
export class CounterComponent implements OnInit, ControlValueAccessor {

    constructor() { }

    counterValue = 0;

    writeValue(value: any) {
        console.log('writeValue: ', value);
    }

    registerOnChange(fn: any) {
        console.log('on change: ', fn);
    }

    registerOnTouched(fn: any) {
        console.log('on touch: ', fn);
    }

    increment() {
        this.counterValue++;
    }

    decrement() {
        this.counterValue--;
    }

    ngOnInit() {
    }

}

template:

<button (click)="increment()">Increment</button>
{{ counterValue }}
<button (click)="decrement()">Decrement</button>

and:

<app-counter name="counter" [(ngModel)]="counter"></app-counter>

<p>ngModel: {{ counter }}</p>

At this point in the tutorial, incrementing/decrementing the counter should be triggering the controlvalueaccessor methods I have defined but it isn't.

Upvotes: 5

Views: 2981

Answers (1)

Alexandre Annic
Alexandre Annic

Reputation: 10738

You must register the change. Do something like that :

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

@Component({
    selector: 'app-counter',
    template: `
        <button (click)="increment()">Increment</button>
        {{ counterValue }}
        <button (click)="decrement()">Decrement</button>
    `,
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => CounterComponent),
            multi: true
        }
    ]
})
export class CounterComponent implements ControlValueAccessor {

    constructor() { }

    counterValue = 0;

    writeValue(value: any) {
        this.counterValue = value;
    }

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

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


    registerOnTouched(fn: any) {
        console.log('on touch: ', fn);
    }

    increment() {
        this.counterValue++;
        this.onChangeCallback(this.counterValue);
    }

    decrement() {
        this.counterValue--;
        this.onChangeCallback(this.counterValue);
    }    
}

Or a more elegant version using setter:

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

@Component({
    selector: 'app-counter',
    template: `
        <button (click)="increment()">Increment</button>
        {{ counterValue }}
        <button (click)="decrement()">Decrement</button>
    `,
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => CounterComponent),
            multi: true
        }
    ]
})
export class CounterComponent implements ControlValueAccessor {

    private _counterValue = 0;

    get counterValue(): number {
        return this._counterValue;
    }

    set counterValue(value: number) {
        this._counterValue = value;
        this.onChangeCallback(value);
    }

    writeValue(value: any) {
        this.counterValue = value;
    }

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

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


    registerOnTouched(fn: any) {
        console.log('on touch: ', fn);
    }

    increment() {
        this._counterValue++;
    }

    decrement() {
        this._counterValue--;
    }
}

PS: Don't forget to update your internal model with incoming values in writeValue() as shown in both examples.

Upvotes: 4

Related Questions