如何找出破坏Angular绑定的方式?

时间:2019-05-16 06:24:54

标签: angular

我有angular app,它支持多个浏览器选项卡,这些选项卡具有2个绑定,其他绑定使用angular-redux @select和另一个{{property}}。该应用程序可以正常运行。但是,我可以通过使用redux-state-sync中间件将我的角度存储配置为使用广播通道而不是localstorage来打破绑定。因此,在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>

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 }));
  }
}

3 个答案:

答案 0 :(得分:6)

使用本地存储频道时,您的应用程序已订阅浏览器storage事件。并且,一旦您的应用程序在一个选项卡中更改了值,它就会更新locastorage,从而在另一选项卡中触发storage事件,以便对其进行更新。

在默认的redux_state_sync频道中,选项卡之间没有这种连接,但频道现在使用{strong>未被zonejs修补的BroadcastChannel ,因此您始终会收到不正确的消息区域。

你能做什么?

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

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;
      ...
    })
});

请注意,您也可以运行cdRef.detectChanges,但是在这种情况下,您可以陷入某些Angular处理程序将在Angular区域之外注册的情况结果是,您将在其他地方调用detectChanges或再次使用zone.run

所以我建议您使用zone.run来确保您位于Angular应用程序的正确区域中

答案 1 :(得分:0)

也许您可以运行此命令以查看是否有任何错误输出到控制台。 --verbose会将所有内容记录到控制台

ng build --prod --aot --verbose 

答案 2 :(得分:0)

似乎没有在角度循环挂钩中运行。

您可以使用import UIKit enum ViewType { case new, approved, rejected } class ViewController { private var viewType: ViewType = .new func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { switch viewType { case .new : let cell = self.addNewTblview.dequeueReusableCell(withIdentifier: "AddInstructorNewCell", for: indexPath) as! AddInstructorNewCell return cell case .approved: let cell = self.addNewTblview.dequeueReusableCell(withIdentifier: "ApprovedCell", for: indexPath) as! ApprovedCell return cell case .rejected: let cell = self.addNewTblview.dequeueReusableCell(withIdentifier: "RejectedCell", for: indexPath) as! RejectedCell return cell } } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { switch viewType { case .new: return 170 case .approved: return 165 case .rejected: return 178 } } @IBAction func btnNewClick(_ sender:UIButton) { viewType = .new tableView.reloadData() } @IBAction func btnapprovedClick(_ sender:UIButton) { viewType = .approved tableView.reloadData() } @IBAction func btnrejectedClick(_ sender:UIButton) { viewType = .rejected tableView.reloadData() } } 类中的方法detectChanges()手动请求角度运动来进行紧缩。

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

ChangeDetectorRef

然后在订阅中 constructor( private ngRedux: NgRedux<IApplicationState>, private actions : LoginActions, private cd: ChangeDetectorRef, ) { const appReducer = combineReducers<IApplicationState>({ login: loginReducer })

detectChanges