-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(Devtools): Support for persist, lock, pause #955
Conversation
@@ -279,7 +279,10 @@ export class StoreRouterConnectingModule { | |||
|
|||
if (this.router.url !== this.storeState[this.stateKey].state.url) { | |||
this.navigationTriggeredByDispatch = true; | |||
this.router.navigateByUrl(this.storeState[this.stateKey].state.url); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a separate PR. I don't think this will work for SSR apps also because you can't guarantee this will be finished before the next navigation starts.
I removed the router-store-commit from the PR and instead opened #960 . What do you think about the other changes? |
8bd2704
to
69f11c1
Compare
@brandonroberts any plans if/when this will be merged? |
I'll take a look this week. I'd like something a little more predictable for waiting for the reducers to load when importing, but we'll see. |
Thanks for the update! Regarding the import and "wait on lazy loaded reducers": Since lazy loaded features/reducers are directly tied to lazy loaded modules - what about injecting the
I could add a commit implementing this behavior. What do you think? Side note: This would not solve all import issues with lazy loading. What about a state history which does some navigation action and only at this point a new reducer is loaded (#919)? But I think this is far more complex to think through / implement and does not belong into this PR. |
@brandonroberts any updates on this? What do you think about the more predictable "wait for reducers by using Router"-proposal? |
Sorry to ask again but is there any timeline when (or if at all) this will be merged? Any feedback would be appreciated. |
Apologies for getting behind on this one. Will you rebase against master? |
Thanks for the update, I will do it next week. |
…igation if there is one
I rebased the commits and also added the suggested import router enhancement (#955 (comment)). Please have a look. |
…oaded reducers time to instantiate
I resolved some new merge conflicts. The build fails but this seems to have nothing to do with the commits (bazel ran out of memory and crashed). |
map(change => this.unwrapAction(change.payload)), | ||
concatMap((action: any) => { | ||
if (action.type === IMPORT_STATE) { | ||
if (this.router) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the router should be integrated here. The route may need the state we are importing, but this prevents that from happening until the navigation is over.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can listen for any @ngrx/store/update-reducers
actions and delay the import until none have been received within a span of time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
liftedActions$
retrieves the IMPORT_STATE-action before it is passed to the reducer. So as long as IMPORT_STATE is not emitted by liftedActions$
, the import will not be triggered. update-reducers
etc will be fired before the route is loaded because that happens on a different stream of actions (actions$
), so there is no block. So in my opinion this is fine - or maybe I don't quite understand what you mean 😃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's not quite clear what I mean, so I'll expand on it:
Situation 1: Router is used by user. User clicks the "import"-button of the redux devtools and manually imports a state
The added logic will not matter because router.navigated will be true (the first navigation already happened) , therefore nothing to wait on, therefore the IMPORT action is fired straight away.
Situation 2: Router is used by user. User turned on the "persist"-feature and reloads.
The added logic now matters. router.navigated will be false because the router is in the process of navigating when the action comes in. What that means is that the modules connected to that route are still getting instantiated, so for example components/reducers/effects for that route are not there yet. So if we just let the IMPORT action pass through, the reducer would import the state too soon, some actions would get ignored because the reducers handling it do not exist yet. That's why we wait until the NavigationEnd-event of the router is fired. At this point we know all the initialization logic is done.
There will be no blocking of state needed for the router because initial reducers/effects/state will be loaded during navigation. These things do not come through the liftedActions$
stream, so blocking further events there until we are finished importing is not an issue. Also something like "oh you are not logged in, I will redirect you" is fine because, well, we just import the state after that and the application will get back to the correct route.
Situation 3: Router is not used by user.
The whole router logic does not matter now, we wait 1 second just to make sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand what's going on here, but I'd still rather support this in a consistent way without the router if possible.
Idea: After the IMPORT_STATE action comes in, wait some amount of time, then switch to listening for update-actions and debounce the IMPORT_STATE action. We can inject and listen to the store actions being dispatched.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like this?
concatMap((action: any) => {
if(action.type === IMPORT_STATE) {
return actions$.pipe(
ofType(UPDATE), // '@ngrx/store/update-reducers'
debounceTime(1000),
map(() => action)
);
}
...
This way, no router dependency + when there is no UPDATE within 1 second, it is assumed that everything is loaded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's pretty close, we also need to account for using the import button from the extension.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot know whether the IMPORT_STATE action is fired because of the persist option turned on or because of a manual import. But that's no harm, the user just has a minimal delay (of 1 second) until the manual import starts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha. I had debounce
in my mind and not debounceTime
. What happens if we never get an update action though? The example being that they import from the extension, or possible don't have any feature states.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, a race condition / timeout in the likes of "there is no UPDATE action at all" that fires after 1 second is needed.
I will implement it and update the PR.
const extensionOptions: ReduxDevtoolsExtensionConfig = { | ||
instanceId: instanceId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The instanceId was set randomly at every start, so the devtools extension did not see any connection between the state before and after the reload (ids never matched). Removing it means the devtools extension takes care of that itself. If the id is not removed on ngrx' side, the persist feature therefore would not work.
@dummdidumm we are still working through the Bazel memory issues, but at long as the tests pass, its fine for now. I left you some feedback on the PR. Let me know what you think. |
I added you some feedback, let me know what you think. |
@@ -17,6 +28,8 @@ import { | |||
sanitizeStates, | |||
unliftState, | |||
} from './utils'; | |||
import { UPDATE } from '../../store/src'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be @ngrx/store
instead of a relative import
Closes #853:
Devtools now support:
Fixes part of #919:
When importing a state, there is now a one second delay to give lazy loaded reducers time to instantiate.
Think of this situation: User is on a page where a reducer was lazy loaded. He wants to use the persist feature. The page is reloaded. Now the "import state" action is fired from the devtools, but the actions are replayed too soon because the lazy loaded reducer is not instantiated yet. That means, the actions are replayed with incomplete reducers. To fix this, a small delay is added before importing a state.