Jeff G
Jeff G

Reputation: 2175

Interfacing between view-models in Aurelia

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?

Upvotes: 2

Views: 1398

Answers (1)

Jeremy Danyow
Jeremy Danyow

Reputation: 26386

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.

Upvotes: 5

Related Questions