2
votes

Within an Aurelia project I have a view model that I want to maintain state between router navigation. I thought that adding @singleton() to my view model class would accomplish this.

In fact, I have created a simple Aurelia project where this works. I am able to navigate away from and back to the same page and state is maintained. My constructor is only called the first time I navigate to that page.

import { singleton } from 'aurelia-framework';

@singleton()
export class Welcome {
  heading = 'Welcome to the Aurelia Navigation App!';

  constructor() {
      console.log('constructor');
  }

  activate() {
      console.log('activate');
  }

  attached() {
      console.log('attached');
  }
}

However, in my larger application this is not working. I add the decorator and my view model's constructor is still called the second time I navigate to that page. (I have even copied this view model into my larger application and it is not treated as a singleton.)

Obviously something must be different between these two projects. However, I don't see any difference. Is there a setting I may have set that would override the behavior of @singleton()?

2

2 Answers

1
votes

You likely have multiple instances of the singleton custom element - singleton by definition cannot have multiple instances of itself. What you want is a view model that is created once, and reused.

Here's a solution that I used successfully. The idea is to have a global view model instance that is constructed once, and there is a controller view that uses that to compose the view/vm.

HTML

<template>
  <require from="yourView.html"></require>
  <p> parentClass.html </p>
  <compose view="yourView.html" view-model.bind="singletonViewModel"></compose>
</template>

JS

import { YourView } from 'yourView';
export class parentClass {
  // constructor for parentClass will run multiple times.
  constructor() {
    /** this is the key, you must instantiate a custom view model once
     on some globally singleton object like window or global in Node */
    if ( typeof window.yourView === 'undefined' ) {
      window.yourView = new YourView();
    }
  }

  bind() {
    this.singletonViewModel = window.yourView;
  }
}

Everything you do within this view model will persist now between navigations and constructor will only be called once. attached() and detached() for the singleton view model will still activate as you would expect.

Also note that you can use this to cache multiple instances of a view models that you can switch between. My application has a global service to keep track of multiple VMs that constructs/returns the VM as requested. Advantage this brings is I can do heavy processing once at the constructor stage then never worry about it again.

1
votes

It turns out the solution was to jspm update. I had thought that deleting my jspm_packages and running jspm install would be equivalent. But it is not.

Now that my Aurelia modules are up to date the singleton() decorator works fine.