angularangular2-hostbinding

HostBinding to value works only the first time


I have a ControlValueAccessor directive that performs value formatting but its @HostBinding('value') works only the first time.

@Component({
  selector: 'my-app',
  template: `
  Value: {{value}}<br/>
  <input type="text" uppercase [(ngModel)]="value">
  <button type="button" (click)="reset()">Reset</button>
  `,
})
export class AppComponent {
  value = 'Angular ' + VERSION.major;

  reset() {
    this.value = 'Reset';
  }
}

@Directive({
  selector: 'input[uppercase]',
  providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => UpperCaseInputDirective), multi: true }],
})
export class UpperCaseInputDirective implements ControlValueAccessor {

  @HostBinding('value') lowerValue = '';

  ...
}

Please see full example at https://stackblitz.com/edit/angular-ivy-vfahu3?file=src%2Fapp%2Fapp.component.ts

  1. When I click Reset button for the first time, is sets ngModel to "Reset" and text of <input> to "reset" as expected.
  2. Then I edit the input value to "Test" and value is propagated back to model as "TEST" as expected.
  3. When I click Reset button for the second time, ngModel is set to "Reset" as expected but text of <input> stays with "Test". I would expect it to change to "reset" as it did in point 1.

Can you explain me this behavior? Any clues?

Side note: I know the example above could be implemented by other means, but it just a simplification of case where I am using <input type="datetime-local> and the directive translates input value to ISO format.


Solution

  • I realized that the value was not updated due to Angular optimization - it does not propagate value to DOM if it thinks that value did not change. In point 1 it changes bound variable to value "Reset" and in point 3 it "thinks" the value is still "Reset" and therefore it does not update DOM.

    When I update bound variable this.lowerValue in onChange listener (point 2) then Reset button works event in point 3:

      @HostListener('change', ['$event']) onChange(event: Event) {
        if (event.target instanceof HTMLInputElement) {
          this.lowerValue = event.target.value?.toLocaleUpperCase();
        }
        this._onChangeCallback(this.lowerValue);
      }