SimonC
SimonC

Reputation: 385

Aurelia Routing - Prevent view swapping to instantiate new view model each time

my question is: is it possible to keep the same view model instance of a router view when we swap between views in a router?

I explain my problem: I'd like to make router child components communicate thanks to EventAggregator. One of my router-child component publishes a message on click on a button and another one subscribes to it.

Here is the code in the component that subscribed to that event:

constructor(private ea: EventAggregator) {
  ea.subscribe(GameInfo, msg => console.log(msg));
}

When I click on my button, I see in the console multiple logs (or none) depending on the number of time I navigated to my view that is subscribing.

Here is my router component:

import {Router, RouterConfiguration} from 'aurelia-router';

export class Playground {
  public router: Router;

  public configureRouter(config: RouterConfiguration, router: Router) {
    config.map([
      { route: ['', 'media-creator'], name: 'media-creator', moduleId: './media-creator/media-creator', nav: true, title: 'Media Creator' },
      { route: 'board-initializer', name: 'board-initializer', moduleId: './board-initializer/board-initializer', nav: true, title: 'Board Initializer' },
      { route: 'code-editor', name: 'code-editor', moduleId: './code-editor/code-editor', nav: true, title: 'Code Editor' }
    ]);

    this.router = router;
  }
}

I'd like to send info from the Board Initializer to the Code Editor and I need to keep the state of each component when I'm navigating through the router.


Maybe it is not possible to prevent this mechanism and my solution would be to use a bootstrap nav-tabs/data-toggle.

Thank you any given insights on that.

Upvotes: 1

Views: 512

Answers (2)

Tom
Tom

Reputation: 4292

Firstly, regards the issue with the Event Listener being added multiple times - you need to alter the way you're adding it and then also make sure you're removing it when the current VM is detached.

Below, I've taken code from the excellent ILikeKillNerds.com Blog - which is quite honestly a lifesaver sometimes.

https://ilikekillnerds.com/2016/02/working-with-the-aurelia-event-aggregator/

import { inject } from 'aurelia-framework';
import { EventAggregator } from 'aurelia-event-aggregator';

@inject(EventAggregator)
export class MyClass {
    constructor(EventAggregator) {
        this.ea = EventAggregator;
    }

    attached() {
        this.subscriber = this.ea.subscribe('puppyMonkeyBaby', response => {
            console.log(response.testValue);
        });
    }

    detached() {
        this.subscriber.dispose();
    }
}

As you can see, the event listener is added in the attached() method, and then importantly removed when the VM is detached in (unsurprisingly) detached().

Storing state is harder, as Mgiesa has covered there's a few different ways to approach it and it really depends on your use case.

Personally, I'd consider using a class as a singleton, that is injected into each of the different View Models and allows your to share state.

Again, it's the excellent ILikeKillNerds.com who provides a great blog about this;

https://ilikekillnerds.com/2016/02/shared-state-in-aurelia/

Any class injected into a View Model (without using NewInstance.of()) will be considered a singleton.

Upvotes: 2

mgiesa
mgiesa

Reputation: 1013

I've seen quite a few questions about maintaining state while navigating lately. Viewmodels are deconstructed when no longer needed so you can't maintain state from within the viewmodel itself with the router config you've shown.

You have two options:

1) inject a service into both of the viewmodels that need to communicate and track messages in a queue within that service. As you navigate around, check the queue and handle the messages in order. Not s perfect solution but it will work as long as the queue doesn't grow too large.

2) create a single route and add a parameter to the route that represents which page should be visible, then put both viewmodels on the page as components and show.bind on the route parameter. When you want to switch components still call router.navigateToRoute() but point to the same route (just change the parameter for which component is visible). You'll also need to set the activation strategy to "invokeLifecycle" so that the viewmodel's router lifecycle hooks are called and the value of the route parameter can be handled. The benefit here is that the viewmodels for the two components will stay alive so they can continue passing messages to each other, and to the user it still appears as if they're navigating between pages. If you don't care to update the URL then of course you can skip all of this and just keep track of a variable locally.

I can write out a code sample when I get to a computer

As a side note, don't the subscribe to the event aggregator in the constructor because as you've noticed you'll get multiple subscriptions. Subscribe in activate or attached and then unsubscribe in deactivate or detached.

Upvotes: 1

Related Questions