Как избежать внедрения $state в $rootScope при использовании фиксированных состояний ui-router-extras?

Пример для ui-router-extras для липкие состояния показывают нам, что нам нужно показывать/скрывать представления самостоятельно, используя ng-show в сочетании с $state. Таким образом, нам нужно как-то сделать $state доступным, что в примере делается путем глобальной инъекции $state в $rootScope.

Мне не нравится идея, что эта переменная глобально сканирует мои области видимости, и локальное внедрение этой переменной со всех контроллеров также не кажется оптимальным. Как я могу решить это более элегантно?


person yankee    schedule 07.02.2015    source источник


Ответы (2)


Это идеальное приложение для пользовательской директивы. Вот простая директива, которую вы можете добавить в свой тег ui-view:

 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);
      }
    }
  });

Чтобы использовать это, просто добавьте его как атрибут к тегу и укажите имя состояния, которое вы хотите проверить.

Например, на примере ui-router-extras Sticky State, вы бы изменили это:

<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>

к этому:

<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>
person Chris T    schedule 08.02.2015

Я изучил исходный код директива ui-sref чтобы выяснить, могу ли я создать аналогичную директиву, но не для установки активного класса в зависимости от текущего состояния, а для изменения видимости текущего элемента.

К сожалению, ui-router не выглядит так, как будто он предназначен для такого расширения. Однако я мог бы взломать свою собственную директиву вместе. Обратите внимание, что parseStateRef и stateContext — это функции копирования и вставки из исходного кода ui-router, которые недоступны для другого кода.

Исходный код работает в моем текущем проекте, как и предполагалось, однако я не знаю, какие случаи он пропустит, о чем я не думал/не нуждался в своем проекте.

В любом случае: использование будет <... show-on-sref="targetState">. Я успешно протестировал директиву с дочерними относительными ссылками (начинающимися с точки, например .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);
            }
        };
    }]);
person yankee    schedule 07.02.2015