Ember-Cli-Typescript loading & error substates

Hello Everyone,

We are currently using ember-cli-typescript along on the frontend of our application. When implementing a loading substate as detailed here, typescript reports:

app/routes/loading.ts:10:19 - error TS2769: No overload matches this call.
  Overload 1 of 2, '(key: "toString" | "get" | "getProperties" | "set" | "setProperties" | "notifyPropertyChange" | "addObserver" | "removeObserver" | "getWithDefault" | "incrementProperty" | "decrementProperty" | ... 16 more ... | "actions", value: any): any', gave the following error.
    Argument of type '"currentlyLoading"' is not assignable to parameter of type '"toString" | "get" | "getProperties" | "set" | "setProperties" | "notifyPropertyChange" | "addObserver" | "removeObserver" | "getWithDefault" | "incrementProperty" | "decrementProperty" | ... 16 more ... | "actions"'.
  Overload 2 of 2, '(key: "toString" | "get" | "getProperties" | "set" | "setProperties" | "notifyPropertyChange" | "addObserver" | "removeObserver" | "getWithDefault" | "incrementProperty" | "decrementProperty" | ... 16 more ... | "actions", value: true): true', gave the following error.
    Argument of type '"currentlyLoading"' is not assignable to parameter of type '"toString" | "get" | "getProperties" | "set" | "setProperties" | "notifyPropertyChange" | "addObserver" | "removeObserver" | "getWithDefault" | "incrementProperty" | "decrementProperty" | ... 16 more ... | "actions"'.

10    controller.set('currentlyLoading', true);

Why is this?
I imagine typescript can’t find the currentlyLoading symbol/overload, from the error message :sweat_smile:, but how do I instruct typescript on where to find this symbol/overload?

I should note that even though typescript is reporting an error, the substate displays correctly when a route is in transition.

Your controller class needs to define the currentlyLoading property. In Octane, that would look something like this:

import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';

export default class FooController extends Controller {
  @tracked currentlyLoading = false;
}
import Route from '@ember/route';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import type Store from '@ember-data/store';

export default class FooRoute extends Controller {
  @service store: Store;

  model() {
    return this.store.findAll('slow-model');
  }

  @action
  loading(transition, originRoute) {
    let controller = this.controllerFor('foo');
    controller.currentlyLoading = true;
    transition.promise.then(() => {
      controller.currentlyLoading = false;
    });

    return true; // allows the loading template to be shown
  }
}

You can of course also use controller.set as that example shows, but if you’re introducing new code I’d do it the Octane way as shown here.

The key is that the value you’re setting on the target controller class has to be defined for TS to recognize it as legitimate! The error message you’re getting is somewhat less clear because of how we have to handle get for Ember Classic. Switching to a @tracked property and just using normal assignment will give you the best of both worlds.

Thanks, I have a follow up question as well.
Let’s say I have the following route index.ts defined as:

import Route from '@ember/routing/route';
import { action } from '@ember/object';
export default class Index extends Route.extend({
  // anything which *must* be merged to prototype here
}) {
  // normal class body definition here
  model()
  {

      let controller = this.controllerFor('index');
     console.log(controller);
     console.log(controller.currentlyLoading);
  }
  @action
  loading(transition, originRoute)
  {
     console.log("IN LOADING");
     let controller = this.controllerFor('index');
     console.log(controller.currentlyLoading);
     controller.currentlyLoading = true;
     transition.promise.then(() => {
      controller.currentlyLoading = false;
    });
     return true; // allows the loading template to be shown
   }
}

and a controller also called index.ts defined as:

import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
export default class Index extends Controller.extend({
  // anything which *must* be merged to prototype here
}) {
  // normal class body definition here
  @tracked public currentlyLoading: boolean;
  private constructor()
  {
    super(...arguments);
    this.currentlyLoading = false;
  }
}

// DO NOT DELETE: this is how TypeScript knows how to look up your controllers.
declare module '@ember/controller' {
  interface Registry {
    'index': Index;
  }
}

On my local machine I get:

app/routes/index.ts:21:17 - error TS2339: Property 'currentlyLoading' does not exist on type 'Controller'.

even though in chrome dev tools I can see currentlyLoading as a property on the index controller.

Would I just use,

// @ts-ignore

or am I still misunderstanding?