Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

State decorators? #125

Open
groner opened this issue Nov 4, 2014 · 9 comments
Open

State decorators? #125

groner opened this issue Nov 4, 2014 · 9 comments

Comments

@groner
Copy link
Contributor

groner commented Nov 4, 2014

I'm looking for a way to override a view in a state at runtime to use an alternate template and/or controller.

I think what I'm looking for is a $stateProvider.decorate(stateName, decoratorFn) call.

What do you think of this @jeme? Does this sound like an appropriate API?

@jeme
Copy link
Contributor

jeme commented Nov 4, 2014

Is there any reason you can't use transition handlers?

@groner
Copy link
Contributor Author

groner commented Nov 4, 2014

How would that work? I just tried modifying $to.views in the before stage, but it didn't seem to have any effect. I'm running v0.6.1 if that matters.

I guess I might be able to drive $view directly, but I would prefer to keep using the declarative style of $state.

@jeme jeme added the feature label Nov 4, 2014
@jeme
Copy link
Contributor

jeme commented Nov 4, 2014

Ideally it would make sense that you could just modify the state object in the transition handlers.

It's a bit tricky though, because we don't wan't to to be a permanent change in the configuration, that is why we do copies along the way.

And views adds a bit to that complexity as we traverse the state tree for views.

@groner
Copy link
Contributor Author

groner commented Nov 5, 2014

I'm not looking for completely dynamic configuration, I just want to overlay some of the state configuration from a separate module that is only loaded for some users. Once the configuration phase is over, I don't need the states to change.

@jeme
Copy link
Contributor

jeme commented Nov 6, 2014

So that sounds a bit similar to what people wan't to do with server side configurations.
See: #19

Perhaps that could be of use to you?...

@groner
Copy link
Contributor Author

groner commented Nov 6, 2014

Hmmm, I'm not sure. I'm really looking for a way to amend a state without having to restate the details I'm not changing.

@jeme
Copy link
Contributor

jeme commented Nov 7, 2014

Yes I understand that, but what triggered me towards the initialization feature is that you just wanted to do it once.

But I guess the feature isn't explained well enough (I have not gotten around to document that, so many other things to do) But all loading of states are deferred to happen at run-time today, so your already using that feature, you just don't know it.

//So if you have:
$state.state('myState', { /* configure */ });

//You can replace that by: (This happens behind the scene for the above anyways, but without using an injector)
$state.state(['$register', function(reg) {
    reg('myState', { /* configure */ });
}]);

Now given you are talking about specific users..

$state.state(['$register', '$user', function(reg, user) {
    var myState = { /* configure */ }
    if(user.needsExtraStuff) {
      myState.views.extra = { /*..*/ }
    }
    reg('myState', myState);

    if(user.needsExtraStates) {
      reg('extraState', { /* configure */ });
      reg('extraState.child', { /* configure */ });
    }
}]);

@groner
Copy link
Contributor Author

groner commented Nov 7, 2014

$register does give some extra flexibility by allowing you to defer configuration until other services are available, but I don't need that for what I'm trying to do here. By the time angular bootstraps, we have selected and loaded the modules needed for the current user.

Conditional blocks don't really help me here because this code is in other modules. I could redefine the state in the other modules, but that requires me to copy (and maintain) the parts of the configuration I'm not changing too. Last time I looked, redefining extraState would cause any child states of it to need ot be redefined as well.

This is what I'm using to allow extra modules to override views right now. I think it will work ok, but dotjem could provide a cleaner way to do it if it would be useful to others.

angular.module('core')

  // This services provides an extension point for states that can have their views overriden.
  .provider('stateViewOverrides', function() {
    var stateViews = {};

    this.define = define;
    this.extend = extend;
    this.$get = angular.noop;

    function define(name, views) {
      stateViews[name] = views;
      return views;
    }

    function extend(name, views) {
      angular.extend(stateViews[name], views);
      return this;
    }
  });
angular.module('core')

  .config(function($stateProvider, stateViewOverridesProvider) {
    $stateProvider

      // Here a state registers it's views with the overrides service
      .state('whatever', {
        views: stateViewOverridesProvider.define('whatever', {
          main: {
            template: 'whatever.html',
            controller: 'WhateverCtrl',
          },
        }),
      });
  });
angular.module('ext')

  // For certain users the ext module is loaded to provide certain customizations.
  .config(function(stateViewOverridesProvider) {
    stateViewOverridesProvider

      .extend('whatever', {
        main: {
          template: 'alt/whatever.html',
          controller: 'WhateverCtrl',
        },
      });
  });

@jeme
Copy link
Contributor

jeme commented Nov 10, 2014

We are really talking about server bound configurations in your case though (based on the user that which only the server can rationale about), which the state loaders was meant to handle, it's just that the way you do it through modules is really a different approach that what angular-routing provides.

It's always super interesting to see different takes on solving the same problem, but I am at a stage where we can't add them all into the core of the framework as that would just generate bloat (there is already plenty of bloat)

Back to what you have done... I honestly never saw that as being a bad pattern, putting decorators around the $stateProvider... I encourage it, and there is a ton of examples:

  • State prototypes/templates.
  • Navigation trackers
  • State reconfiguration (was new for me, but that is your case).
  • etc.
    (Perhaps in the future I might add some of these my self or even pull some existing things out into other modules)

And I think it's better to do this over adding it into the core. However there could maybe be a more clear way of pushing these decorators directly into the $stateProvider so you could get back to calling: $stateProvider.extend but under the hoods that would be a decorator kicking in.

You can actually already do this , this is javascript after all, there is nothing blocking you from doing:

angular.module('core')
  //Or we could just use config here
  .provider('stateDecorator', function($stateProvider) {
    var views = {};
    var stateFn = $stateProvider.state;
    $stateProvider.state = function(nameOrFn, state) {
       if(angular.isString(nameOrFn) {
         views[nameOrFn] = state.views;
       }
       stateFn(nameOrFn, state);
    }

    $stateProvider.extend = function extend(name, views) {
      angular.extend(stateViews[name], views);
      return this;
    }

    this.$get = angular.noop;
  });

But it would perhaps be better to provide a pattern similar to $provide.decorator(name, decorator) which sadly is for services only.

@jeme jeme added this to the Future milestone Nov 11, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants