top of page

Examples of losing the context of ‘this’ in Angular

Writer: ZsoltZsolt
If you are a Medium member, please read here
— I’m more of a practical guy. — Have you ever needed to know this? actual interview quotes

Losing track of this was a common headache pre-ES6, then came arrow functions and it became a thing of the past. So why would a new JavaScript developer bother learning the difference between call contexts? (or inheritance, hoisting, OO principles etc.) I’ll show why, but before that a quick recap on the definitions:


'this’ in functions:

The value of this in JavaScript depends on how a function is invoked (runtime binding), not how it is defined. When a regular function is invoked as a method of an object (obj.method()), this points to that object.


‘this’ in arrow functions:

Arrow functions differ in their handling of this: they inherit this from the parent scope at the time they are defined. This behavior makes arrow functions particularly useful for callbacks and preserving context.

After this preamble comment if you see the problem with this simple Angular component (other than my coding style ofc):

export type State = 'FAILED' | 'PERFECT';

@Component({
  ...
  template: `
      <input type="number" [formControl]="value"/> 
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SelfValidatingFieldComponent {
  protected value = new FormControl(1, this.controlValidator);

  private innerStateThing: State = 'PERFECT';

  private controlValidator(control: AbstractControl): ValidationErrors | null {
    if (control.value < 2 && this.innerStateThing === 'PERFECT') {
      return null;
    }
    this.innerStateThing = 'FAILED';
    return { selfValidating: { value: 'oh no, this failed' } };
  }
}

It’s a simple input field that has a validator. I defined the validator so it’s a one-off field. If you made it invalid once, it stays that way, because reasons. Needless to say this code won’t work well.

innerStateThing of undefined?

When we debug into it, it’s bad and worse. Our validator is called twice, first as this.validator(this) from FormControl2 class inside Angular and then from a util pure function.

function executeValidators(control, validators) {
    return validators.map((validator) => validator(control));
}

The first time this.innerStateThing will see this as FormControl2, the second time as undefined . Neither is what we wanted. Why is this the case? new FormControl(1, this.controlValidator) will assign our validator to the FormControl object in some way, and as such when invoked it’ll recieve that object as this . As for the second call

When invoked as a standalone function (not attached to an object: func()), this typically refers to the global object (in non-strict mode) or undefined (in strict mode).

Let’s fix the validator and break it again. Feel free to brag if now you see the new problem (highlighted the changes in our class):

@Component({
  ...
  template: `
    <app-self-validating-field [customValidator]="customValidator"></app-self-validating-field>
  `,
})
export class App {
  private readonly treshold = 1;

  customValidator(val: number): boolean {
    return val < this.treshold;
  }
}

...
export class SelfValidatingFieldComponent {
  customValidator = input.required<CustomValidator>(); // <== required custom validation

  protected value = new FormControl(1);

  private innerStateThing: State = 'PERFECT';

  ngOnInit() {
    this.value.addValidators([this.controlValidatorFactory()]);
  }

  private controlValidatorFactory(): ValidatorFn { // <== factory returning a arrow function, context is SelfValidatingField
    return (control: AbstractControl): ValidationErrors | null => {
      if (
        control.value < 2 &&
        this.customValidator()(control.value) && // <== using the custom validator
        this.innerStateThing === 'PERFECT'
      ) {
        return null;
      }
      this.innerStateThing = 'FAILED';
      return { selfValidating: { value: 'oh no, this failed' } };
    };
  }
}

Now controlValidatorFactory would work, but there’s an error again.

It’s maybe even better highlighted here. I assigned a function from App to a member of SelfValidatingField, and since it is a simple function, when calling it back from SelfValidatingField, although its definition is in App, “this depends on how a function is invoked” and so this will be undefined.


Fun fact: if I had used @Input instead of signal input this would be SelfValidatingFieldComponent.


Bottomline is, you just don’t know where your function will be called from, so either:

  • make it a pure

  • use property holding arrow function `customValidator = (val: number): boolean => {`

  • use factory (← look, design patterns in the wilderness!)


And also send this article to a friend that says they’re more, like, practical.

Comments


SIGN UP AND STAY UPDATED!

Thanks for submitting!

© 2019 ZD Engineering. Proudly created with Wix.com

bottom of page