2

I have been trying and searching for articles on this and what i found out is adding state dynamically on runtime only. The closest to the solution i find is make use of deferIntercept, $urlRouter.sync() and $urlRouter.listen(). However, it seems that $urlRouter can only be used in runtime (which run before controller user action).

This plunker shows working dynamic added state into runtime but the challenge is to implement it in controller instead.

below is illustration to what i intend to achieve. I have been struggling on this issue for weeks. Hope someone could give me some hint on how to acheived the desire outcome.

// Predefined state in config
$stateProvider.state('default', {
    url: '',
    templateUrl: '',
})

// Predefined state in config
$stateProvider.state('home', {
    url: '',
    templateUrl: '',
})

// Additional Added state in controller when user click button
$scope.clickButton = function () {
    $stateProvider.state('newpage', {
        url: '',
        templateUrl: ''
    })
};
stackdisplay
  • 1,949
  • 6
  • 29
  • 46
  • There you have full answer(s) http://stackoverflow.com/q/24727042/1679310. They cover startup scenario (configuring states from data coming from server) and adding new states in runtime... – Radim Köhler Sep 08 '16 at 17:59
  • check this might help http://stackoverflow.com/questions/25866387/angular-ui-router-programmatically-add-states/31478967 – nmanikiran Sep 08 '16 at 18:01
  • @RadimKöhler Thanks for the comment, i have read through the questions and answers as well. But i would like to add the new states inside controller user action, not in runtime. – stackdisplay Sep 08 '16 at 18:01
  • @ManiKiran aprreciate your link but it just touches the surface of it. It still do not explain and show how $urlRouter can be used inside controller. As you see from the working plunkr code that i posted, `$urlRouter.sync()` and `$urlRouter.listen()` is mandatory. The entire dynamic route will not work if any of it were commented. And i have tested that calling both of it in controller does not work – stackdisplay Sep 08 '16 at 18:47
  • @stackdisplay can you explain why this is a good thing to do? I'm just curious because what I've seen in the past is just adding extra properties to the state itself and then using a state change event handler to decide based on data stored in services and the extra config on the state object if it should actually navigate to that state or redirect. – shaunhusain Sep 08 '16 at 18:49
  • @stackdisplay Providers are configured during the config phase so they can reliably be used during runtime under the assumption that the configuration was established up front, it's fairly against the grain to try and modify their configuration after the config phase (which happens before run and before any directives are being "compiled" or executed). Resources that are actually important should be protected on the server side and should never make it to the client, conditionally adding or removing states is ineffective (anyone can rewrite the client). – shaunhusain Sep 08 '16 at 19:11
  • I understand sometimes there are hard requirements but would push back on this one at least a bit unless there is good reason. – shaunhusain Sep 08 '16 at 19:11
  • @stackdisplay check this http://plnkr.co/edit/CO2OkODYgggAVO6QJszi?p=preview i made few changes in .JSON and add `runtimeStates` provider – nmanikiran Sep 08 '16 at 19:17
  • The question doesn't explain why this is necessary in the first place. Choosing the hard way when there may be a proper one indicates that this may be XY problem. – Estus Flask Sep 08 '16 at 19:18
  • @estus i have updated the question and explain why this is necessary in first place based on the requirement given., – stackdisplay Sep 08 '16 at 19:29
  • @ManiKiran appreciate you effort. But it still is not being implemented in controller right? The state is still dynamically being added in runtime. I have updated my question for better description though. – stackdisplay Sep 08 '16 at 19:30
  • @shaunhusain were u suggesting that the dynamic population of state should be banned entirely (including to populate add in runtime) or only the part of dynamically populating it in controller only? – stackdisplay Sep 08 '16 at 19:35
  • @stackdisplay if this is really necessary I think I'd handle it by just reloading the whole page or separating the login from the application itself so after the user is logged in and has a token the token can be sent with the request for the application and if the server gets a request with the token for the userStateConfig.js it properly populates the state config for the particular user. The guy Chris Thielen who answered the question that Mani linked is a core contributor on ui-router you can find him on slack in angularbuddies usually, he can probably tell you if there is a solution. – shaunhusain Sep 08 '16 at 19:35
  • @stackdisplay you may be able to get away with using some regex in the URL matching or otherwise that also uses services to determine if the user can go to the particular state, but I think anything where you are trying to modify things meant to be configured before the app is running is likely to get you into trouble (maybe works for a while but who knows how long since it will lack testing in the library you're depending on, can write your own tests but just seems fragile to me). – shaunhusain Sep 08 '16 at 19:41
  • I see. There's no practical difference between `run` block (the thing you've called 'runtime') and a controller, they are both called *run phase*, so it is not clear what your problem with controller exactly is. Any way, the providers cannot be used in run phase for a reason, this is hacking and this leads to unpredictable results, the thing you're trying to do should be avoided if possible. – Estus Flask Sep 08 '16 at 19:45
  • The proper way to handle this case is to define all routes right from the start and use a route resolver and/or state changing event to authorize the user for current route, see [this](http://stackoverflow.com/a/33873011/3731501) or [this answer](http://stackoverflow.com/a/33895051/3731501). – Estus Flask Sep 08 '16 at 19:45
  • @stackdisplay i updated code to add state from controller http://plnkr.co/edit/CO2OkODYgggAVO6QJszi?p=preview – nmanikiran Sep 09 '16 at 04:04

1 Answers1

2

I updated your code a little bit: http://plnkr.co/edit/wDVf2S?p=preview

I added controller with click callback and 2 arrays which I print with ng-repeat in index.html

app.controller('mainCtrl', mainCtrl);

mainCtrl.$inject = ['$urlRouter'];
function mainCtrl ($urlRouter) {
  var vm = this;

  // 'add new state' button click function
  vm.addState = function () {
    var name = 'new-state-' + (vm.hrefs.length - 2);
    var newState = {
      "url": "/" + name,
      "abstract": false,
      "parent": "",
      "views": {}
    };
    // add views
    newState.views[name] = {
      "name": name,
      "template": "<div style='padding:20px; border: solid 1px red; margin-bottom: 15px;'>TEMPLATE FOR <em>" + name.toUpperCase() + "</em> GOES HERE</div>"
    };
    newState.views["container@"] = {
      "name": "container@",
      "templateUrl": "tpl.content.html"
    };
    newState.views["left@"] = {
      "name": "left@",
      "templateUrl": "tpl.left.html"
    };

    $stateProviderRef.state(name, newState);

    vm.hrefs.push(name);
    // add to the head
    vm.views.reverse().push(name);
    vm.views.reverse()

    $urlRouter.sync();
    $urlRouter.listen();
  };

  vm.hrefs = ['home', 'other', 'about'];
  vm.views = ['', 'header', 'left', 'container', 'footer'];
}
Andriy
  • 14,781
  • 4
  • 46
  • 50
  • Whether helpful or not to the OP, that's still some awesome stuff. – TSmith Sep 09 '16 at 01:06
  • I updated my plunker, added button for loading states from JSON file along with add new state button. Both actions are executed from the controller. – Andriy Sep 09 '16 at 22:02