Adding a custom variable to EmberRouter and then using it in another file

Viewed 237

I recently tried extended the EmberRouter to include the following piece of information.

router.js

import EmberRouter from '@ember/routing/router';

const Router = EmberRouter.extend({
  lastVisitedURL: null,

  init() {
    this._super(...arguments);
    this.on('routeWillChange', () => {
      this._super(...arguments);
      this.lastVisitedURL = this.currentURL;
    });
  }

  // Router.map code (not important)
})

I would now like to extract lastVisitedURL from a controller file. However, I'm not sure how to do it. Some of the things I've tried include importing the EmberRouter directly (I.E.):

import Router from '../router';

export default Controller.extend({
   someFunction() {
     console.log(Router.lastVisitedURL); // returns undefined
   }
});

I'm not perfectly sure what the problem is with this approach, but it appears to be returning me some sort of other object or function that doesn't truly contain the state of the router.

So the next approach, that seems to be a little more accepted, was to try to use the RouterService object that I believe is meant to provide an API to the EmberRouter.

import Router from '../router';

export default Controller.extend({
   router: service(),

   someFunction() {
     console.log(this.router.lastVisitedURL) // returns undefined
   }
});

The problem I encountered with this solution though is that even though the routerService can store the state of the EmberRouter, it doesn't store my specific new variable. So I now need a way to add this specific pice of data to the RouterService as well as the EmberRouter.

I'm not really sure how to do this or if there is a better approach to the problem I'm trying to solve. Any thoughts appreciated!

1 Answers

I'm a little bit confused about your use case to be honest. The current URL is available on the RouterService, which is shipped with Ember by default. You could access it like this:

import Controller from '@ember/controller';
import { inject as service } from '@ember/service';

export default Controller.extend({
   router: service(),

   someFunction() {
     console.log(this.router.currentURL);
   }
});

It seems like you are trying to reinvent that feature.

If you want to go with declaring a property on the EmberRouter instance and use it at other places you need to look up the router on the container. It's available as router:main. You can't import it directly as it's neither a service nor a controller. The code would look like:

import Controller from '@ember/controller';
import { getOwner } from '@ember/application';

export default Controller.extend({
   someFunction() {
     let owner = getOwner(this);
     let router = owner.lookup('router:main');
     console.log(router.currentURL);
   }
});

I would not recommend such a pattern. I don't think it's officially supported. As far as I'm aware router:main is private API. So it might be broken in a minor release.

This could be way better addressed by a service:

// app/services/location-history.js

import Service from '@ember/service';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';

export default Service.extend({
  router: service(),

  updateRoute: action(function() {
    this.visitedURLs = [...this.visitedRoutes, this.router.currentURL];
  }),

  init() {
    this.router.on('routeDidChange', this.updateRoute);
    this.set('visitedURLs', []);
  },

  willDestroy() {
    this.router.off('routeDidChange', this.updateRoute);
  }
});

If you find that syntax hard to read I recommend switching to native ECMAScript classes, which is the default syntax since Ember Octance:

// app/services/location-history.js

import Service from '@ember/service';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';

export default class LocationHistoryService extends Service {
  @service router;

  visitedURLs = [];

  @action
  updateRoute() {
    this.visitedURLs = [...this.visitedRoutes, this.router.currentURL];
  }

  constructor() {
    this.router.on('routeDidChange', this.updateRoute);
  },

  willDestroy() {
    this.router.off('routeDidChange', this.updateRoute);
  }
});
Related