2
votes

I have two views on the same page. View A’s view-model needs to call a method in view B’s view-model. Is that possible with Aurelia?

Also, is this the best approach? Would it be better to use the EventAggregator (pub/sub) to communicate between the view-models?

----- More Details -----

To be much more specific, there is a nav bar used in my app.html file like this:

<template>

    <require from="nav-bar-view"></require>

    <nav-bar-view></nav-bar-view>

    <router-view></router-view>

</template>

View-models within the router-view need to be able to change the nav bar's title and button text.

My initial design was to use pub/sub to communicate the changes to the nav bar view-model. Since that seemed a bit messy and overcomplicated, I wanted to come up with a simpler approach.

My latest idea is to create a singleton NavBar class that is injected into the NavBarView class and the "consumer" view-models.

The following is a simplified version of the code:

nav-bar-view.html:

<template>
    <div class="center">${navBar.title}</div>
</template>

nav-bar-view.js:

import {inject} from 'aurelia-framework';
import {NavBar} from 'nav-bar';

@inject(NavBar)
export class NavBarView {
    constructor(navBar) {
        this.navBar = navBar;
    }
}

nav-bar.js:

import {singleton} from 'aurelia-framework';

@singleton()
export class NavBar {
    constructor() {
        this.title = '';
    }
}

view.js (a consumer of the nav bar):

import {inject} from 'aurelia-framework';
import {NavBar} from 'nav-bar';

@inject(NavBar)
export class View {
    constructor(navBar) {
        this.navBar = navBar;
    }

    attached() {
        this.navBar.title = 'This View's Title';
    }
}

Again, this is much simpler than the actual code, but it servers to illustrate the idea.

I've tried it out and it works fine. Does this make sense? Is there a better way?

1
could you provide a gist containing what you have so far? would be easier to understand and helpFabio Luz
Ask yourself honestly, "does view A have any business knowing anything about the inner workings of view B? Should view A's code get updated every time view B's code gets updated?" If the answer is "no", then you want to use the EventAggregator. Without more context, it's really hard to tell.Technetium
If there is a way in Aurelia to specify that a view-model is a singleton, it would eliminate the need to have a separate class (in this case NavBar).Jeff G

1 Answers

5
votes

pub/sub would work but I suspect you're looking for something a little more targeted than that.

The preferred way to pass something into a custom element is via a bindable property. Assuming you have component-a and component-b and A needs to call a method on B's view-model. Here's what you could do:

  1. get a reference to B's view-model so we can bind to it's properties and methods:
<component-b view-model.ref="b"></component-b>
  1. Add bindable property to component A so we can give component A the reference to B's method.
import {bindable} from 'aurelia-framework';

export class ComponentA {
  @bindable sayHello;
}
  1. Bind component A's sayHello property to B's sayHello method.
<component-a say-hello.call="b.sayHello()"></component-a>

Here's a runnable example: https://gist.run/?id=91269472d4e6509e32123ca2a63dd9ca

Edit

Based on the updated information in the question, here's what I would recommend:

1. Create a class that contain's your nav-bar state

export class NavState {
  title = 'some default title';
}

2. Take a dependency on the NavState in your nav-bar component

@inject(NavState)
export class NavBar {
  constructor(state) {
    this.state = state; // now you can bind to "state.title" in nav-bar.html
  }
  ...
}

3. Take a dependency on the NavState in components that need to change the title.

@inject(NavState)
export class MyComponentThatChangesTheTitle {
  constructor(state) {
    this.state.title = 'something else';
  }
  ...
}

This will be more flexible than passing around a component's viewmodel as state. For example, with this model, you can configure the title before the nav-bar is even instantiated.