# Route lifecycle: @bindable vs @observable

**URL:** <https://discourse.aurelia.io/t/route-lifecycle-bindable-vs-observable/2847>\
**Category:** Help Requests\
**Created:** [August 19, 2019, 5:29pm UTC](https://discourse.aurelia.io/t/route-lifecycle-bindable-vs-observable/2847 "2019-08-19T17:29:52Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![lorond](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/lorond/32/1598_2.png) [@lorond](https://discourse.aurelia.io/u/lorond)\
**Post date:** [August 19, 2019, 5:29pm UTC](https://discourse.aurelia.io/t/route-lifecycle-bindable-vs-observable/2847/1 "2019-08-19T17:29:52Z")

</div>

Look’s like changing `@bindable` property value until `bind` would not lead to `{property}Changed` invocation at all.  
I have a route with a parameter, for example `:id`. When `id` is changed (doesn’t matter, via routing, gui or in some other way) I need to load some data. Consider following vm:

```auto
export class MiracleCustomElement
{
  @bindable
  id: string;

  activate({ id }: { id: string })
  {
    this.id = id;
  }

  idChanged(cur: string, prev: string)
  {
    // some very important magic here
  }
}

```

When the property is decorated with `@bindable`, on the first `activate` method `idChanged` is not invoked at all. On subsequent `activate` calls on route changes it works well. Of course `replace` activation strategy breaks that.

When the property is decorated with `@observable`, everything work well. But observable properties cannot be used in bindings in views between custom elements.

Also property cannot be both `@observable` and `@bindable`.  
This works just like bindable:

```auto
  @bindable
  @observable
  id: string;

```

And this throws `ERROR [app-router] TypeError: can't redefine non-configurable property "id"`:

```auto
  @observable
  @bindable
  id: string;

```

So if I want both property being bindable and `{property}Changed` being called during `activate` (more precisely - before `bind`) the only solution I see is to create two properties (one bindable and one observable) and sync them manually.

Is there any other options? Am I missing something?

---

<div class="post-metadata">

**Author:** ![Krussicus](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/krussicus/32/1605_2.png) [@Krussicus](https://discourse.aurelia.io/u/Krussicus)\
**Post date:** [August 26, 2019, 7:47pm UTC](https://discourse.aurelia.io/t/route-lifecycle-bindable-vs-observable/2847/2 "2019-08-26T19:47:50Z")

</div>

I’ve only needed to use `@bindable` when I want to do something like `<my-element id.bind="...">`.  
Since you’re using `activate`, I’m guessing that this is not what you’re doing? Custom elements do not have an `activate` and the difference you’re describing is probably due to when the `Changed` delegate is hooked up or designed to be triggered.  
`@observable` basically applies immediately and can even trigger before the `constructor`.  
Your description sounds like `@bindable` is set-up once, probably at `bind`, and then kept alive, since VMs are singletons by default. I imagine adding `@transient` will break things too.

“But observable properties cannot be used in bindings in views between custom elements.” - Why not? Could you provide an example with the issues you’ve encountered? I might be misunderstanding you.

A potential solution for you, though, might be to just call `idChanged(id)` in the `activate`, if you must use `@bindable` as you’ve described. This will probably lead to duplicate calls on subsequent page loads.

---

<div class="post-metadata">

**Author:** ![huochunpeng](https://avatars.discourse-cdn.com/v4/letter/h/49beb7/32.png) [@huochunpeng](https://discourse.aurelia.io/u/huochunpeng)\
**Post date:** [August 26, 2019, 10:46pm UTC](https://discourse.aurelia.io/t/route-lifecycle-bindable-vs-observable/2847/3 "2019-08-26T22:46:41Z")

</div>

FYI [Difference between @observable and @bindable](https://discourse.aurelia.io/t/difference-between-observable-and-bindable/400)

---

<div class="post-metadata">

**Author:** ![lorond](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/lorond/32/1598_2.png) [@lorond](https://discourse.aurelia.io/u/lorond)\
**Post date:** [August 27, 2019, 9:50am UTC](https://discourse.aurelia.io/t/route-lifecycle-bindable-vs-observable/2847/4 "2019-08-27T09:50:24Z")

</div>

> A potential solution for you, though, might be to just call `idChanged(id)` in the `activate`

When VM is reused, changes to all `@bindable` propertied invokes `{proeprty}Changed` handlers. So in that case I need to control it by myself to prevent two subsequent call - one made by aurelia and one made by myself. Also I need to check it by myself if value actually changed.

---

<div class="post-metadata">

**Author:** ![lorond](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/lorond/32/1598_2.png) [@lorond](https://discourse.aurelia.io/u/lorond)\
**Post date:** [August 27, 2019, 10:40am UTC](https://discourse.aurelia.io/t/route-lifecycle-bindable-vs-observable/2847/5 "2019-08-27T10:40:46Z")

</div>

Yep, I’ve read this. And this does not answer my question at all.

I don’t know why changes to `@bindable` properties on `activate` stage does not lead to `{property}Changed` handler call when VM just created and was not bound yet. Maybe this was done on purpose.

But when VM was bound and is reused, on same `activate` stage changes became to lead to handler’s calls. Different behavior on same stage. However this is desired behavior.

Actually, most frustrating is that on fresh unbound VM handler is not called.

So possible workarounds are either use one `@observable` property and one `@bindable` and synchronize them manually, or as @Krussicus suggested, keep track weither VM was bound or not, and if it wasn’t, call handler manually.

However both workarounds looks like kind of complications for pretty simple scenario.

---

<div class="post-metadata">

**Author:** ![Krussicus](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/krussicus/32/1605_2.png) [@Krussicus](https://discourse.aurelia.io/u/Krussicus)\
**Post date:** [August 27, 2019, 10:43am UTC](https://discourse.aurelia.io/t/route-lifecycle-bindable-vs-observable/2847/6 "2019-08-27T10:43:26Z")

</div>

It’s not really a solution I’d recommend. Just an option.  
I’m still not clear on why you need a `@bindable` within a VM. What does your HTML look like?

---

<div class="post-metadata">

**Author:** ![lorond](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/lorond/32/1598_2.png) [@lorond](https://discourse.aurelia.io/u/lorond)\
**Post date:** [August 27, 2019, 12:01pm UTC](https://discourse.aurelia.io/t/route-lifecycle-bindable-vs-observable/2847/7 "2019-08-27T12:01:17Z")

</div>

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_

```auto
<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_

```auto
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_

```auto
<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_

```auto
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_

```auto
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_

```auto
<template>
  <div><a href="/">home</a> (form data will not load)</div>
  <br />
  <router-view></router-view>
</template>

```

_app.ts_

```auto
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;
  }
}

```

---

<div class="post-metadata">

**Author:** ![Krussicus](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/krussicus/32/1605_2.png) [@Krussicus](https://discourse.aurelia.io/u/Krussicus)\
**Post date:** [August 27, 2019, 9:14pm UTC](https://discourse.aurelia.io/t/route-lifecycle-bindable-vs-observable/2847/8 "2019-08-27T21:14:02Z")

</div>

I see. You’re using your `./grid` as both an element and a VM?  
I’ve never really tried this approach. I haven’t seen it before either. I’m not sure how recommended it is, probably for these reasons.  
Consider creating another VM to act as a view space for your `grid` route instead of trying to re-use your element. Then use `@bindable` within the grid element and use `@observable` within your VMs.  
That’s usually the approach I take. There are more exotic options but I’ve not field tested those.

---

<div class="post-metadata">

**Author:** ![lorond](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/lorond/32/1598_2.png) [@lorond](https://discourse.aurelia.io/u/lorond)\
**Post date:** [August 28, 2019, 8:25am UTC](https://discourse.aurelia.io/t/route-lifecycle-bindable-vs-observable/2847/9 "2019-08-28T08:25:25Z")

</div>

Yep, wrapping `grid` into another element works, `@observable` can be used on topmost elements and `@bindable` on child. This looks like most elegant solution.  
Thanks.

However I still have no clue, why `@bindable` is not tracked until `bind()`?

---

<div class="post-metadata">

**Author:** ![Krussicus](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/krussicus/32/1605_2.png) [@Krussicus](https://discourse.aurelia.io/u/Krussicus)\
**Post date:** [August 28, 2019, 8:52am UTC](https://discourse.aurelia.io/t/route-lifecycle-bindable-vs-observable/2847/10 "2019-08-28T08:52:04Z")

</div>

You’ll have to ask the Aurelia team. I can only speculate. Seems like a design decision, i.e. there are two options because of their intended usage.  
Makes sense that `@bindable` would be geared towards the binding process while `@observable` is more for standard property observation.

---

<div class="post-metadata">

**Author:** ![lorond](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/lorond/32/1598_2.png) [@lorond](https://discourse.aurelia.io/u/lorond)\
**Post date:** [August 28, 2019, 1:38pm UTC](https://discourse.aurelia.io/t/route-lifecycle-bindable-vs-observable/2847/11 "2019-08-28T13:38:51Z")

</div>

Yep, that makes sense, right.

However with templates everything work fine out of the box. You do not have to do any workarounds. All changes reflected well regardless of whether the VM is fresh or reused. It just works.

Also, I can expect handler would NOT be called until component is bound. All changes handled on the next ‘tick’, that is opposite to `@observable` with immediate calls. And nothing does start ‘ticking’ until everything is bound. So, until `bind()` - no `{property}Changed` calls. This is fine.

But I expect changes to be tracked since the very beginning, and WHEN component got bound and that ‘ticking’ begins, corresponding `{property}Changed` handlers would be called. Of course, only in case of any changes happened before.

Don’t know, maybe it would be better to create an issue on github? Hope folks from Aurelia team are here 🙂

---

<div class="post-metadata">

**Author:** ![Krussicus](https://yyz1.discourse-cdn.com/flex027/user_avatar/discourse.aurelia.io/krussicus/32/1605_2.png) [@Krussicus](https://discourse.aurelia.io/u/Krussicus)\
**Post date:** [August 28, 2019, 2:28pm UTC](https://discourse.aurelia.io/t/route-lifecycle-bindable-vs-observable/2847/12 "2019-08-28T14:28:31Z")

</div>

> [@lorond](#):
>
> But I expect changes to be tracked since the very beginning

What would you like to accomplish with `@bindable` that requires earlier tracking?  
Afaik, it’s usually used in `property.bind` scenarios, which, again afaik, only get assigned during the `bind` life-cycle. I’d argue that there might be alternatives.

For instance, if the data you want in your element is essential before the `bind` life-cycle, consider using the `aurelia-store` instead of the `property.bind` approach. This approach has provided me far more flexibility with regards to when data is retrieved.
