Return Promise from activate() when customElements have loaded

巧了我就是萌 提交于 2019-12-06 08:53:04

问题


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?


回答1:


I'm not sure about such a complex chain with activate and attached, but you can always use Custom Events.

In activate of parent element:

return new Promise((resolve, reject) => {
  var subscription = this.eventAggregator.subscribe('child-element-loaded', e => {
    subscription.dispose();
    resolve();
  });
});

And in the custom element you'll need to trigger this event whenever needed:

this.eventAggregator.publish('child-element-loaded');

And, of course, you'll need to import Event Aggregator

import {EventAggregator} from 'aurelia-event-aggregator';

And inject it into both child and parent elements.




回答2:


Load all the data in the route's activate() callback

As 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

export class RandomQuoteCustomElement {
    @bindable quote: IQuote;
}

randomQuoteCustomElement.html

<template>
  <i>${quote.text}</i>
  <strong>${quote.author}</strong>
</template>

randomQuoteService.ts

export class RandomQuoteService {

  getRandomQuote: Promise<IQuote>() {
    return this.http.fetch('api/quote/random')
      .then((response) => response.json()) 
    });
  }
}

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

import { RandomQuoteService} from './randomQuoteService';

@inject(RandomQuoteService)
export class MainViewModel {

  quote: IQuote;

  constructor(quotes: RandomQuoteService) {
    this.quotes = quotes;
  }

  activate() {
    return Promise.all([
      this.quotes.getRandomQuote()
        .then((quote) => this.quote = quote)
    ]);
  }
}

main.html

<template>
  <require from="./randomQuoteCustomElement"></require>
  <random-quote quote.bind="quote"></random-quote>
</template>

Because we want our activate() function to depend strongly on the results of the RandomQuoteService, we need to include this code directly in our activate() 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

export class RandomQuoteCustomElement {

  @bindable quote;

  constructor(quotes) {
    if (!this.quote) {
      quotes.getRandomQuote()
        .then((quote) => !this.quote && this.quote = quote);
    }
  }
}

Here's a working example: https://gist.run/?id=c5570192afe5631355efe6b5da3e44b5




回答3:


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:

activate () {

   let promise1 = new Promise()...;
   let promise2 = new Promise()...;
   let promise3 = new Promise()...;

   return Promise.all([promise1, promise2, promise3]);
   //bind the results to children components
}

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.



来源:https://stackoverflow.com/questions/38332583/return-promise-from-activate-when-customelements-have-loaded

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!