-1
abstract class Base {
  protected id: number | null = null;

  init(id: number) {
    this.id = id;
  }

  isIdSet(_ = this.id): _ is null | never {
    if (this.id === null) throw new Error(ERROR_NO_ID);
    else return false;
  }
}

class Foo extends Base {
  private fetch(id: number) { ... }
  
  get(): Observable<string> {
    if (this.isIdSet()) {
      return this.fetch(this.id); // TS2322: Type 'number | null' is not assignable to type 'number'.
    }
  }
}
Pavel Staselun
  • 1,970
  • 1
  • 12
  • 24
  • Sadly, this doesn't work because Typescript is unable to infer that `this.id` is not `null` from calling this function. You can use `if(this.id !== null)` instead. By definition, it will be a number if this resolves to `true`. –  Jul 04 '22 at 13:12
  • The idea was to put he null-check and `throw` into a function to get rid of duplicate `if` statements, but now I realize - I'd have `if` statement anyway, so no point in doing that. – Pavel Staselun Jul 04 '22 at 13:33
  • It'd be nice if you could include that in the question, I added an answer to avoid having to use `if` statements whenever you have to access `id` from outside. –  Jul 04 '22 at 14:33

2 Answers2

1

You may be looking for an assertion function.

abstract class Base {
  protected id: number | null = null;

  init(id: number) {
    this.id = id;
  }

  isIdSet(_ = this.id): asserts _ is number {
    if (this.id === null) throw new Error("ERROR_NO_ID");
  }
}

class Foo extends Base {
  private fetch(id: number) {}
  
  get() {
    this.isIdSet(this.id)
    return this.fetch(this.id);    
  }
}

Playground

Tobias S.
  • 21,159
  • 4
  • 27
  • 45
  • Cool, never heard of Assertion Functions before, thanks! But unfortunately it doesn't work if you don't pass any argument to `isIdSet`. – Pavel Staselun Jul 05 '22 at 11:16
1

The idea was to put he null-check and throw into a function to get rid of duplicate if statements

You can do this in the base class with a custom getter that checks if this.m_id is defined and throws an error otherwise, essentially forcing the init method to be called before accessing id.

class Base {
    private m_id: number | null = null;

    protected get id(): number {
        if(this.m_id === null){
            throw new Error(`Id is not defined`);
        }
        return this.m_id;
    }

    public init(a_id: number): void {
        this.m_id = a_id;
    }
}


class Foo extends Base {
    private fetch(a_id: number): string {
        return a_id.toString();
    }

    public get(): string {
        return this.fetch(this.id);
    }
}

Playground