Saturday, February 24, 2018

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

Leave a Comment

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?

1 Answers

Answers 1

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]});             });         }     }); }); 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment