Angular 如何找出破坏角绑定的因素?

Angular 如何找出破坏角绑定的因素?,angular,Angular,我有angular支持多个浏览器选项卡,其中有两个绑定,其他使用angular redux@select和另一个{{property}。该应用程序按预期工作。但是,我可以通过使用redux状态同步中间件将angular store配置为使用广播频道而不是本地存储来打破绑定。因此,在app.component.ts中将一行替换为其下方的注释行会破坏第二个浏览器窗口中的绑定。这看起来真的很奇怪,我不知道如何找出为什么这两个绑定都会从看似无关的更改中中断 编辑:参考yurzui的答案:状态通过选项卡同

我有angular支持多个浏览器选项卡,其中有两个绑定,其他使用angular redux@select和另一个{{property}。该应用程序按预期工作。但是,我可以通过使用redux状态同步中间件将angular store配置为使用广播频道而不是本地存储来打破绑定。因此,在app.component.ts中将一行替换为其下方的注释行会破坏第二个浏览器窗口中的绑定。这看起来真的很奇怪,我不知道如何找出为什么这两个绑定都会从看似无关的更改中中断

编辑:参考yurzui的答案:状态通过选项卡同步,也使用广播频道选项。只有绑定不再工作了。按下按钮时,可以在不同浏览器选项卡的控制台输出中验证这一点

app.component.html

<div style="text-align:center">
  <div *ngIf="(isLoggedIn | async)">
    <p>this appears when logged in!</p>
  </div>
  <h1>
    App status is {{ statusTxt }}!
  </h1>
  <button (click)="toggleLogin()">{{ buttonTxt }}</button>
  <h2>You can either login 1st and then open another tab or you can 1st open another tab and then login. Either way the both tabs should be logged in and look the same</h2>
  <h2>After testing multiple tabs with current 'localstorage' broadcastChannelOption type, you can change it to 'native' in app.component.ts and you'll see that bindings do not work anymore</h2>
</div>

这将在登录时显示

应用程序状态为{{statusTxt}}! {{buttonxt}} 您可以先登录然后打开另一个选项卡,也可以先打开另一个选项卡然后登录。无论哪种方式,两个选项卡都应该登录并看起来相同 在使用当前的“localstorage”选项类型测试多个选项卡后,您可以在app.component.ts中将其更改为“native”,您将看到绑定不再起作用
app.component.ts

import { Component, Injectable } from '@angular/core';
import { createStateSyncMiddleware, initStateWithPrevTab, withReduxStateSync } from 'redux-state-sync';
import { combineReducers, Action, createStore, applyMiddleware } from 'redux';
import { NgRedux, select } from '@angular-redux/store';
import { Observable } from "rxjs";

export interface LoginToggle {
  isLoggedIn : boolean
}

export interface LoginToggleAction extends Action {
  loginToggle: LoginToggle;
}

@Injectable()
export class LoginActions {
  static TOGGLE_LOGIN = 'TOGGLE_LOGIN';

  toggleLogin(loginToggle: LoginToggle): LoginToggleAction {
    return { 
        type: LoginActions.TOGGLE_LOGIN, 
        loginToggle 
    };
  }
}

export interface ILoginState {
  readonly isLoggedIn: boolean;
}

export interface IApplicationState {
  login: ILoginState;
}

export const INITIAL_STATE : IApplicationState = {
   login : { isLoggedIn: false } 
}

export function loginReducer(oldState: ILoginState = { isLoggedIn : false } as any, action: Action) : ILoginState {
  switch (action.type) {
      case LoginActions.TOGGLE_LOGIN: {
          console.log('in reducer');
          const toggleAction = action as LoginToggleAction;
          return {
              ...oldState,
              isLoggedIn: toggleAction.loginToggle.isLoggedIn
          } as ILoginState;       
      }
      default: {
        return oldState;
    }
  }
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  statusTxt = 'logged out';
  subscription;
  loginStatus = false;
  buttonTxt = 'Login';

  @select((state: IApplicationState) => state.login.isLoggedIn) isLoggedIn: Observable<boolean>;

  constructor(private ngRedux: NgRedux<IApplicationState>, 
    private actions : LoginActions) {

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

    const rootReducer = withReduxStateSync(appReducer);
    const store = createStore(rootReducer,
      applyMiddleware(createStateSyncMiddleware({ broadcastChannelOption: { type: 'localstorage' } })));

    /* !! Both bindings break if i replace the row above with this: 
    applyMiddleware(createStateSyncMiddleware())); */

    initStateWithPrevTab(store);

    ngRedux.provideStore(store);

    this.subscription = this.ngRedux.select<ILoginState>('login')
      .subscribe(newState => {
        console.log('new login state = ' + newState.isLoggedIn);
        this.loginStatus = newState.isLoggedIn;
        if (newState.isLoggedIn) {
          this.statusTxt = 'logged in!';
          this.buttonTxt = 'Logout';
        }
        else { 
          this.statusTxt = 'logged out!';
          this.buttonTxt = 'Login';
        }
        console.log('statusTxt = ' + this.statusTxt);
    });
  }

  toggleLogin(): void {
    this.ngRedux.dispatch(this.actions.toggleLogin({ isLoggedIn: !this.loginStatus }));
  }
}
从'@angular/core'导入{Component,Injectable};
从“redux状态同步”导入{createStateSyncMiddleware,initStateWithPrevTab,withReduxStateSync};
从'redux'导入{combinereducer,Action,createStore,applyMiddleware};
导入{NgRedux,从'@angular redux/store'中选择};
从“rxjs”导入{observeable};
导出接口登录切换{
伊斯洛格丁:布尔型
}
导出接口LoginToggleAction扩展操作{
登录切换:登录切换;
}
@可注射()
导出类登录{
静态切换_登录='切换_登录';
切换登录(登录切换:登录切换):登录切换{
返回{
类型:LoginActions.TOGGLE\u登录,
后勤保障
};
}
}
导出接口ILoginState{
只读isLoggedIn:布尔值;
}
导出接口IAApplicationState{
登录:ILoginState;
}
导出常量初始状态:IAApplicationState={
登录名:{isLoggedIn:false}
}
导出函数loginReducer(oldState:ILoginState={isLoggedIn:false}如有,操作:操作):ILoginState{
开关(动作类型){
案例登录选项。切换\u登录:{
console.log('in reducer');
const togglection=作为logintoglection的操作;
返回{
…旧州,
isLoggedIn:togglection.loginToggle.isLoggedIn
}作为木犀草素;
}
默认值:{
返回旧状态;
}
}
}
@组成部分({
选择器:'应用程序根',
templateUrl:“./app.component.html”,
样式URL:['./app.component.css']
})
导出类AppComponent{
statusTxt='注销';
订阅
loginStatus=false;
ButtonText='Login';
@选择((state:iaapplicationstate)=>state.login.isLoggedIn)isLoggedIn:Observable;
构造函数(私有ngRedux:ngRedux,
私人行动:登录){
const appReducer=组合减速机({
登录名:loginReducer
})
const rootReducer=withReduxStateSync(appReducer);
const store=createStore(rootReducer,
applyMiddleware(createStateSyncMiddleware({broadcastChannelOption:{type:'localstorage'}}));
/*!!如果我将上面的行替换为以下内容,则两个绑定都会断开:
applyMiddleware(createStateSyncMiddleware())*/
initStateWithPrevTab(存储);
ngRedux.provideStore(商店);
this.subscription=this.ngRedux.select('login')
.订阅(newState=>{
log('newlogin state='+newState.isLoggedIn);
this.loginStatus=newState.isLoggedIn;
if(newState.isLoggedIn){
this.statusTxt='已登录!';
this.buttonText='Logout';
}
否则{
this.statusTxt='logged out!';
this.buttonText='Login';
}
console.log('statusTxt='+this.statusTxt);
});
}
toggleLogin():void{
this.ngRedux.dispatch(this.actions.toggleLogin({isLoggedIn:!this.loginStatus}));
}
}

当您使用localstorage频道时,您的应用程序将订阅浏览器事件。一旦您的应用程序更改了一个选项卡中的值,它就会更新locastorage,而locastorage会在另一个选项卡中触发
存储
事件,因此它会被更新

使用默认的
redux\u state\u sync
频道,您在选项卡之间没有这种连接,但频道现在使用的不是由zonejs修补的,所以您总是会收到不在角度区域的消息

你能做什么

在订阅时在角度区域内手动运行代码

constructor(...private zone: NgZone) {

this.subscription = this.ngRedux.select<ILoginState>('login')
  .subscribe(newState => {
    this.zone.run(() => {  // enforce the code to be executed inside Angular zone
      console.log('new login state = ' + newState.isLoggedIn);
      this.loginStatus = newState.isLoggedIn;
      ...
    })
});
构造函数(…专用区域:NgZone){
this.subscription=this.ngRedux.select('login')
.订阅(newState=>{
this.zone.run(()=>{//强制执行要在角度区域内执行的代码
log('newlogin state='+newState.isLoggedIn);
this.loginStatus=newState.isLoggedIn;
...
})
});
请注意,您也可以运行cdRef.detectChanges,但在这种情况下,您可能会遇到这样的情况,即某些角度处理程序将在角度区域之外注册
,因此您将在其他位置调用detectChanges,或再次使用区域。运行


因此,我建议您使用zone.run,以确保您在Angular application(角度应用程序)中处于正确的区域。看起来它没有在Angular cycle挂钩中运行

您可以手动请求angular使用
ChangeDetectChanges()
类中的方法运行ckech

您必须在构造函数中添加
ChangeDetectorRef

 constructor(
    private ngRedux: NgRedux<IApplicationState>, 
    private actions : LoginActions,
    private cd: ChangeDetectorRef,
   ) {

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

谢谢你的回答,但是你试过了吗?标签之间应该有广播频道连接。它也适用于som
this.subscription = this.ngRedux.select<ILoginState>('login')
      .subscribe(newState => {
        console.log('new login state = ' + newState.isLoggedIn);
        this.loginStatus = newState.isLoggedIn;
        if (newState.isLoggedIn) {
          this.statusTxt = 'logged in!';
          this.buttonTxt = 'Logout';
        } else { 
          this.statusTxt = 'logged out!';
          this.buttonTxt = 'Login';
        }
        console.log('statusTxt = ' + this.statusTxt);
        this.cd.detectChanges();
    });