Infinite loop when checking roles. VueJS

ぃ、小莉子 提交于 2021-01-28 01:33:32

问题


There is a navigation bar where elements are displayed depending on the user's role:

<b-navbar-toggle
    right
    class="jh-navbar-toggler d-lg-none"
    href="javascript:void(0);"
    data-toggle="collapse"
    target="header-tabs"
    aria-expanded="false"
    aria-label="Toggle navigation">
  <font-awesome-icon icon="bars"/>
</b-navbar-toggle>

<b-collapse is-nav id="header-tabs">
  <b-navbar-nav class="ml-auto">
    <b-nav-item-dropdown
        right
        id="pass-menu"
        v-if="hasAnyAuthority('ROLE_USER') && authenticated"
        :class="{'router-link-active': subIsActive('/pass')}"
        active-class="active"
        class="pointer">
                <span slot="button-content" class="navbar-dropdown-menu">
                    <font-awesome-icon icon="ticket-alt"/>
                    <span>First element</span>
                </span>
    </b-nav-item-dropdown>

    <b-nav-item-dropdown
        right
        id="dictionaries-menu"
        v-if="hasAnyAuthority('ROLE_ADMIN') && authenticated"
        :class="{'router-link-active': subIsActive('/dictionary')}"
        active-class="active"
        class="pointer">
                <span slot="button-content" class="navbar-dropdown-menu">
                    <font-awesome-icon icon="book"/>
                    <span>Second element</span>
                </span>
    </b-nav-item-dropdown>
  </b-navbar-nav>

If I set the same roles for both elements, for example ROLE_USER, everything works correctly. But when the roles are different for two elements, an infinite loop begins and everything freezes.

Component for checking roles:

@Component
export default class JhiNavbar extends Vue {
  @Inject('loginService')
  private loginService: () => LoginService;

  @Inject('accountService') private accountService: () => AccountService;
  public version = VERSION ? 'v' + VERSION : '';
  private currentLanguage = this.$store.getters.currentLanguage;
  private languages: any = this.$store.getters.languages;
  private hasAnyAuthorityValue = false;

  created() {}

  public get authenticated(): boolean {
    return this.$store.getters.authenticated;
  }

  public hasAnyAuthority(authorities: any): boolean {
    this.accountService()
      .hasAnyAuthorityAndCheckAuth(authorities)
      .then(value => {
        this.hasAnyAuthorityValue = value;
      });
    return this.hasAnyAuthorityValue;
  }
}

Service for working with user account:

export default class AccountService {
  constructor(private store: Store<any>, private router: VueRouter) {
    this.init();
  }

  public init(): void {
    this.retrieveProfiles();
  }

  public retrieveProfiles(): void {
    axios.get('management/info').then(res => {
      if (res.data && res.data.activeProfiles) {
        this.store.commit('setRibbonOnProfiles', res.data['display-ribbon-on-profiles']);
        this.store.commit('setActiveProfiles', res.data['activeProfiles']);
      }
    });
  }

  public retrieveAccount(): Promise<boolean> {
    return new Promise(resolve => {
      axios
        .get('api/account')
        .then(response => {
          this.store.commit('authenticate');
          const account = response.data;
          if (account) {
            this.store.commit('authenticated', account);
            if (sessionStorage.getItem('requested-url')) {
              this.router.replace(sessionStorage.getItem('requested-url'));
              sessionStorage.removeItem('requested-url');
            }
          } else {
            this.store.commit('logout');
            this.router.push('/');
            sessionStorage.removeItem('requested-url');
          }
          resolve(true);
        })
        .catch(() => {
          this.store.commit('logout');
          resolve(false);
        });
    });
  }

  public hasAnyAuthorityAndCheckAuth(authorities: any): Promise<boolean> {
    if (typeof authorities === 'string') {
      authorities = [authorities];
    }

    if (!this.authenticated || !this.userAuthorities) {
      const token = localStorage.getItem('jhi-authenticationToken') || sessionStorage.getItem('jhi-authenticationToken');
      if (!this.store.getters.account && !this.store.getters.logon && token) {
        return this.retrieveAccount();
      } else {
        return new Promise(resolve => {
          resolve(false);
        });
      }
    }

    for (let i = 0; i < authorities.length; i++) {
      if (this.userAuthorities.includes(authorities[i])) {
        return new Promise(resolve => {
          resolve(true);
        });
      }
    }

    return new Promise(resolve => {
      resolve(false);
    });
  }

  public get authenticated(): boolean {
    return this.store.getters.authenticated;
  }

  public get userAuthorities(): any {
    return this.store.getters.account.authorities;
  }
}

There is solution by @bigless:

...
  private hasAdminAuthorityValue = false;
  private hasUserAuthorityValue = false;
...
  public hasAdminAuthority(): boolean {
    this.accountService()
      .hasAnyAuthorityAndCheckAuth('ROLE_ADMIN')
      .then(value => {
        this.hasAdminAuthorityValue = value;
      });
    return this.hasAdminAuthorityValue;
  }

  public hasUserAuthority(): boolean {
    this.accountService()
      .hasAnyAuthorityAndCheckAuth('ROLE_USER')
      .then(value => {
        this.hasUserAuthorityValue = value;
      });
    return this.hasUserAuthorityValue;
  }

回答1:


This is caused by Vue's reactivity. You use async method in v-if statement and it is mostly bad idea. Vue watches all reactive dependencies of this method and if any dependecy changes, it re-evaluates this statement again(which means calling that function again). Thats one part of the story.

Now why this happens only when roles are different. Because you use shared property hasAnyAuthorityValue which is reactive dependency of the method mentioned above.

For example: it gets privileges for ROLE_USER, returns hasAnyAuthorityValue immediately(sync), but later this value is fetched and updated in promise(async). It can work if async result doesn't change(one role), but when value of hasAnyAuthorityValue is toggling between ROLE_USER and ROLE_ADMIN async results, it is trapped in endless loop.

There are many solutions. f.e. save results of both hasAnyAuthority(any) as property value of data during create phase or use different property names for each role instead of shared one.



来源:https://stackoverflow.com/questions/62960887/infinite-loop-when-checking-roles-vuejs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!