Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ui-router 1.x.x change $transition$.params() during resolve

Trying to migrate an angularjs application to use the new version of angular-ui-router 1.0.14 and stumbled upon a problem when trying to change $stateParams in the resolve of a state.

For example, previously (when using angular-ui-router 0.3.2) modifying $stateParams worked like this:

 $stateProvider.state('myState', {
   parent: 'baseState',
   url: '/calendar?firstAvailableDate',
   template: 'calendar.html',
   controller: 'CalendarController',
   controllerAs: 'calendarCtrl',
   resolve: {
     availableDates: ['CalendarService', '$stateParams', function(CalendarService, $stateParams) {
       return CalendarService.getAvailableDates().then(function(response){
          $stateParams.firstAvailableDate = response[0];
          return response;
       });
     }]
   }
 })

The problem is firstAvailableDate is populated after a resolve and I do not know how to update $transition$.params() during a resolve when usign the new version of angular-ui-router 1.0.14.

I have tried, and managed to update the url parameter with

  1. firing a $state.go('myState', {firstAvailableDate : response[0]}) but this reloads the state, so the screen flickers

  2. modified $transition$.treeChanges().to[$transition$.treeChanges().length-1].paramValues.firstAvailableDate = response[0]; to actually override the parameters. I have done this after looking through the implementation on params() for $transition$.

Although both those options work, they seem to be hacks rather than by the book implementations.

What is the correct approach to use when trying to modify parameters inside a resolve?

like image 311
rave Avatar asked Feb 14 '18 10:02

rave


1 Answers

Approach with dynamic parameter:

Take a look at this document: params.paramdeclaration#dynamic. Maybe thats what you are looking for: ...a transition still occurs....

When dynamic is true, changes to the parameter value will not cause the state to be entered/exited. The resolves will not be re-fetched, nor will views be reloaded.

Normally, if a parameter value changes, the state which declared that the parameter will be reloaded (entered/exited). When a parameter is dynamic, a transition still occurs, but it does not cause the state to exit/enter.

This can be useful to build UI where the component updates itself when the param values change. A common scenario where this is useful is searching/paging/sorting.

Note that you are not be able to put such logic into your resolve inside your $stateProvider.state. I would do this by using dynamic parameters to prevent the state reload. Unfortunally, the dynamic rules doesn't work when you try to update your state (e.g. by using $stage.go()) inside the resolve part. So I moved that logic into the controller to make it work nice - DEMO PLNKR.

Since userId is a dynamic param the view does not get entered/exited again when it was changed.

Define your dynamic param:

  $stateProvider.state('userlist.detail', {
    url: '/:userId',
    controller: 'userDetail',
    controllerAs: '$ctrl',
    params: {
      userId: {
        value: '',
        dynamic: true
      }
    },
    template: `
        <h3>User {{ $ctrl.user.id }}</h3>
        
        <h2>{{ $ctrl.user.name }} {{ !$ctrl.user.active ? "(Deactivated)" : "" }}</h2>
        
        <table>
          <tr><td>Address</td><td>{{ $ctrl.user.address }}</td></tr>
          <tr><td>Phone</td><td>{{ $ctrl.user.phone }}</td></tr>
          <tr><td>Email</td><td>{{ $ctrl.user.email }}</td></tr>
          <tr><td>Company</td><td>{{ $ctrl.user.company }}</td></tr>
          <tr><td>Age</td><td>{{ $ctrl.user.age }}</td></tr>
        </table>
    `
  });

Your controller:

app.controller('userDetail', function ($transition$, $state, UserService, users) {
  
  let $ctrl = this;
  
  this.uiOnParamsChanged = (newParams) => {
    
    console.log(newParams);
    
    if (newParams.userId !== '') {
      $ctrl.user = users.find(user => user.id == newParams.userId);
    }
  };
  
  this.$onInit = function () {
    
    console.log($transition$.params());
    
    if ($transition$.params().userId === '') {
      UserService.list().then(function (result) {
        $state.go('userlist.detail', {userId: result[0].id});
      });
    }
  }
});

Handle new params by using $transition.on* hooks on route change start:

An other approach would be to setup the right state param before you change into your state. But you already said, this is something you don't want. If I would face the same problem: I would try to setup the right state param before changing the view.

app.run(function (
    $transitions,
    $state,
    CalendarService
) {

    $transitions.onStart({}, function(transition) {
        if (transition.to().name === 'mySate' && transition.params().firstAvailableDate === '') {

            // please check this, I don't know if a "abort" is necessary
            transition.abort();

            return CalendarService.getAvailableDates().then(function(response){
                // Since firstAvailableDate is dynamic 
                // it should be handled as descript in the documents.
                return $state.target('mySate', {firstAvailableDate : response[0]});
            });
        }
    });
});

Handle new params by using $transition.on* hooks on route change start via redirectTo

Note: redirectTo is processed as an onStart hook, before LAZY resolves.

This does the same thing as provided above near the headline "Handle new params by using $transition.on* hooks on route change start" since redirectTo is also a onStart hook with automated handling.

$stateProvider.state('myState', {
    parent: 'baseState',
    url: '/calendar?firstAvailableDate',
    template: 'calendar.html',
    controller: 'CalendarController',
    controllerAs: 'calendarCtrl',
    redirectTo: (trans) => {
        if (trans.params().firstAvailableDate === '') {
            var CalendarService = trans.injector().get('CalendarService');
            return CalendarService.getAvailableDates().then(function(response){
                return { state: 'myState', params: { firstAvailableDate: response[0] }};
            });
        }
    }
});
like image 96
lin Avatar answered Nov 09 '22 05:11

lin