最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Return Promise from activate() when customElements have loaded - Stack Overflow

programmeradmin1浏览0评论

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 ponents and they all make HTTP requests, how can I know when all children are finished from within my parent ponent?

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 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 ponents and they all make HTTP requests, how can I know when all children are finished from within my parent ponent?

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?

Share Improve this question edited Jul 14, 2016 at 22:18 powerbuoy asked Jul 12, 2016 at 15:02 powerbuoypowerbuoy 12.9k9 gold badges51 silver badges83 bronze badges 11
  • 1 I think I would caution you to reconsider if you want to tie your parent ponent so tightly to the child ponents? The answer may be yes or no, but it should be given some careful thought. – Ashley Grant Commented Jul 12, 2016 at 15:38
  • That sounds like sound advice. But in this case the parent ponent does nothing but include child ponents. In fact, the .js file is empty as of right now and the .html file does nothing but <custom-element-1></custom-element-1><custom-element-2></custom-element-2> etc so I'm not sure it's that much worse that the JS file also knows about the child ponents? – powerbuoy Commented Jul 12, 2016 at 19:09
  • 1 Maybe the data would be better suited flowing in to the custom elements via binding rather than the elements loading it themselves? It's worth considering. I consider what you're asking to be a code smell that would make me step back and reevaluate what I'm doing before proceeding. – Ashley Grant Commented Jul 12, 2016 at 19:14
  • Hmm, perhaps you're right about that :/ The thing is that many of my custom elements should be re-used in several places. Take for example a "Recent Comments"-ponent that is used on several pages, but in different places, and sometimes with a different number of ments. I'd like to have just one <recent-ments> that I can pass arguments to (limit, title, whatever). This is the child-ponent I would like to load before Aurelia navigates to the page containing the child ponent. Now imagine I have several such ponents. – powerbuoy Commented Jul 12, 2016 at 19:22
  • I wouldn't want every single page using the <recent-ments> element to have to first use the CommentService to fetch the most recent ments - I think that's perfectly suited for the RecentComments ponent. Otherwise I'd just have to copy/paste the CommentService-code into every single page that uses that element. – powerbuoy Commented Jul 12, 2016 at 19:25
 |  Show 6 more ments

3 Answers 3

Reset to default 6

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

As Ashley noted in the ment 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

I'm not sure about such a plex 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.

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 ponents. 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 ponents
}

In this way, activate() will await all promises. However, as @AshleyGrant said in his ment, you should be careful with this. This could result in a slow process.

发布评论

评论列表(0)

  1. 暂无评论