Reputation: 40810
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
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
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