Does there exist a good binding implementation for use with Promises? Everything I try seems to end up giving me an error about being unable to apply bindings multiple times to the same element (which I understand, and am trying to find a way around). I found this, but it's rather old and only seems to work with bindings that don't control descendant bindings for the same reason.
I have also tried writing my own implementation that attempted to remove children, and only re-attach/bind them after the promise resolves, but got the same result.
As an alternative, it's possible that I create an async computed observable to bind against, but then typing (I'm using TypeScript) becomes a little murky, since I would be returning a promise, but the value read from the observable would be something else. I could just type it as (effectively) "Promise | T", but that might be confusing, as it will only ever actually be "T".
Edit: This is the typing problem I'm talking about. Keep in mind that I'm using a method to hide observables behind getters/setters so that my properties look like regular javascript properties. My thought was to use a decorator to sorta turn a getter that returns a promise into a getter that returns the value
export class Foo {
@promise get bar(): int {
return new Promise<int>((resolve, reject) => {
setTimeout(() => { resolve(1) }, 100);
});
}
}
Doing this would look alright, except that TypeScript is going to complain about the return type. I could cast it to any, but that's just not right. Alternatively, I could cast the getter return value to Promise | T, but that misrepresents the actual return type, because it would always be T.
In the way of the binding, I'd prefer not to rebind everything, but it appears, in cases like the "if" binding (which is actually what I'm trying to use), there's not really a way around it.
Edit 2: just in case, here's my current "promise" binding handler incarnation:
import * as ko from "knockout";
ko.bindingHandlers["promise"] = {
init(element: HTMLElement, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
let first = true;
function apply(bindingName: string, val: any) {
ko.applyBindingsToNode(element, { [bindingName]: val }, bindingContext);
}
ko.computed(() => {
let bindings = ko.unwrap(valueAccessor());
if(bindings) {
ko.tasks.schedule(() => {
for(let bindingName in bindings) {
let promise = bindings[bindingName] as Promise<any>;
if(promise && promise.then) {
promise.then(val => {
apply(bindingName, val);
first = false;
});
} else {
apply(bindingName, bindings[bindingName]);
first = false;
}
}
});
}
}, null, { disposeWhenNodeIsRemoved: element })();
return {
controlsDescendantBindings: false
};
}
}
ko.virtualElements.allowedBindings["promise"] = true;
I've tried setting "controlsDescendantBindings" to false, which works for some things, but seems to cause a bit of havoc when promise bindings are nested.
Edit 3: As for what I'm trying to do, it's something along the lines of this (note: using Bootstrap).
<ul class="nav navbar-nav">
<!-- ko promise: { if: canAccessFooBar } -->
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown">Foobar</a>
<ul class="dropdown-menu>
<!-- ko promise: { if: canAccessFoo } -->
<li>
<a href="/foo">Foo</a>
</li>
<!-- /ko -->
<!-- ko promise: { if: canAccessBar } -->
<li>
<a href="/bar">Bar</a>
</li>
<!-- /ko -->
</ul>
</li>
<!-- /ko -->
</ul>
Where canAccessFoo, canAccessBar, and canAccessFooBar are promises that resolve to a boolean value.