How to inject state parameter automatically

2019-07-23 13:45发布

问题:

Abstract

Hi, I'm using angular + ui-router in my project, I have huge amount of nested states and different views that in turn contain huge amount of different inputs, a user fills these inputs incrementally step by step.

The problem

Sometimes users require additional info that is located on the previous step, and browsers "back" button helps the user to sneak peek into that data, but as soon as the user presses it, the info he already entered is lost due to state transition, which is obviously a bad thing.

Strategy

In order to overcome described problem I have the following plan:

  1. Associate each user's "navigation" (guess this is a proper term) with a random id
  2. To prevent scope-inheritance-and-serialization issues, instead of putting viewmodel into $scope use ordinary javascript object that will be storing immediate values that are bound to UI.
  3. Add watcher to look for changes on that "storage object"
  4. As soon as the change spotted, serialize the object and persist it

Explanations

Why do we need a random parameter in URL?

We don't want to store all data in URL, since there might be quite some amount of data that wont fit into URL. So in order to provide the guarantees the URL won't break, we put only small random GUID/UUID into it that later allows obtaining the data associated with current "navigation" by this random GUID/UUID.

The storage

There are multitude of storage scenarios available out there: LocalStorage, IndexedDB, WebSQL, Session Storage, you name it, but due to their cross-tab, cross-browser, browser-specific nature it would be hard to manipulate and manage all of the data that gets into the storage. The implementation will be buggy / might require server-side support.

So the most elegant storage strategy for this scenario would be storing data in special window.name variable which is capable of storing data in-between requests. So the data is safe until you close your tab.

The Question

On behalf of everything written above, I have the root view called "view" that has a state parameter id (this is the random GUID/UUID)

$stateProvider.state('view', {
    url: '/view/{id}',
    controller: 'view',
    templateUrl: 'views/view.html'
});

All of the other views derive from this view, is there way to make ui-sref directive to automatically inject a random GUID/UUID into id state parameter of my root view, instead of writing each time ui-sref's like:

<a ui-sref="view({id:guid()}).someNestedView({someNestedParam: getParam()})"

I would like to have something like:

<a ui-sref="view.someNestedView({someNestedParam: getParam()})"

回答1:

The AOP and Decorator pattern are the answer. The comprehensive description could be found here:

Experiment: Decorating Directives by Jesus Rodriguez

Similar solution as described below, could be observed:

Changing the default behavior of $state.go() in ui.router to reload by default

How that would work? There is a link to working example

In this case, we do not solve from which source the random GUID comes from. Let's just have it in runtime:

var guidFromSomeSource = '70F81249-2487-47B8-9ADF-603F796FF999';

Now, we can inject an Decorator like this:

angular
.module('MyApp')
.config(function ($provide) {
    $provide.decorator('$state', function ($delegate) {

        // let's locally use 'state' name
        var state = $delegate;

        // let's extend this object with new function 
        // 'baseGo', which in fact, will keep the reference
        // to the original 'go' function
        state.baseGo = state.go;

        // here comes our new 'go' decoration
        var go = function (to, params, options) {
            params = params || {};

            // only in case of missing 'id'
            // append our random/constant 'GUID'
            if (angular.isUndefined(params.id)) {

                params.id  = guidFromSomeSource;
            }

            // return processing to the 'baseGo' - original
            this.baseGo(to, params, options);
        };

        // assign new 'go', right now decorating the old 'go'
        state.go = go;

        return $delegate;
    });        
})

Code should be self explanatory, check it in action here