将全局 Object/Model 传递给 Aurelia 中的自定义元素

Pass global Object/Model to custom element in Aurelia

参考以下 post Whosebug Question 我有一个完全不同的场景,我想知道 Aurelia 是否有解决方案。

场景:

我有一个用户模型:

export class User{
    @bindable name: string;
    @bindable address: Address

如你所见,"Address"是一个子模型。

我有一个主视图模型 "registration"。在这个视图模型中,我有一个模型 "user":

export class RegistrationView{
    @bindable user: User

    public attached(){
        this.user = userService.fetchUserFromApi();
    }

除此之外,我还有一个自定义元素 "user-address",其中我有一个 "user-address" 模型(因为我想要专用的封装自定义元素)。

export class userAddress{
    @bindable userAddress: Address

现在我只想从 API 请求一次用户模型并将用户地址发送到自定义元素:

<template>
  <require from="user-address"></require>

  <user-address user.address.bind="${dj.address}"></user-address>

最后,如果用户已经加载,我将(拥有我可以在任何地方使用的专用封装自定义元素)检查附加方法,如果没有,那么自定义元素将加载所有需要的数据:

export class userAddress{
    @bindable userId: string
    @bindable address: Address

    public attached(){
        if(!(typeof this.address === "undefined")){
             this.address = this.addressAPIService.getAddressByUserId(id)
        }
    }
  1. 问题 1:我知道,提到的模板 dj.address.bind 不起作用。但现在我的问题是,我该如何处理这种情况?
  2. 问题 2:如何确保用户对象仅被请求一次?
  3. 我的想法是否合理,是 Aurelia 的想法吗?

如果我对你的问题的理解正确,你只需要某种形式的 client-side 坚持。

如果即使在用户关闭浏览器后您仍需要这种持久性,您将需要使用 localStorage 或对其进行一些封装。有很多不错的插件可用,例如 localForage, LokiJS and a recently developed (still in beta) aurelia plugin aurelia-store

您可能希望将用户的检索封装在某种 UserService 中。这不是 Aurelia 特有的,只是您希望在大多数类型的应用程序中如何执行此操作。

例子

所以在你的视图模型中你可能有这样的东西(跳过一些实现细节,例如检查参数,为简洁起见配置路由器等):

@autoinject()
export class UserViewModel {
  public user: User;

  constructor(private userService: UserService){}

  // happens before bind or attached, so your child views will always have the user in time
  public async activate(params: any): Promise<void> {
    this.user = await this.userService.getUserById(params.id);
  }
}

并且在您的用户服务中:

// singleton will ensure this service lives as long as the app lives
@singleton() 
export class UserService {
  // create a simple cache object to store users
  private cache: any = Object.create(null); 

  constructor(private client: HttpClient) {}

  public async getUserById(id: number): Promise<User> {
    let user = this.cache[id];
    if (user === undefined) {
      // immediately store the user in cache
      user = this.cache[id] = await this.client.fetch(...);
    }
    return user;
  }
}

让你的视图模型变得愚蠢,并在需要加载用户时调用 UserService,让你的服务变得聪明,只在尚未缓存时从 API 中获取它。

我还想指出 attached() 不是您想要获取数据的时候。 attached() 是当你做 DOM 东西(add/remove 元素、样式、其他装饰性东西)。 bind() 最好限制为您在客户端上已有的 grabbing/manipulating 数据。

那么什么时候取数据?

在路由生命周期中的路由视图模型中。那将是 configureRoutercanActivateactivatecanDeactivatedeactivate。这些将在任何 DOM 涉及之前递归解决。

不在您的自定义元素中。或者你很快就会发现自己陷入了通知机制和额外绑定的维护地狱,只是为了让组件可以让彼此知道 "it's safe to render now because I have my data".

如果您的自定义元素可以假定它们在 bind() 发生后就拥有它们的数据,那么一切都会变得 很多 更易于管理。

那么 API 用户调用的调用呢?

比您想象的更多的时候,您可以让一个动作成为一个路线而不是一个直接的方法。您可以无限地嵌套 router-view,它们真的不需要是页面,它们可以像您喜欢的那样精细。

当很少sub-views可以通过特定路线直接访问时,它增加了很多可访问性。它为您提供额外的挂钩来处理授权、未保存更改的警告和排序,它为用户提供 back/forward 导航等

对于所有其他情况:

从 event-triggered 方法调用服务,就像您通常在 activate() 期间所做的那样,除了通常情况下路由器会延迟页面加载直到数据存在,现在您必须为此自己做元素.

最简单的方法是使用 if.bind="someEntityThatCanBeUndefined"。该元素仅在该对象具有值时才会呈现。而且它不需要处理获取数据的基础设施。