Why redux @select does not work even though the st

2019-07-24 21:55发布

问题:

EDIT - This all works if I configure redux-state-sync to use localstorage. I made a repository https://bitbucket.org/em-aitch/sync-app/src/master/ that contains example of working code. If broadcastchannel type (app.component.ts : 76) is changed from 'localstorage' to 'native' the bindings (both @select and {{ property }} don't work anymore!

EDIT2 - Question How to find out what breaks Angular binding? answers this question

I have setup using redux-state-sync described below. The redux state change is synced to other browser window but @select does not work.

When I log in in one window app-nav-menu appears because ngIf evaluates true. Also both subsriptions write to console:

new login state = true                       app.component.ts:20
subsribed to local observable : true         app.component.ts:24

However other browser window does not work the same way even though the login state change done in the other window is synced there. Console ouput is the same:

new login state = true                       app.component.ts:20
subsribed to local observable : true         app.component.ts:24

The problem however is that the app-nav-menu DOES NOT appear. Below first is the code directly related to this and in the end the redux definitions.

app.component.html

<body>
  <app-nav-menu *ngIf="(isLoggedIn | async)"></app-nav-menu>
  <div class="container">
    <router-outlet></router-outlet>
  </div>
</body>

app.component.ts

export class AppComponent {
  title = 'app';
  @select((state: IApplicationState) => state.login.isLoggedIn) isLoggedIn: Observable<boolean>;
  subscription;

  constructor(private ngRedux: NgRedux<IApplicationState>) {
    this.subscription = this.ngRedux.select<ILoginState>('login')
    .subscribe(newState => {
      console.log('new login state = ' + newState.isLoggedIn);
    });

    this.isLoggedIn.subscribe(newState =>
      console.log('subsribed to local observable : ' + newState));
  }
}

app.module.ts

import { createStateSyncMiddleware, initStateWithPrevTab } from 'redux-state-sync';
import { Store, createStore, applyMiddleware } from 'redux';

export const store: Store<IApplicationState> = createStore(
  rootReducer,
  applyMiddleware(createStateSyncMiddleware())
);

export class AppModule {
  constructor(ngRedux: NgRedux<any>) {
    ngRedux.provideStore(store);
    initStateWithPrevTab(store);
  }
}

store/index.ts

import { combineReducers  } from 'redux'
import { ILoginState, loginReducer } from './login'
import { withReduxStateSync } from 'redux-state-sync'

export interface IApplicationState {
  login: ILoginState;
}

export const INITIAL_STATE : IApplicationState = {
   login : { isLoggedIn: false, tokenInfo : null } 
}

const appReducer = combineReducers<IApplicationState>({
  login: loginReducer
})

export const rootReducer = withReduxStateSync(appReducer);

Like I said in edit in the top. This works if I configure this to use localstorage:

applyMiddleware(createStateSyncMiddleware( 
  { broadcastChannelOption: { type: 'localstorage' } })));

回答1:

If the state is already in sync, likely the problem is not in redux-state-sync, I would like to help you to solve the problem, however, I have limited knowledge of Angular and @select.

But I can share with you that how redux-state-sync works to sync the data across the browser tabs.

  1. Three actions will be triggered during the initialization.
const getIniteState = () => ({ type: GET_INIT_STATE });
const sendIniteState = () => ({ type: SEND_INIT_STATE });
const receiveIniteState = state => ({ type: RECEIVE_INIT_STATE, payload: state });
  1. The tabs that opened after the first tab will dispatch GET_INIT_STATE action to the other tab's store, once they received this action, they will dispatch SEND_INIT_STATE along with there existing state. At the same time your last opened tab will receive this SEND_INIT_STATE event along with the other tab's entire state and triggers RECEIVE_INIT_STATE with the state.

  2. Basically, withReduxStateSync will listen on RECEIVE_INIT_STATE action, and replace the entire state with this action's payload.

export const withReduxStateSync = appReducer =>
  ((state, action) => {
    let initState = state;
    if (action.type === RECEIVE_INIT_STATE) {
      initState = action.payload;
    }
    return appReducer(initState, action);
  });
  1. All the other actions that dispatched from your app will be dispatched in all the opening browser tabs with exact action type and payload.

From what I see, the problem that you are facing is probably the observable is not detecting any changes on the state, so that it doesn't trigger the update.

Hope this helps, sorry that I couldn't give you more useful suggestions. Let me know if you need more information from me and if you solved this problem please let me know as well. ;-)