I know that Aurelia allows us to return a Promise()
from the VM's activate()
method, and if so it'll wait for the promise to resolve before switching to the new view.
But, say I have a view that consists of one or several child components and they all make HTTP requests, how can I know when all children are finished from within my parent component?
Bonus question: Is it correct that only VM's that go in the <router-outlet>
utilize the activate()
method, whereas VM's that are used as custom elements utilize the attached()
method?
Edit: To elaborate a little, here's what one of my pages/routes/main views might look like:
<template>
<main>
<section id="item">
<h1>${item.title}</h1>
<img src="${item.image}">
<p>${item.description}</p>
<ul>
<li repeat.for="si of item.subItems">
${si.subItem}
</li>
</ul>
</section>
<aside>
<author-info></author-info>
<recent-items limit="3"></recent-items>
<random-quote></random-quote>
</aside>
</main>
</template>
I can easily wait for ${item}
to load and then resolve the promise in the main view's activate
method, but that doesn't guarantee that the three child elements in the aside
have loaded. This makes them pop up one after the other and it doesn't look great.
I'd very much like to use Aurelia's built in functionality if at all possible, but I guess I might have to resort to my own loader using the EventAggregator or a two-way binding like Ashley suggested.
Would love to hear from someone on the Aurelia team as to whether this is possible at all?
I'm not sure about such a complex chain with
activate
andattached
, but you can always use Custom Events.In
activate
of parent element:And in the custom element you'll need to trigger this event whenever needed:
And, of course, you'll need to import Event Aggregator
And inject it into both child and parent elements.
Load all the data in the route's
activate()
callbackAs Ashley noted in the comment above, the best strategy is to load all of the data in the parent route and push that data into a custom element via bindings. You mentioned that this would lead to copy/pasted code in each route that contained that element, but we can solve this problem by moving the loading code to a service class and injecting that class into the route. This way, we can keep the code dry while also keep it straightforward and readable.
We'll demonstrate by creating a
<random-quote>
custom element along with a service that will provide us with random quotes.randomQuoteCustomElement.ts
randomQuoteCustomElement.html
randomQuoteService.ts
Next, we'll include the custom element in our view, inject the service into our view model, fetch the data through the service, and have our
activate()
method depend on the returned promise.main.ts
main.html
Because we want our
activate()
function to depend strongly on the results of theRandomQuoteService
, we need to include this code directly in ouractivate()
callback. We can also design the the custom element to allow binding data, but fall back to fetching its own data, leveraging the same service.randomQuoteCustomElement.ts
Here's a working example: https://gist.run/?id=c5570192afe5631355efe6b5da3e44b5
If you have several http calls that must be returned before rendering the view, you could use
Promise.all
, then bind the results to children components. For instance:In this way,
activate()
will await all promises. However, as @AshleyGrant said in his comment, you should be careful with this. This could result in a slow process.