Aurelia-store and app lifecycle


#1

Hi!

I’m working on an app where I’d like to populate the store with some values from a service in app.ts, so I don’t have to do this request for every view that uses the data.

However, I find that I’m having troubles reading values from the store, as it seems to be undefined at the time I’m trying to access it.

Here a rough outline of the scenario:

  1. From App.ts I dispatch two actions; an async action (I’ve tried multiple lifecycle methods to dispatch from here) and a regular function action.
  2. Looking at redux devtools the store has been populated.
  3. When doing a simple console.log from any consuming component, it appears that the async part of the state has not been added yet. (The slice of the state that is generated by the non-async action is populated at the time of accessing it)

Now, I’ve tried both an async / await approach before dispatching;
const result = await apiCall();
this.store.dispatch('TheAction', result);

as well as;
this.apiCall().then(result => {
this.store.dispatch('TheAction', result);
}

Both of them yielding the same empty result when trying to access it from another viewmodel than app.ts.

I even tried implementing the async action, as described in the docs, but it yields the same results (or lack thereof…)

Should this be the case, or am I simply doing something wrong here?


#2

Could you create a minimal sample showing the issue?


#3

Here you go:
https://github.com/nwinger/aureliastoreissue


#4

So the Problem is in this component as far as I can see right? https://github.com/nwinger/aureliastoreissue/blob/master/src/async/async.ts

You want to have access in the constructor but are using the connecTo decorator with a setup inside attached. Since this hook gets triggered far after the constructor call the local variable is not yet set. You would have to create a manual subscription, as described in the docs, inside the constructor and would have access once the subscription callback is executed.


#5

Hi - I’ve pushed an update to the repo,

Tried your approach, and I still experience the issue where functions are being run before the store has been populated with the data. (See home.ts in particular)

Two issues:

  1. Home is the default route, which is still not able to access the data in time
  2. If I past the async url (http://localhost:8081/async) directly, it’s not able to access the data in time

However, If I open the default home route, and then navigate to to /async, I’m able to access the data from the constructor.


#6

for 1. this can’t work as you’re trying to access the data right away. The subscription is what it is, a subscription, means it will get called once the state is there. Since your home is called by the router, a option would be to move this.functionDependingOnState(); into the activate hook of home. Depending on whether your state is async or not this should resolve by then.
2. same applies here. Just subscribing doesn’t necessarily mean that the callback is executed right away.

The Aurelia Store plugin is working with RxJS under the hood which promotes a reactive system. Reactive means you setup listeners to react to a change. That of course means that you can’t really do sync code, which is a good thing. By keeping your system reactive you tell your system what to do when X happens instead of imperatively telling what to do right now as that has to be repeated over and over again.

If you really need to synchronously access the current state, which again is a bad practice, but nevertheless you can do it as this:

...
constructor(private store: Store<State>) {
  const currentState = (this.store as any)._state.getValue();
  // use the current state
}

this will get you the currently active store. Again don’t do this if not absolutely necessary and in your specific case it would just point to the old state.


#7

Thanks for the clarifications @zewa666! :slight_smile:

Will see if I can refactor the dependant logic to run inside another lifecycle hook instead, as I need to populate the store with data from the API first.


#8

Do you do the prepopulation as part of the initial state or a separate API request and then firing a new dispatch? Maybe to make it more clear … do you request/obtain the data before aurelia.start or after the app is already launched?


#9

I fire a separate API request after the app has been launched. (From app.ts)

     return aurelia.start().then(() => {
         authService = aurelia.container.get(AuthenticationService);
         const root = authService.isLoggedIn() ? 'app' : 'loginRoot';
         aurelia.setRoot(PLATFORM.moduleName(root));
      });

Guess I could possibly do it here, before setting the root?


#10

Yes perhaps before the root and make sure to await the dispatch call so its properly settled.