A Quick Post-Mortem

In the previous part of the tutorial, we built a component that fetches and displays nearby retail stores. As you can see, it takes quite a bit of code to cover all of the corner cases and build something that is actually production-ready:

export default TutorialComponent.extend({
  result: null,
  isFindingStores: false,
  actions: {
    findStores() {
      if (this.isFindingStores) { return; }

      let geolocation = this.get('geolocation');
      let store = this.get('store');

      this.set('isFindingStores', true);
        .then(coords => store.getNearbyStores(coords))
        .then(result => {
          if (this.isDestroyed) { return; }
          this.set('result', result);
        .finally(() => {
          if (this.isDestroyed) { return; }
          this.set('isFindingStores', false);
Toggle JS / Template

This is not the beautiful Ember code we all thought we'd be writing, and unfortunately this kind of code is extremely commonplace.

Alternative: Move tricky code to an object with a long lifespan

Components have limited lifespans: they're rendered, and then eventually they're unrendered and destroyed. Controllers, Services, Ember-Data Stores, and Routes, on the other hand, live forever (or at least until the app is torn down in a testing environment).

As such, one approach to avoiding "set on destroyed object" errors is to move tricky async logic into a method/action on a Controller or Service that is invoked by a Component. Sometimes this works, but it's often the case that even though you no longer see exceptions in the console, you still need to clean up / stop / cancel some operation on a long lived object in response to a Component being destroyed. There are Component lifecycle hooks like willDestroyElement that you can use for these kinds of things, but then you still end up with the same amount of code, but now it's smeared between Component and Controller.