83

I have searched for a similar question but the ones that came up seem slightly different. I am trying to change the ui-sref='' of a link dynamically (this link points to the next section of a wizard form and the next section depends on the selection made on the dropdown list). I am simply trying to set the ui-sref attribute depending on some selection in a select box. I am able to change the ui-sref by binding to a scope attribute which is set when a selection is made. however the link does not work, is this possible at all? thanks

  <a ui-sref="form.{{url}}" >Next Section</a>

and then in my controller, I set the url parameter this way

switch (option) {
  case 'A': {
    $scope.url = 'sectionA';
  } break;
  case 'B': {
    $scope.url = 'sectionB';
  } break;
}

Alternatively, I used directives to get it to work by generating the hyperlink with the desired ui-sref attribute according to the option selected on the select box (drop down).

Hhowever this means I have to re-create the link each time a different option is selected from the selectbox which causes an undesirable flickering effect. My question is this, is it possible to change the value of the ui-sref as I tried doing above by simpling changing the value of url in my controller or do I have to re-create the entire element using a directive each time a selection is made as I have done below? (just showing this for completeness)

Select option directive (this directive generates the link directive)

define(['app/js/modules/app', 'app/js/directives/hyperLink'], function (app) {
app.directive('selectUsage', function ($compile) {

    function createLink(scope,element) {
        var newElm = angular.element('<hyper-link></hyper-link>');
        var el = $(element).find('.navLink');
        $(el).html(newElm);
        $compile(newElm)(scope);
    }

    return {

        restrict: 'E',
        templateUrl: '/Client/app/templates/directives/select.html'

        ,link: function (scope, element, attrs) {

            createLink(scope, element);

            element.on('change', function () {
                createLink(scope,element);
            })
        }
    }
})

Hyperlink directive

define(['app/js/modules/app'], function (app) {
app.directive('hyperLink', function () {

    return {
        restrict: 'E',
        templateUrl: '/Client/app/templates/directives/hyperLink.html',
        link: function (scope, element, attrs) { }
    }

})

Hyperlink template

<div>
    <button ui-sref="form.{url}}">Next Section</button>
</div>
theDmi
  • 17,546
  • 6
  • 71
  • 138
user3728830
  • 843
  • 1
  • 6
  • 7
  • possible duplicate of [Angularjs adding html to variable with ui-sref link](http://stackoverflow.com/questions/30058287/angularjs-adding-html-to-variable-with-ui-sref-link) – theDmi Sep 09 '15 at 11:45

13 Answers13

72

Looks like this is possible to do after all.

A breadcrumb on GitHub by one of the ui-router authors led me to try the following:

<a ng-href="{{getLinkUrl()}}">Dynamic Link</a>

Then, in your controller:

$scope.getLinkUrl = function(){
  return $state.href('state-name', {someParam: $scope.someValue});
};

Turns out, this works like a charm w/ changing scoped values and all. You can even make the 'state-name' string constant reference a scoped value and that will update the href in the view as well :-)

RavenHursT
  • 2,336
  • 1
  • 25
  • 46
  • 1
    Cleanest solution - and what is worth to mention, "router-library independent". – Radek Anuszewski Feb 20 '17 at 06:28
  • 1
    This forces getLinkUrl() to be processed every digest cycle, which is VERY Frequently. Stick a console.log in getLinkUrl() and just move your mouse around the page and watch your log blow up. Not saying this method doesn't work, it's just messy. – Suamere Aug 17 '17 at 14:32
  • 2
    This was a solution that was written in Angular 1.4 over 3 years ago. It was the only viable option to achieve the desired functionality back then, with that version of Angular. Just criticizing, and down-voting an answer, w/o even bothering to give an alternative solution (if there is one), doesn't help anyone. – RavenHursT Aug 17 '17 at 19:17
  • 1
    It absolutely *does* help. It suggests to future viewers that this may not be the best solution and they should look elsewhere. There was nothing "unconstructive" about that comment. If anything was unconstructive, it was your response, lashing out at a user for a valid criticism (and assuming facts not in evidence, like the downvote was connected to the comment). – Cody Gray - on strike Aug 18 '17 at 05:16
  • One thing that I discovered with this (as valuable as it is) is that `ui-sref-active` doesn't seem to work with it so if using it in nav it's a manual step to highlight the active nav item – Peter Nixey Sep 15 '17 at 09:28
  • 1
    @FabioPicheli's answer does provide a more updated native solution, make sure to check it out. – phazei Jul 16 '18 at 07:29
  • The [other answer](https://stackoverflow.com/a/42556341/52499) sounds like what angular developers would recommend. – x-yuri Mar 19 '21 at 16:08
61

There is a working plunker. The most easier way seems to be to use combination of:

  • $state.href() (doc here) and
  • ng-href (doc here)

These together could be used as:

<a ng-href="{{$state.href(myStateName, myParams)}}">

So, (following this plunker) having states like these:

$stateProvider
  .state('home', {
      url: "/home",
      ...
  })
  .state('parent', {
      url: "/parent?param",
      ...
  })
  .state('parent.child', { 
      url: "/child",
      ...

We can change these values to dynamically generate the href

<input ng-model="myStateName" />
<input ng-model="myParams.param" />

Check it in action here

ORIGINAL:

There is a working example how to achieve what we need. But not with dynamic ui-sref .

As we can can check here: https://github.com/angular-ui/ui-router/issues/395

Q: [A]re dynamic ui-sref attributes not supported?
A: Correct.

But we can use different feature of ui-router: [$state.go("statename")][5]

So, this could be the controller stuff:

$scope.items = [
  {label : 'first', url: 'first'},
  {label : 'second', url: 'second'},
  {label : 'third', url: 'third'},
];
$scope.selected = $scope.items[0];
$scope.gotoSelected = function(){
  $state.go("form." + $scope.selected.url);
};

And here is the HTML template:

<div>
  choose the url:
  <select
    ng-model="selected"
    ng-options="item.label for item in items"
  ></select>

  <pre>{{selected | json}}</pre>
  <br />
  go to selected
  <button ng-click="gotoSelected()">here</button>

  <hr />
  <div ui-view=""></div>
</div>

The working example

NOTE: there is more up to date link to $state.go definition, but the deprecated one is a bit more clear to me

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • 1
    IMHO, this doesn't completely accomplish the desired behavior. ui-sref actually sets the href attribute of the anchor tag upon initialization of the ui-sref directive. What I typically do for dynamically controlled anchor tags is something like the following: ```My Dynamic Link``` The only problem with this approach, obviously, is that ui-sref doesn't watch for changes in the scoped variables so the href doesn't change on subsequent value changes. AFAIK, ui-sref doesn't support this. – RavenHursT Mar 24 '15 at 20:05
  • After a bit more research, I think I've found a way to accomplish dynamically generated href attributes referencing states w/ state params. Please see my answer below. – RavenHursT Mar 24 '15 at 20:28
  • How are you avoiding a hard page refresh while using ng-href? I'm currently having problems with this solution because ng-href doesn't doesn't use ui-routers `$state.go()` method. – a_dreb May 28 '15 at 18:33
29

Take a look in this issue #2944.

The ui-sref doesn't watch the state expression, you can use ui-state and ui-state-params passing the variable.

  <a data-ui-state="selectedState.state" data-ui-state-params="{'myParam':aMyParam}">
       Link to page {{selectedState.name}} with myParam = {{aMyParam}}
  </a>

Also a quickly demo provided in the ticket.

Fabio Picheli
  • 939
  • 11
  • 27
2

I managed to implement it this way (I'm using the controllerAs variant though - not via $scope).

Template

<button ui-sref="main({ i18n: '{{ ctrlAs.locale }}' })">Home</button>

Controller

var vm = this;
vm.locale = 'en'; // or whatever dynamic value you prepare

Also see the documentation to ui-sref where you can pass params:

https://github.com/angular-ui/ui-router/wiki/Quick-Reference#ui-sref

  • Nope, `button` behaves the same way as `a`, the directive actually creates a `href` attribute on the button at compile time but never updates this. And a click on the button will always go to the state of the `href` value of the but not the updated `ui-sref` – Jens Mar 09 '16 at 08:13
2

After trying various solutions I found the problem in the angular.ui.router code.

The problem comes from the fact that ui.router update method is triggered with the ref.state which means that it is not possible to update the value of the href used when the element is clicked.

Here are 2 solutions to solve the problem:

Custom Directive

    module.directive('dynamicSref', function () {
    return {
        restrict: 'A',
        scope: {
            state: '@dynamicSref',
            params: '=?dynamicSrefParams'
        },
        link: function ($scope, $element) {
            var updateHref = function () {
                if ($scope.state) {
                    var href = $rootScope.$state.href($scope.state, $scope.params);
                    $element.attr('href', href);
                }
            };

            $scope.$watch('state', function (newValue, oldValue) {
                if (newValue !== oldValue) {
                    updateHref();
                }
            });

            $scope.$watch('params', function (newValue, oldValue) {
                if (newValue !== oldValue) {
                    updateHref();
                }
            });

            updateHref();
        }
    };
});

The HTML to use it is quite simple :

<a  dynamic-sref="home.mystate"
    dynamic-sref-params="{ param1 : scopeParam }">
    Link
</a>

Fix ui.router code :

In angular.router.js your will find the directive $StateRefDirective (line 4238 for version 0.3).

Change the directive code to :

function $StateRefDirective($state, $timeout) {
    return {
        restrict: 'A',
        require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
        link: function (scope, element, attrs, uiSrefActive) {
            var ref = parseStateRef(attrs.uiSref, $state.current.name);
            var def = { state: ref.state, href: null, params: null };
            var type = getTypeInfo(element);
            var active = uiSrefActive[1] || uiSrefActive[0];
            var unlinkInfoFn = null;
            var hookFn;

            def.options = extend(defaultOpts(element, $state), attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {});

            var update = function (val) {
                if (val) def.params = angular.copy(val);
                def.href = $state.href(ref.state, def.params, def.options);

                if (unlinkInfoFn) unlinkInfoFn();
                if (active) unlinkInfoFn = active.$$addStateInfo(ref.state, def.params);
                if (def.href !== null) attrs.$set(type.attr, def.href);
            };

            if (ref.paramExpr) {
                scope.$watch(ref.paramExpr, function (val) { if (val !== def.params) update(val); }, true);
                def.params = angular.copy(scope.$eval(ref.paramExpr));
            }

            // START CUSTOM CODE : Ability to have a 2 way binding on ui-sref directive
            if (typeof attrs.uiSrefDynamic !== "undefined") {
                attrs.$observe('uiSref', function (val) {
                    update(val);

                    if (val) {
                        var state = val.split('(')[0];
                        def.state = state;

                        $(element).attr('href', $state.href(def.state, def.params, def.options));
                    }
                });
            }
            // END OF CUSTOM CODE

            update();

            if (!type.clickable) return;
            hookFn = clickHook(element, $state, $timeout, type, function () { return def; });
            element.bind("click", hookFn);
            scope.$on('$destroy', function () {
                element.unbind("click", hookFn);
            });
        }
    };
}
Linvi
  • 2,077
  • 1
  • 14
  • 28
  • 2
    Unless you create your own fork of `angular-ui-router` i would suggest against it. Your fellow co-worker might try to update it and *boom*, no one knows where the bug came from. – Rafael Herscovici Aug 01 '18 at 10:18
1

Came to answer that for good :)

Fortunately, you don't need to use a button for ng-click, or use a function inside an ng-href to achieve what you seek. Instead;

You can create a $scope var in your controller and assign the ui-sref string in it and use it in your view, as ui-sref attribute.

Like this:

// Controller.js

// if you have nasted states, change the index [0] as needed.
// I'm getting the first level state after the root by index [0].
// You can get the child by index [1], and grandchild by [2]
// (if the current state is a child or grandchild, of course).
var page = $state.current.name.split('.')[0];
$scope.goToAddState = page + ".add";


// View.html
<a ui-sref="{{goToAddState}}">Add Button</a>

That works perfectly for me.

ilter
  • 4,030
  • 3
  • 34
  • 51
  • 2
    I have tried your approach ,I can see `ui-sref` but `href` not generated.Any suggestions – Arepalli Praveenkumar Aug 25 '15 at 14:27
  • I did not mention "href" anywhere in my answer. How is that relevant? – ilter Aug 25 '15 at 14:38
  • 2
    ui-sref is a directive that sets an href attribute on an element if the provided state has an url (but in this case apparently not). – askmike Sep 21 '15 at 10:17
  • Should I have mentioned that in order the ui-sref generate a href attribute, you SHOULD HAVE the appropriate states registered? In my example I have state hierarchy seperated by dots. Without dots its parent, with one dot it's child, with two dots grandchild, etc... Be sure that you're registering your states correctly implying that hierarchy. – ilter Sep 25 '15 at 10:21
  • 3
    @ilter this works the first time, but there's not any two way binding - the link won't update when the scope changes. – Ben Dec 09 '15 at 19:26
  • If you have this necessity, then you should $watch for this variable for changes. But I haven't got the need of doing what you ask anywhere, so didn't even think of mentioning that in my answer. – ilter Dec 10 '15 at 07:12
  • Can the person who downvoted this answer, care to make an explanation, at least? – ilter Apr 26 '16 at 05:24
  • 2
    I can only speak for myself, but in my case it's because the answer doesn't work for the question. When the dropdown changes you will no longer have a valid link. – Josh Gagnon Jun 06 '16 at 15:44
1

The best approach is to make use of uiRouter's $state.go('stateName', {params}) on button's ng-click directive. And disable the button if no option is selected.

HTML

<select ng-model="selected" ng-options="option as option.text for option in options"></select>
<button ng-disabled="!selected" type="button" ng-click="ctrl.next()">Next</button>

Controller

function Controller($scope, $state){
    this.options = [{
        text: 'Option One',
        state: 'app.one',
        params: {
            param1: 'a',
            param2: 'b'
        }
    },{
        text: 'Option Two',
        state: 'app.two',
        params: {
            param1: 'c',
            param2: 'd'
        }
    },{
        text: 'Option Three',
        state: 'app.three',
        params: {
            param1: 'e',
            param2: 'f'
        }
    }];

    this.next = function(){
        if(scope.selected){
            $state.go($scope.selected.state, $scope.selected.params || {});
        }
    };
}

State

$stateProvider.state('wizard', {
    url: '/wizard/:param1/:param2', // or '/wizard?param1&param2'
    templateUrl: 'wizard.html',
    controller: 'Controller as ctrl'
});
rynangeles
  • 104
  • 1
  • 3
1
<a ng-click="{{getLinkUrl({someParam: someValue})}}">Dynamic Link</a>

$scope.getLinkUrl = function(value){
  $state.go('My.State',{someParam:value});

}

It returns an object

bklups
  • 300
  • 1
  • 13
0

this is just working for me

in controller

$scope.createState = 'stateName';

in view

ui-sref="{{ createState }}"
Abou-Emish
  • 2,201
  • 1
  • 22
  • 22
  • 5
    as noted by @ben on the other answers: this works the first time, but there's not any two way binding - the link won't update when the scope changes – Jens Mar 09 '16 at 08:15
0

For manage multiple dynamic params using ui-sref, here my solution :

Html : ('MyPage.html')

<button type="button" ui-sref="myState(configParams())">

Controller : ('MyCtrl')

.controller('MyCtrl', function ($scope) {
  $scope.params = {};
  $scope.configParams = function() {
    $scope.params.param1 = 'something';
    $scope.params.param2 = 'oh again?';
    $scope.params.param3 = 'yes more and more!';
    //etc ...

    return $scope.params;
  };
}

stateProvider : ('myState')

 $stateProvider
          .state('myState', {
            url: '/name/subname?param1&param2&param3',
            templateUrl: 'MyPage.html',
            controller: 'MyCtrl'
          });

Enjoy !

Emidomenge
  • 1,172
  • 17
  • 26
0
<ul class="dropdown-menu">
  <li ng-repeat="myPair in vm.Pairs track by $index">
     <a ui-sref="buy({myPairIndex:$index})"> 
          <span class="hidden-sm">{{myPair.pair}}</span>
     </a>
  </li>
</ul>

If someone only wants to dynamically set the $stateParams of ui-sref in Angularjs. Note: In inspect element it will still appear as "buy({myPairIndex:$index})" but $index will be fetched in that state.

Ryan Augustine
  • 1,455
  • 17
  • 14
0

I found this solution the most appropriate:

.state('history', {
    url: 'home',
    controller: 'homeCtrl',
    templateUrl: "home.html"
  })
  .state('settings', {
    url: "/settings",
    controller: 'settingsCtrl',
    templateUrl: 'settings.html'
  })
<button ui-sref="{{$ctrl.newProductLink()}}"</button>
ctrl.newProductLink = () => a > b ? 'home' : 'settings';
NBash
  • 455
  • 3
  • 10
-1

Or just something like this:

<a ui-sref="{{ condition ? 'stateA' : 'stateB'}}">
  Link
</a>
Karens
  • 613
  • 5
  • 18
  • 4
    as noted by @ben on the other answers: this works the first time, but there's not any two way binding - the link won't update when the scope changes – Jens Mar 09 '16 at 08:14
  • I am trying this method, but couldnot make it wirk any suggestion or – Aswin Apr 07 '17 at 08:40