简体   繁体   中英

Aurelia: Inheritance of view and view-model

I am building an Aurelia app which will basically allow users to show different lists for different resources. These lists share a couple of functions like a toolbar with search and refresh capabilities and a paginated list of resources.

I created this for a single resource and everything worked like a charm, but I would now need to duplicate big parts of both TypeScript code as well as HTML for the other resource lists. I decided to go for a different approach, create a custom-element with some named view-slots and an abstract view-model. This worked for the first run, but immediately after manipulating the list it would stop updating the slot contents.

Is there a way to achieve what I am trying to do?

Any help would be appreciated.

side note : I tried to create a simple Gist to demonstrate the issue, but it seems like the latest CDN version I could find does not support view-slots yet (I couldn't get it to work). :(

Basically what I am trying to do is something like:

list.html

<template>
    <div>list: ${counter}</div>
    <slot></slot>
    <button click.delegate="load()">Increase counter</button>
</template>

list.ts

import { autoinject } from 'aurelia-dependency-injection';
import { customElement } from 'aurelia-templating';

@customElement('list')
@autoinject
export abstract class List {
    public abstract counter;
    public abstract load();
}

resource.html

<template>
    <require from="./list"></require>

    <list>
        <div>resource: ${counter}</div>
    </list>
</template>

resource.ts

import { autoinject } from 'aurelia-dependency-injection';
import { List } from './list';

@autoinject
export class App extends List {
    public counter = 0;

    constructor() {
        super();
    }

    public load() {
        this.counter++;
    }
}

This gives the following output:

list: 0
resource: 0
<button>Increase counter</button>

After the button is clicked it should have increased both the list as well as the resource counter, but instead it still shows " 0 " for the resource. My guess is that the button is calling the load function on the abstract parent class that has no implementation and is therefor doing nothing. Changing the button to the child class would work, but is not really helping my code reduction goal. How can I keep using the implementer's (resource) binding context? Is there a way? Or am I completely off-track?

EDIT
So I found a way to work around it, but it just doesn't feel right. I changed the following bit in the List :

@bindable({defaultBindingMode: bindingMode.twoWay}) public abstract counter:number;

And then in the resource HTML:

<list counter.bind="counter">
    <div>users: ${counter}</div>
</list>

This way it synchronises the binding context for the counter property. I would prefer if there was a way to tell the List not to have its own binding context at all as this could theoretically lead to many, many bindables. Does anyone know how I could tell the List to inherit the binding context?

EDIT 2
Found another workaround for the previous EDIT, removed the bindable behaviour and implemented the bind callback in the List component. This gives me access to the parent binding context as first parameter. I save locally in the List component and then manually update the parent binding context when the counter property changes. This is still a workaround as this would make the List component quite big, but allows me not worry about bindings in the resources. The List now looks like this:

import { customElement } from 'aurelia-templating';
import { autoinject } from 'aurelia-dependency-injection';
import { observable } from 'aurelia-binding';

@customElement('list')
@autoinject
export abstract class List {
    private parentContext;
    @observable public abstract counter:number;

    bind(bindingContext) {
        this.parentContext = bindingContext;
    }

    counterChanged(newValue) {
        if (this.parentContext) this.parentContext.counter = newValue;
    }

    public abstract load();
}

I would still be interested in a way to remove the binding context for the List component altogether letting it inherit the parent binding context.

Note instruction.inheritBindingContext mentioned in this GitHub issue is acting like a one-time initial inherit and was thus not useful in my case, but might be useful for someone else.

看看这篇博文Aurelia Dynamic Compose Using compose and v-model and model, you can have different custom elements in the same list

After Edit 2 I was still getting issues when I had multiple resource lists implemented. It seemed like it was doing all kinds of functions x amount of times. So instead of manually syncing the binding contexts I now only update the parent (resource) context, like so:

List.ts

import { customElement } from 'aurelia-templating';

@customElement('list')
export abstract class List {
    public parentContext;
    public abstract counter:number;

    bind(bindingContext) {
        this.parentContext = bindingContext;
    }

    public abstract load();
}

List.html

<template>
    <div>list: ${parentContext.counter}</div>
    <slot></slot>
    <button click.delegate="parentContext.load()">Increase counter</button>
</template>

This is not only decreasing the complexity of my List , but also making a lot more sense and is less error prone.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM