Skip to content
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

Sync state across multiple tabs #40

Closed
AirBorne04 opened this issue Jul 6, 2017 · 16 comments
Closed

Sync state across multiple tabs #40

AirBorne04 opened this issue Jul 6, 2017 · 16 comments

Comments

@AirBorne04
Copy link

Is there the option to sync the stores through the localstorage across multiple tabs?

@dojchek
Copy link

dojchek commented Jul 26, 2017

It should already work like that. For me it syncs the storage across multi tabs.

@ferdis
Copy link

ferdis commented Jul 28, 2017

It saves across tabs, but does not synchronize. If the store in tab A changes, the store in tab B will only be updated when the page is reloaded.

I'm not really sure I'd like such side effects in my store, but that's just my 2c.

@AirBorne04
Copy link
Author

I understand what you are saying, but I find that possibility pretty useful, if you think that a user logs in on a primary tab and the secondary tab gets notified. How it is handled in the secondary tab is up to the application, maybe via a specific action. Now i had to do that manually via a window 'storage' event, what is also ok but could be documented or made optional via settings?

@dinvlad
Copy link

dinvlad commented Oct 11, 2017

+1 for this capability, especially because currently changes in an older tab may overwrite updates from the other.

@dinvlad
Copy link

dinvlad commented Oct 12, 2017

Here's a snippet that can be used to sync states based on @AirBorne04's suggestion:

// actions.ts
const STORAGE = '@ngrx/store/storage';

export class Storage implements Action {
  readonly type = STORAGE;
  constructor(readonly payload: string) {}
}

// reducers.ts
import { localStorageSync, rehydrateApplicationState } from 'ngrx-store-localstorage';

export function localStorageSyncReducer(reducer: ActionReducer<any>): ActionReducer<any> {
  return (state: State, action: All) => {
    const keys = ['key1', 'key2'];

    if  (action.type === STORAGE && keys.includes(action.payload)) {
      const rehydratedState = rehydrateApplicationState([action.payload], localStorage, k => k);
      return { ...state, ...rehydratedState };
    }

    return localStorageSync({
      keys,
      rehydrate: true,
    })(reducer)(state, action);
  }
}

// app.component.ts
export class AppComponent implements OnInit {
  constructor(
    private readonly renderer: Renderer2,
    private readonly store: Store<State>,
  ) {}

  ngOnInit() {
    this.renderer.listen('window', 'storage', event => {
      this.store.dispatch(new Storage(event.key));
    });
  }
}

This approach avoids an infinite loop when a storage update in one tab leads to a storage update in another, which in turn causes update in the first one, etc.

It also carefully updates only the relevant part of the state, while treating storage as the source of truth.

@ubcent
Copy link

ubcent commented Oct 13, 2017

Thanks @dinvlad It was helpful for me.

@un33k
Copy link

un33k commented Dec 20, 2017

@dinvlad this works great in dev, however, in production, there is an extra action @ngrx/store/update-reducers which gets in there and updates the store to default state which in-turn resets the localStorage copy.

So, when you reload the page, it logs you out if you were storing token to localStorage, as the token gets wiped out.

@dinvlad
Copy link

dinvlad commented Dec 21, 2017

@un33k strange, I haven't experienced that.

@ubcent
Copy link

ubcent commented Dec 21, 2017

@dinvlad I had the same issue

@dinvlad
Copy link

dinvlad commented Dec 21, 2017

@ubcent maybe something recent. In any case, unfortunately I'm moving to Vue/Vuex so may not be of much help..

@un33k
Copy link

un33k commented Dec 21, 2017

More info here. FYI

@tanyagray
Copy link
Collaborator

Closing as there is a solution posted by @dinvlad above and I'm not sure this is something which should be a default part of this package.

Feel free to post any reasons for re-opening if you feel strongly about this 👍

@dhanraj07
Copy link

It saves across tabs, but does not synchronize. If the store in tab A changes, the store in tab B will only be updated when the page is reloaded.

I'm not really sure I'd like such side effects in my store, but that's just my 2c.

This issue actually exist. Localstorage sync does not update store in application memory as per value updated in localstorge. So when I change store from one tab my localstorage updates. But in another tab the redux (application memory) will still hold the old value. Now if user does not refresh the page and performs some action on site, my localstorage will get updated with the value in redux again and will cause system to misbehave!

@BBlackwo BBlackwo changed the title Sync across different Tabs Sync state across multiple tabs Feb 2, 2021
@BBlackwo
Copy link
Collaborator

BBlackwo commented Feb 2, 2021

Added the above solution to a known workarounds section in the README

@rattkin
Copy link

rattkin commented Apr 15, 2021

This seem to no longer work in this form. The Action type does not have a payload.

@versave
Copy link

versave commented May 30, 2022

Works nicely in the following format.
Tested with Angular 13

// actions.ts
const storageActionType = '@ngrx/store/storage';

export const storageAction = createAction(
	storageActionType,
	props<{ payload: string }>()
);

type PayloadAction = Action & {
	payload: unknown;
};

// reducers.ts
import { localStorageSync, rehydrateApplicationState } from 'ngrx-store-localstorage';

export const localStorageSyncReducer = (reducer: ActionReducer<any>): ActionReducer<any> => (state: State<any>, action: Action): any => {
	const keys = ['key1', 'key2'];

	const isPayloadAction = 'payload' in action;
	const payloadAction: PayloadAction = action as PayloadAction;

	if (action.type === storageActionType && isPayloadAction && keys.includes(payloadAction.payload as string)) {
		const rehydratedState = rehydrateApplicationState([payloadAction.payload] as Keys, localStorage, k => k, true);
		return { ...state, ...rehydratedState };
	}

	return localStorageSync({
		keys,
		rehydrate: true,
	})(reducer)(state, action);
};

// app.component.ts
export class AppComponent implements OnInit {
  constructor(
    private readonly renderer: Renderer2,
    private readonly store: Store<State>,
  ) {}

  ngOnInit() {
    this.renderer.listen('window', 'storage', event => {
      this.store.dispatch(storageAction({ payload: event.key as string }));
    });
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests