yankee
yankee

Reputation: 40810

How can I avoid injecting $state into $rootScope when using ui-router-extras sticky states?

The example for ui-router-extras for sticky states shows us that we need to show/hide views by ourself using ng-show in combination with $state. Thus we need to make $state somehow accessible which in the example is done by injecting $state globally into $rootScope.

I don't like the idea that this variable is globally crawling through my scopes and injecting that variable from all controllers locally does not sound optimal either. How can I solve this more elegantly?

Upvotes: 0

Views: 1385

Answers (2)

Chris T
Chris T

Reputation: 8216

This is the perfect application for a custom directive. Here is a simple directive which you can add to your ui-view tag:

 app.directive("showWhenStateActive", function($state) {
    return {
      restrict: 'A',
      link: function(scope, elem, attrs) {
        var stateChangedFn = function stateChanged() {
          var addOrRemoveFnName = $state.includes(attrs.showWhenStateActive) ? "removeClass" : "addClass";
          elem[addOrRemoveFnName]("ng-hide");
        };
        scope.$on("$stateChangeSuccess", stateChangedFn);
      }
    }
  });

To use this, just add it as an attribute to the tag, and supply the state name you want to check for.

For example, on the ui-router-extras Sticky State example, you would change from this:

<div class="tabcontent well-lg" ui-view="peopletab" ng-show="$state.includes('top.people')" class="col-sm-6"></div>
<div class="tabcontent well-lg" ui-view="invtab"    ng-show="$state.includes('top.inv')"    class="col-sm-6"></div>
<div class="tabcontent well-lg" ui-view="custtab"   ng-show="$state.includes('top.cust')"   class="col-sm-6"></div>

to this:

<div class="tabcontent well-lg" ui-view="peopletab" show-when-state-active="top.people" class="col-sm-6"></div>
<div class="tabcontent well-lg" ui-view="invtab"    show-when-state-active="top.inv"    class="col-sm-6"></div>
<div class="tabcontent well-lg" ui-view="custtab"   show-when-state-active="top.cust"   class="col-sm-6"></div>

Upvotes: 3

yankee
yankee

Reputation: 40810

I studied the source code of the ui-sref directive to figure out whether I could build a similar directive but not for setting the active class depending on the current state, but instead to change visibility of the current element.

Unfortunately ui-router does not look like it is intended to be extended that way. However I could hack my own directive together. Note that parseStateRef and stateContext are copy&pasted functions from the ui-router source code which are not accessible to other code.

The source code works in a current project of mine as intended, however I don't know which cases it will miss that I did not think about/need in my project.

Anyway: Usage would be <... show-on-sref="targetState">. I successfully tested the directive with child relative links (those starting with a dot such as .someState).

var attributeName = 'showOnSref';

function parseStateRef(ref, current) {
    var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
    if (preparsed) ref = current + '(' + preparsed[1] + ')';
    parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
    if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
    return { state: parsed[1], paramExpr: parsed[3] || null };
}
function stateContext(el) {
    var stateData = el.parent().inheritedData('$uiView');

    if (stateData && stateData.state && stateData.state.name) {
        return stateData.state;
    }
}

angular
    .module('showOnSref', [])
    .directive(attributeName, ['$state', function($state) {
        return {
            restrict: 'A',
            link: function(scope, element, attrs) {
                var ref = parseStateRef(attrs[attributeName], $state.current.name),
                    linkedState = $state.get(ref.state, stateContext(element)),
                    params = angular.copy(scope.$eval(ref.paramExpr)),
                    inactiveClass = 'ng-hide',
                    bind = angular.bind,
                    show = bind(element, element.removeClass, inactiveClass),
                    hide = bind(element, element.addClass, inactiveClass),
                    update = function() {
                        $state.includes(linkedState.name, params) ? show() : hide();
                    };
                    scope.$on('$stateChangeSuccess', update);
            }
        };
    }]);

Upvotes: 0

Related Questions