Dynamically set the value of ui-sref Angularjs
Asked Answered
A

13

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>
Abbott answered 22/6, 2014 at 9:3 Comment(1)
possible duplicate of Angularjs adding html to variable with ui-sref linkJuglandaceous
B
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 :-)

Beyer answered 24/3, 2015 at 20:26 Comment(7)
Cleanest solution - and what is worth to mention, "router-library independent".Bernardinebernardo
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.Limbourg
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.Beyer
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).Cubism
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 itemTann
@FabioPicheli's answer does provide a more updated native solution, make sure to check it out.Glassware
The other answer sounds like what angular developers would recommend.Honaker
Q
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

Qianaqibla answered 22/6, 2014 at 9:31 Comment(3)
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: <a ui-sref="{{scopedStateName}}( { 'some-state-param': '{{scopedParamValue}}' })">My Dynamic Link</a> 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.Beyer
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.Beyer
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.Mankind
C
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.

Carvey answered 2/3, 2017 at 13:6 Comment(1)
Great. It does not require additional logic and supports ui-sref-active as wellSelfish
A
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

Anubis answered 7/2, 2016 at 13:48 Comment(1)
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-srefBeacon
C
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);
            });
        }
    };
}
Chestnut answered 27/7, 2016 at 15:35 Comment(1)
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.Philosophism
L
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.

Localism answered 26/4, 2015 at 0:45 Comment(8)
I have tried your approach ,I can see ui-sref but href not generated.Any suggestionsCadwell
I did not mention "href" anywhere in my answer. How is that relevant?Localism
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).Lifelike
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.Localism
@Localism this works the first time, but there's not any two way binding - the link won't update when the scope changes.Hoedown
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.Localism
Can the person who downvoted this answer, care to make an explanation, at least?Localism
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.Tunable
A
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'
});
Armhole answered 21/6, 2016 at 0:29 Comment(1)
this doesn't work with ui-sref-active or ui-sref-active-eqBromley
P
1
<a ng-click="{{getLinkUrl({someParam: someValue})}}">Dynamic Link</a>

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

}

It returns an object

Pagano answered 20/3, 2018 at 13:10 Comment(0)
L
0

this is just working for me

in controller

$scope.createState = 'stateName';

in view

ui-sref="{{ createState }}"
Lucre answered 25/2, 2016 at 15:32 Comment(1)
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 changesBeacon
F
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 !

Fated answered 20/5, 2016 at 13:4 Comment(5)
It gives me an error stating that cannot navigate to myState from current stateElicia
@Elicia Are you using ui-router ? Is it possible to see your code ?Fated
Yes, I'm using ui-router. <ion-item class="accordion-item" ng-repeat="item in group.items" ng-show="isGroupShown(group)" ui-sref="{{item.state}}" ui-sref-active="iactive"> {{ item.title }} </ion-item>Elicia
try ui-sref="item.state"Fated
its working with flower braces only. I'm sorry. There was some other place I made a mistake. Sorry.Elicia
M
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.

Merriman answered 17/11, 2017 at 6:43 Comment(0)
B
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';
Bemuse answered 2/9, 2021 at 21:30 Comment(0)
R
-1

Or just something like this:

<a ui-sref="{{ condition ? 'stateA' : 'stateB'}}">
  Link
</a>
Rainwater answered 29/2, 2016 at 12:8 Comment(2)
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 changesBeacon
I am trying this method, but couldnot make it wirk any suggestion orSlue

© 2022 - 2024 — McMap. All rights reserved.