1
votes

Update: Link to Plunker

Running into a weird problem. I'm using Angular 2.4.1. When the component initially loads, the value setter runs and onChangeCallback correctly fires. However, I am console-logging onChangeCallback, and after it is initially set, it is being replaced with an anonymous function.

Here are the results of the console.log:

  1. function (_) { }
  2. function (newValue) { dir.viewToModelUpdate(newValue); control.markAsDirty(); control.setValue(newValue, { emitModelToViewChange: false }); }

  3. function (_) { }

The first happens on page load, immediately followed by 2. I'm assuming Angular is handling the token and implementing ControlValueAccessor correctly at this point. When I then make a change on the select element, onChangeCallback is being called as the anonymous function again without the ControlValueAccessor additions.

Any idea on how to fix this? It is only this form-control that I'm having this problem with.

import { Component, Input, Output, EventEmitter, forwardRef, ViewChild, ElementRef, Renderer, ChangeDetectorRef, OnChanges } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'select-resizable',
  template:`
    <select #mainSelectElement [(ngModel)]="value" (change)="updateSelected($event.target.value)" style="width: initial;">
      <ng-content></ng-content>
    </select>
    <select class="usa-sr-only" aria-hidden="true" role="presentation" #hiddenSelectElement style="width: initial;">
      <option>{{value}}</option>
    </select>
  `,
  providers: [
    { provide: NG_VALUE_ACCESSOR, useClass: forwardRef(() => SelectResizableComponent), multi: true }
  ]
})
export class SelectResizableComponent implements ControlValueAccessor {

  @ViewChild('hiddenSelectElement') hiddenSelectElement: ElementRef;
  @ViewChild('mainSelectElement') mainSelectElement: ElementRef; 

  private _selected: any;

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

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

  set value(val: any) {
    this._selected = val;
    this.onChangeCallback(val);
    console.log(this.onChangeCallback);
  }

  constructor(private renderer: Renderer, private ref: ChangeDetectorRef) {}

  updateSelected(event) {
    this.ref.detectChanges();
    const width = (this.hiddenSelectElement.nativeElement.clientWidth * 1.02) + 'px';
    this.renderer.setElementStyle(this.mainSelectElement.nativeElement, 'width', width);
  }

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

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

  registerOnTouched(fn: any) {
    this.onTouchedCallback = fn;
  }
}
1
Are you using the SelectResizableComponent more than once? The console.log could be running from a second use of the component. - Adam
No, it is only being used one time. Each time I select a new item, the setter is called and it consoles an empty callback. You can play with it at the plunker I just added. - Colin Alford
I setup a getter and setter to see if the function was being changed, and it's not. plnkr.co/edit/kE2wDNAsiczDmRqCXm67?p=preview I don't have an answer for you, but there are two instances being created for some reason, that's somewhere to start investigating. - Adam

1 Answers

2
votes
  providers: [
    { provide: NG_VALUE_ACCESSOR, useClass: forwardRef(() => SelectResizableComponent), multi: true }
  ]

The problem was right there. I provided the token with useClass rather than useExisting. So after the component was instantiated, a new value was created from the components class for the NG_VALUE_ACCESSOR token.

Thanks, Adam, for spotting that I had two values.