当自定义元素加载时,从 activate() 返回 Promise

Return Promise from activate() when customElements have loaded

本文关键字:activate 返回 Promise 自定义 元素 加载      更新时间:2023-09-26

我知道Aurelia允许我们从VM的activate()方法返回Promise(),如果是这样,它将等待承诺解析,然后再切换到新视图。

但是,假设我有一个由一个或多个子组件组成的视图,并且它们都发出 HTTP 请求,我怎么知道所有子组件何时从我的父组件中完成?

奖励问题:只有进入<router-outlet>的 VM 使用 activate() 方法,而用作自定义元素的 VM 使用 attached() 方法是否正确?

编辑:为了详细说明,这是我的一个页面/路线/主视图可能的样子:

<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>

我可以轻松地等待${item}加载,然后在主视图的 activate 方法中解析 promise,但这并不能保证aside中的三个子元素已加载。这使得它们一个接一个地弹出,看起来不太好。

如果可能的话,我非常想使用 Aurelia的内置功能,但我想我可能不得不使用 EventAggregator 或 Ashley 建议的双向绑定来求助于我自己的加载器。

很想听听Aurelia团队中的某个人关于这是否可能?

加载路由activate()回调中的所有数据

正如 Ashley 在上面的评论中指出的那样,最佳策略是加载父路由中的所有数据,并通过绑定将该数据推送到自定义元素中。您提到这会导致在包含该元素的每个路由中复制/粘贴代码,但我们可以通过将加载代码移动到服务类并将该类注入路由来解决此问题。这样,我们可以保持代码干燥,同时保持其简单易读。

我们将通过创建一个<random-quote>自定义元素以及一个将为我们提供随机报价的服务来演示。

randomQuoteCustomElement.ts

export class RandomQuoteCustomElement {
    @bindable quote: IQuote;
}

randomQuoteCustomElement.html

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

随机报价服务.ts

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

接下来,我们将在视图中包含自定义元素,将服务注入视图模型,通过服务获取数据,并使activate()方法依赖于返回的承诺。

主目录

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)
    ]);
  }
}

主.html

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

因为我们希望我们的activate()函数强烈依赖于RandomQuoteService的结果,所以我们需要直接在activate()回调中包含此代码。我们还可以设计自定义元素以允许绑定数据,但回退到获取自己的数据,利用相同的服务。

randomQuoteCustomElement.ts

export class RandomQuoteCustomElement {
  @bindable quote;
  constructor(quotes) {
    if (!this.quote) {
      quotes.getRandomQuote()
        .then((quote) => !this.quote && this.quote = quote);
    }
  }
}

下面是一个工作示例:https://gist.run/?id=c5570192afe5631355efe6b5da3e44b5

我不确定如此复杂的链具有activateattached,但您可以随时使用自定义事件。

在父元素activate

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

在自定义元素中,您需要在需要时触发此事件:

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

当然,您需要导入事件聚合器

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

并将其注入到子元素和父元素中。

如果在呈现视图之前必须返回多个 http 调用,则可以使用 Promise.all ,然后将结果绑定到子组件。例如:

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
}

这样,activate()将等待所有的承诺。但是,正如@AshleyGrant在他的评论中所说,您应该小心这一点。这可能会导致过程缓慢。