$stateChangeStart event continuing after state cha

2019-06-07 16:04发布

问题:

I'm attempting to send the user to a specific 'closed' UI State in Angular using the following:

$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
  // check the destination is active
  if(toState.data.isClosed) { // note that the 'closed' state has isClosed set to false
    event.preventDefault();
    $state.go('closed');
  }
  $rootScope.data = toState.data;  // getting executed twice after our state transition to 'closed'
  $log.debug(toState);
});

The issue I'm having is that $rootScope.data = toState.data is getting called twice AFTER we've transitioned to the 'closed' state.

On the first time $startChangeStart executes when navigating to our 'order' state with data.isClosed = true set in the router, the state is changed to 'closed' and the code in question doesn't get executed.

As we are changing states to 'closed' now, $startChangeStart gets triggered again, and the code in question executes for the first time with toState being our 'closed' state.

Strangely, the code is then subsequently executed, starting after the if() logic with toState being the original state of 'order'... meaning that when everything is loaded up, the $rootScope.data variable contains the data from 'order' rather than 'closed'. Adding a few breakpoints and the debugging code above confirms.

Any explanations?

Update

Due to the execution pattern with the state transition to the 'closed' state, I've added a return to ensure that the continued execution after the $state.go() call is terminated. Revised code:

$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
  // check the destination is active
  if(toState.data.isClosed) { // note that the 'closed' state has isClosed set to false
    event.preventDefault();
    $state.go('closed');
    return;
  }
  $rootScope.data = toState.data;  // getting executed twice after our state transition to 'closed'
  $log.debug(toState);
});

This is now working as expected, but I'm not sure it's 'correct'.

回答1:

The solution with return statement is simple correct. I created this playground, where you can test that.

Let's have these states. Firstly some state with isClosed false - which should not be managed by our event listener

  .state('state1', {
    url: "/state1",
    template: "<div>this is a state1</div>",
    data: { isClosed : false },
  })
  .state('state2', {
    url: "/state2",
    template: "<div>this is a state2</div>",
    data: { isClosed : false },
  })

and these will be caught and redirected

  .state('stateClosed1', {
    url: "/stateClosed1",
    template: "<div>this is a stateClosed1</div>",
    data: { isClosed : true },
  })
  .state('stateClosed2', {
    url: "/stateClosed2",
    template: "<div>this is a stateClosed2</div>",
    data: { isClosed : true },
  })
  // even this would be handy
  .state('closed', {
    url: "/closed",
    template: "<div>this is a closed</div>",
    data: { isClosed : false },
  })

Now, whenever we go to any state, the event $stateChangeStart is fired. So if we go to "not handled" states (state1, state2) - the event will be triggered once.

If we go to handled states (stateClosed1, stateClosed2), event is fired always twice:

  • go to stateClosed1
  • go to closed

Just to be sure: the event is really fired twice. And that's why we should write these listeners rather like this (get out ASAP):

  $rootScope.$on('$stateChangeStart', function(event, toState, toParams
                                                    , fromState, fromParams) {

    // get out if we already go to state, which is about to be handled below
    if(toState.name === "closed") {
      return;
    }

    // check the destination is active
    if (toState.data.isClosed) { // note that the 'closed' 
      event.preventDefault();    // state has isClosed set to false 
      $state.go('closed');
      return; 
    }
    $rootScope.data = toState.data; // getting executed 'closed'
                                    // twice after our state transition to 
                                    // only if above if does not contain return
    console.debug(toState);
  });

In fact, this solution (getting out as soon as possible), was issue in these Q & A:

  • Angular ui router - Redirection doesn't work at all
  • How can I fix 'Maximum call stack size exceeded' AngularJS