简体   繁体   中英

Pass global Object/Model to custom element in Aurelia

referring to the following post StackOverflow Question I have a quite different scenario where I want to know if Aurelia has a solution for.

Scenario:

I have a user model:

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

As you can see, "Address" is a sub-model.

I have a main view-model "registration". In this view model I have a model "user":

export class RegistrationView{
    @bindable user: User

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

In addition to that I have a custom-element "user-address" where I have a "user-address"-model (because I want to have dedicated encapsulated custom-elements).

export class userAddress{
    @bindable userAddress: Address

Now I want to request the user model only once from the API and send the user address it to the custom-element:

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

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

Finally I would (to have dedicated encapsulated custom-elements that I can use everywhere) check in attached method if the user is already load and if not then the custom-element would load all needed data:

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

    public attached(){
        if(!(typeof this.address === "undefined")){
             this.address = this.addressAPIService.getAddressByUserId(id)
        }
    }
  1. Problem 1 : I know, that the mentioned template dj.address.bind doesn't work. But now my question is, how can I handle that situation?
  2. Problem 2 : How do I assure, that the user object is only requested once?
  3. Does my concept makes sense and does it is the idea of Aurelia?

If I understand your problem correctly, you simply need some form of client-side persistence.

If you need this persistence even after the user closed the browser, you'll want to use either localStorage or some encapsulation thereof. There are many good plugins available such as localForage , LokiJS and a recently developed (still in beta) aurelia plugin aurelia-store

You probably want to encapsulate the retrieval of your user in a UserService of some sort. This is nothing specific to Aurelia, just generally how you want to do this in most types of applications.

Example

So in your viewmodel you might have something like this (skipping some of the implementation details such as checking the params, configuring the router etc for brevity):

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

And in your userservice:

// 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;
  }
}

Let your view model just be dumb and call the UserService whenever it needs to load a user, and let your service be clever and only fetch it from the API when it's not already cached.

I'd also like to point out that attached() is not when you want to be grabbing data. attached() is when you do DOM stuff (add/remove elements, style, other cosmetic things). bind() is best restricted to grabbing/manipulating data you already have on the client.

So when to fetch data?

In your routed view models during the routing lifecycle. That'll be configureRouter , canActivate , activate , canDeactivate and deactivate . These will resolve recursively before any of the DOM gets involved.

Not in your custom elements. Or you'll soon find yourself in maintenance hell with notification mechanisms and extra bindings just so components can let eachother know "it's safe to render now because I have my data".

If your custom elements can assume tehy have their data once bind() occured, everything becomes a lot simpler to manage.

And what about API calls invoked by users?

More often than you think, you can let an action be a route instead of a direct method. You can infinitely nest router-view s and they really don't need to be pages, they can be as granular as you like.

It adds a lot of accessibility when little sub-views can be directly accessed via specific routes. It gives you extra hooks to deal with authorization, warnings for unsaved changes and the sorts, it gives the user back/forward navigation, etc.

For all other cases:

Call a service from an event-triggered method like you normally would during activate() , except whereas normally the router defers page loading until the data is there, now you have to do it yourself for that element.

The easiest way is by using if.bind="someEntityThatCanBeUndefined" . The element will only render when that object has a value. And it doesnt need to deal with the infrastructure of fetching data.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM