1

I'm pretty new to TypeScript and recently discovered an expresssion of the following form:

class AsyncPromisePipeComponent{

  private resolve: Function|null = null;
  (...)
  this.resolve !('hi there!');
}

I'm struggling to understand this.resolve !('hi there!'), since this.resolve refers to a function. Is this a function call without parentheses? What exactly does the ! stand for (since you can't negate a string, can you)?

I found this in the official Angular Doc for the AsyncPipe. Perhaps just a typo. Otherwise: can someone explain the meaning of this.resolve !('hi there!');?

2 Answers 2

2

Theat's the non-null assertion operator. It tells the compiler that the expression before it is neither undefined nor null.

Here's the whole example for for context:

export class AsyncPromisePipeComponent {
  greeting: Promise<string>|null = null;
  arrived: boolean = false;

  private resolve: Function|null = null;

  constructor() { this.reset(); }

  reset() {
    this.arrived = false;
    this.greeting = new Promise<string>((resolve, reject) => { this.resolve = resolve; });
  }

  clicked() {
    if (this.arrived) {
      this.reset();
    } else {
      this.resolve !('hi there!');
      this.arrived = true;
    }
  }
}

As you can see, this.resolve is typed as Function|null. We can't just call it like this.resolve('hi there!') or TypeScript would complain because it could be null.

Adding a postfix ! tells the compiler that we're sure it isn't null.


Sidenote: I find the formatting they use a bit strange. I personally would use

this.resolve!('hi there!')
Sign up to request clarification or add additional context in comments.

1 Comment

It was exactly the weird formatting with the blank between resolve and ! that confused me, too. Thanks for the clarification, now I know the counterpart for the Elvis operator.
0

Non-null assertion operator ! is usually used in cases where variable/field in your code is instantiated by external code. This assumes that compiler option strictNullChecks, which enables this check, is turned on.

There are 3 ways to approach situation when your function is set by external code:

1) If you define your function as only Function type, compiler will complain that you did not instantiate it before using it.

class Example1 {
  // Error: Property 'getUsername' has no initializer and is not definitely assigned in the constructor.
  public getUsername: Function;

  public formatUsername(){
    return `Username is : ${this.getUsername()}`;
  }
}

In this case you could add dummy initial function in the constructor, until function is replaced by external code:

class Example1a {
  public getUsername: Function;

  constructor(){
    this.getUsername = () => ""; // add dummy function, until it's replaced.
  }

  public formatUsername(){
    return `Username is : ${this.getUsername()}`;
  }
}

2) If you define it as Function | undefined type, compiler will complain that you are calling function that is possibly not defined at that point:

class Example2 {
  public getUsername: Function | undefined;

  public formatUsername(){
    // Error: Cannot invoke an object which is possibly 'undefined'.
    return `Username is : ${this.getUsername()}`;
  }
}

Usual fix for this is that you check if function is indeed undefined before you call it:

class Example2a {
  public getUsername: Function | undefined;

  public formatUsername(){
    // No Error: Check manually if function has been set.
    if (this.getUsername === undefined) {
      return "";
    }
    return `Username is : ${this.getUsername()}`;
  }
}

3) If you know for sure that function will be set by external code (for example Angular's binding) and you don't want to add extra unnecessary check, you can use '!' to tell to compiler to skip strict-null-check on that particular function call.

class Example3 {
  public getUsername: Function | undefined;

  public formatUsername(){
    // No error: You tell compiler that `getUsername` won't be undefined.
    return `Username is : ${this.getUsername!()}`;
  }
}

Typescript Playground link.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.