In Aurelia, can I bind a function from my containing view-model to be called by my custom element?
Asked Answered
C

4

9

I have a custom element which will take user input, and on [save] button click, I want to pass information to the parent view-model so I can send it to the server and move to the next section. I'm going to simplify this for example's sake:

my-element.js:

import { customElement, bindable } from 'aurelia-framework';

@customElement('my-element')
@bindable('save')
export class MyElement { }

my-element.html:

<template>
    <button click.delegate="save()">Click this</button>
</template>

parent-view-model.js:

export class ParentViewModel {
  parentProperty = 7;
  parentMethod() {
    console.log(`parent property: ${this.parentProperty}`);
  }
}

parent-view-model.html:

<template>
    <require from="./my-element"></require>
    <div class="content-panel">
        <my-element save.bind="parentMethod"></my-element>
    </div>
</template>

For a demo, see (app.js and app.html represent parent-view-model.js and parent-view-model.html):

https://gist.run/?id=96b203e9ca03b62dfb202626c2202989

It works! Kind of. Unfortunately, this seems to be bound to my-element instead of parent-view-model, so in this example, what is printed to console is: parent property: undefined. That will not work.

I know I can utilize the EventAggregator to facilitate some communication between the custom element and the view-model, but if I can help it I'd like to avoid the added complexity.

Cavan answered 20/8, 2016 at 14:46 Comment(0)
U
15

You have two options for this. You could handle this using Custom Events, or you can do it using the call binding that Anj mentioned in his answer. Which one you use depends on your actual use case.

If you want the custom element to be able to call a method on your parent VM and pass data out of the custom element, then you should use a Custom Event as shown in this gist: https://gist.run/?id=ec8b3b11f4aa4232455605e2ce62872c:

app.html:

<template>
    <require from="./my-element"></require>
    <div class="content-panel">
        <my-element save.delegate="parentMethod($event)"></my-element>
    </div>

    parentProperty = '${parentProperty}'
</template>

app.js:

export class App {
  parentProperty = 7;
  parentMethod($event) {
    this.parentProperty = $event.detail;
  }
}

my-element.html:

<template>
    <input type="text" value.bind="eventDetailValue" />
    <button click.delegate="save()">Click this</button>
</template>

my-element.js:

import { inject, customElement, bindable } from 'aurelia-framework';

@customElement('my-element')
@inject(Element)
export class MyElement {
  eventDetailValue = 'Hello';

  constructor(element) {
    this.element = element;
  }

  save() {
    var event = new CustomEvent('save', { 
      detail: this.eventDetailValue,
      bubbles: true
    });

    this.element.dispatchEvent(event);
  }
} 

You would basically attach any data you need to pass on the detail property of the Custom Event. In the event binding declaration, you would add $event as a parameter to the function and then check the detail property of $event in your event handler (you could also just pass $event.detail if you wanted).

If you want the custom element to be able to call a method on your parent VM and have data passed in from the parent VM (or from another custom element or something), then you should use the call binding. You can specify arguments that will be passed to the method by specifying them in the binding declaration (foo.call="myMethod(myProperty)". These arguments come from the VM context where the binding is being declared, not from the Custom Element's VM).

Ungainly answered 20/8, 2016 at 16:46 Comment(0)
C
10

I was able to solve this after trolling through the aurelia docs hub for a bit. I don't know all the nuances that may be involved, but for this simple example, I was able to fix it by doing the following simple change:

In parent-view-model.html (or app.html in the gist-run example), change save.bind="parentMethod" to save.call="parentMethod()"

I am still unsure how to pass data from the custom element into the parent view-model's method, however.

Here is the documentation from aurelia website.

Cavan answered 20/8, 2016 at 15:44 Comment(1)
Updated link for the comment from @rodu: aurelia.io/docs/binding/basics#function-referencesGoines
N
0

To pass parametrized functions to custom child components write the following (according to documentation http://aurelia.io/docs/binding/basics#function-references).

Parent ViewModel

private _generate(myParam:string) {
    return myParam + " world";
}

Parent View

<custom-comp generator.call="_generate(myParam)"></custom-comp>

Child View

<template bindable="generator">
${generator({myParam:"hello"})}
</template>
Nickelodeon answered 4/12, 2020 at 10:47 Comment(0)
E
0

This has been updated for aurelia v2:

The call binding no longer assigns properties of the first argument pass to the call to the calling override context. This is unreasonably dynamic and could result in hard-to-understand templates. In Aurelia 1, you would have used call bindings like this:

export class MyElement {
  onChange;

  onInternalButtonClick() {
    this.onChange({ value: this.value });
  }
}

<my-element on-change.call="propertyChanged(value)">

In Aurelia 2, the property name is now on the $event property passed to the callback. It's a minor change, but you now do this instead:

<my-element on-change.call="propertyChanged($event.value)">
Exeat answered 7/4, 2022 at 9:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.