Here is simplified example of what I have in my app. Everything else is a default webpack ts app.
When form or grid opens for the first time, data get not loaded as {property}Changed
handler is not invoked.
So the main difficulty is that I need to track my myself, would or would not {property}Changed
be called. For example, be adding boolean
property, that I set to true
in bind()
. And if it would not be called, call it by myself. But this is not reliable as behavior could be changed.
Another option is to add something like @observable propObs:string
.
In propChanged
set this.propObs = this.prop
.
In propObsChanges
set this.prop = this.propObs
and start actually data load.
So prop
property will be for templating, propObs
for actual data management.
This is looks more reliable but is kind of excess.
form.html
<template>
<require from="./grid"></require>
<!-- way to change id via ui - explicitly bound to input -->
<div>form for <input value.bind="id & debounce:500" /></div>
<div>
<!-- way to change id via routing, kind of paging -->
<a if.bind="id.length > 1" route-href="route: form; params.bind: { id: id.substring(0, id.length - 1) }">prev</a>
<span else>prev</span>
<a route-href="route: form; params.bind: { id: id + (id.length + 1) }">next</a>
</div>
<br />
<template if.bind="data || loading">
<div>ID is ${loading ? '...' : data.id}</div>
<div>Date is ${loading ? '...' : data.date}</div>
</template>
<template else>
<div>nothing found</div>
</template>
<br />
<div><a route-href="route: grid; params.bind: { q: id }">open full grid</a>(grid data will not load)</div>
<some-grid query.to-view="id" limit="3"></some-grid>
</template>
form.ts
import { bindable, autoinject } from "aurelia-framework";
import { Router } from "aurelia-router";
import { SomeExternalLoader } from "loader";
import { Logger } from "aurelia-logging";
@autoinject
export class SomeFormCustomElement {
@bindable id: string;
@bindable loading: boolean;
@bindable data: { id: string, date: Date };
constructor(private router: Router, private external: SomeExternalLoader, private log: Logger) {
this.log.id = 'form';
}
activate({ id }: { id: string }) {
this.log.info('activated');
this.id = id;
}
async idChanged(cur: string, prev: string) {
this.log.info(`id changed from ${prev} to ${cur}`);
this.router.navigateToRoute('form', { id: this.id });
this.loading = true;
const data = await this.external.formData(this.id);
if (cur === this.id) {
this.data = data;
this.loading = false;
}
}
}
grid.html
<template>
<div>search for <input value.bind="query & debounce:500" /></div>
<template if.bind="loading">
<li><ul repeat.for="item of query.length">...</ul></li>
</template>
<template else>
<li><ul repeat.for="item of data">${item}</ul></li>
</template>
</template>
grid.ts
import { bindable } from "aurelia-templating";
import { autoinject } from "aurelia-framework";
import { SomeExternalLoader } from "loader";
import { Logger } from "aurelia-logging";
@autoinject
export class SomeGridCustomElement {
@bindable query: string;
@bindable limit: number;
@bindable loading: boolean;
@bindable data: string[];
constructor(private external: SomeExternalLoader, private log: Logger) {
this.log.id = 'grid';
}
activate({ q }: { q: string }) {
this.log.info('activated');
this.limit = undefined;
this.query = q;
}
async queryChanged(cur: string, prev: string) {
this.log.info(`query changed from ${prev} to ${cur}`);
this.loading = true;
const data = await this.external.gridData(this.query, this.limit);
if (cur === this.query) {
this.data = data;
this.loading = false;
}
}
}
loader.ts
export class SomeExternalLoader {
public formData(id: string): Promise<{ id: string, date: Date }> {
return this.delay(id.length > 5 ? null : { id, date: new Date() }, 700);
}
public gridData(query: string, limit: number): Promise<string[]> {
return this.delay(query.split('').slice(0, limit).map(x => `${x} - data`), 1200);
}
private delay<T>(data: T, ms: number): Promise<T> {
return new Promise(r => setTimeout(() => r(data), ms));
}
}
app.html
<template>
<div><a href="/">home</a> (form data will not load)</div>
<br />
<router-view></router-view>
</template>
app.ts
import { PLATFORM } from "aurelia-pal";
import { Router, RouterConfiguration } from 'aurelia-router';
export class App {
public router: Router;
public configureRouter(config: RouterConfiguration, router: Router) {
config.map([
{ route: '', redirect: '/form/1234' },
{ route: '/form/:id', name: 'form', moduleId: PLATFORM.moduleName('./form'), title: 'Form' },
{ route: '/grid/:q', name: 'grid', moduleId: PLATFORM.moduleName('./grid'), title: 'Grid' },
]);
this.router = router;
}
}