Установить заголовок страницы с помощью UI-Router

101

Я переношу свое приложение на основе AngularJS на использование ui-router вместо встроенной маршрутизации. Я настроил его, как показано ниже

.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/home');
$stateProvider
    .state('home', {
        url: '/home',
        templateUrl : 'views/home.html',
        data : { pageTitle: 'Home' }

    })
    .state('about', {
        url: '/about',
        templateUrl : 'views/about.html',
        data : { pageTitle: 'About' }
    })
     });

Как я могу использовать переменную pageTitle для динамической установки заголовка страницы? Используя встроенную маршрутизацию, я мог

$rootScope.$on("$routeChangeSuccess", function(currentRoute, previousRoute){
    $rootScope.pageTitle = $route.current.data.pageTitle;
  });

а затем привяжите переменную в HTML, как показано ниже

<title ng-bind="$root.pageTitle"></title>

Есть ли подобное событие, к которому я могу подключиться с помощью ui-router? Я заметил, что есть функции onEnter и onExit, но они, кажется, привязаны к каждому состоянию и потребуют от меня повторения кода для установки переменной $ rootScope для каждого состояния.


источник
3
Есть событие $ stateChangeSuccess.
Джеррад

Ответы:

108

Используйте $stateChangeSuccess.

Вы можете поместить это в директиву:

app.directive('updateTitle', ['$rootScope', '$timeout',
  function($rootScope, $timeout) {
    return {
      link: function(scope, element) {

        var listener = function(event, toState) {

          var title = 'Default Title';
          if (toState.data && toState.data.pageTitle) title = toState.data.pageTitle;

          $timeout(function() {
            element.text(title);
          }, 0, false);
        };

        $rootScope.$on('$stateChangeSuccess', listener);
      }
    };
  }
]);

И:

<title update-title></title>

Демо: http://run.plnkr.co/8tqvzlCw62Tl7t4j/#/home

Код: http://plnkr.co/edit/XO6RyBPURQFPodoFdYgX?p=preview

Даже с $stateChangeSuccessтем $timeout, чтобы история была правильной, по крайней мере, когда я проверял себя.


Изменить: 24 ноября 2014 г. - Декларативный подход:

app.directive('title', ['$rootScope', '$timeout',
  function($rootScope, $timeout) {
    return {
      link: function() {

        var listener = function(event, toState) {

          $timeout(function() {
            $rootScope.title = (toState.data && toState.data.pageTitle) 
            ? toState.data.pageTitle 
            : 'Default title';
          });
        };

        $rootScope.$on('$stateChangeSuccess', listener);
      }
    };
  }
]);

И:

<title>{{title}}</title>

Демо: http://run.plnkr.co/d4s3qBikieq8egX7/#/credits

Код: http://plnkr.co/edit/NpzQsxYGofswWQUBGthR?p=preview

тассеKATT
источник
Супер здорово. Это не могло быть проще
Мэтью Харвуд
3
Этот пример также некорректно работает с историей (по крайней мере, в Chrome 37). Если вы переходите между различными состояниями, а затем просматриваете свою историю, заголовок элемента истории будет значением предыдущей страницы. Если вы перейдете page1 -> page2 -> page3, затем посмотрите историю, URL-адрес страницы 2 будет совпадать с заголовком страницы 1.
jkjustjoshing 02
2
На самом деле, это не совсем точно. Заголовок страницы изменяется до изменения хэша URL, поэтому браузер считает, что новый заголовок предназначен для старой страницы. Тогда история кнопки «Назад» будет отключена на 1 страницу. У element.text(title)меня сработало включение вызова в тайм-аут $. Редактирование исходного сообщения.
jkjustjoshing 02
3
Это не сработает, если заголовок должен быть динамическим на основе некоторых параметров URL.
Кушагра Гур 08
10
@KushagraGour Если потребности титульных быть динамичными на основе $ stateParams, вы можете использовать функцию resolveдля его генерации, а затем получить доступ к «разрешенному» значению в течение $ stateChangeSuccess события с: $state.$current.locals.resolve.$$values.NAME_OF_RESOLVE_FUNCTION.
Клаус Конрад
91

Есть другой способ сделать это, уже здесь объединив большинство ответов. Я знаю, что на это уже дан ответ, но я хотел показать, как я динамически меняю заголовки страниц с помощью ui-router.

Если вы посмотрите на пример приложения ui-router , они используют блок angular .run для добавления переменной $ state в $ rootScope.

// It's very handy to add references to $state and $stateParams to the $rootScope
// so that you can access them from any scope within your applications.
// For example, <li ng-class="{ active: $state.includes('contacts.list') }"> 
// will set the <li> to active whenever 'contacts.list' or one of its 
// decendents is active.

.run([ '$rootScope', '$state', '$stateParams',
function ($rootScope, $state, $stateParams) {
  $rootScope.$state = $state;
  $rootScope.$stateParams = $stateParams;
}])

После этого вы можете легко динамически обновлять заголовок своей страницы тем, что вы опубликовали, но изменили, чтобы использовать определенное состояние:

Таким же образом настройте состояние:

.state('home', {
    url: '/home',
    templateUrl : 'views/home.html',
    data : { pageTitle: 'Home' }
})

Но немного отредактируйте html ...

<title ng-bind="$state.current.data.pageTitle"></title>

Я не могу сказать, что это лучше, чем ответы ранее ... но мне было легче понять и реализовать. Надеюсь, это кому-то поможет!

cwbutler
источник
3
Более декларативный, чем принятый ответ. Мне это нравится!
Энди Тьяджоно
3
Не уверен, что мне нужен весь объект $ scope в $ rootScope только для заголовка страницы ...
Хесус Каррера
Я не уверен, где ссылаются на объект $ scope @ JesúsCarrera
cwbutler
Упс, извините, я имею в виду объект $ state
Хесус Каррера
4
просто для подтверждения github.com/angular-ui/ui-router/wiki/Quick-Reference также рекомендует установку $stateи $stateParamsвключение $rootScopeизнутри run.
Марк Петерсон
17

Угловой-UI-маршрутизатор титульный плагин позволяет легко обновить заголовок страницы к статическому или динамическому значению , основанное на текущем состоянии. Он также корректно работает с историей браузера.

Степан Рига
источник
Это кажется лучшим решением на будущее. Я заметил несколько несоответствий с историей браузера при использовании некоторых других решений на этой странице.
angular-ui-router-title действительно кажется лучшим решением. Прежде всего, это бесплатно! Спасибо, Степан.
Дарио
Это очень крошечный исходный файл.
Тайлер Кольер
15

$stateChangeSuccessтеперь устарела в UI-Router 1.x и отключена по умолчанию. Теперь вам нужно будет использовать новую $transitionслужбу.

Решение не так уж и сложно, если вы понимаете, как $transitionработает. Я получил некоторую помощь от @troig, чтобы разобраться во всем этом. Вот что я придумал для обновления названия.

Поместите это в свое приложение Angular 1.6. Обратите внимание, что я использую синтаксис ECMAScript 6; если нет, вам нужно, например, перейти letна var.

.run(function($transitions, $window) {
    $transitions.onSuccess({}, (transition) => {
        let title = transition.to().title;
        if (title) {
            if (title instanceof Function) {
                title = title.call(transition.to(), transition.params());
            }
            $window.document.title = title;
        }
    });

Затем просто добавьте titleстроку в свое состояние:

$stateProvider.state({
    name: "foo",
    url: "/foo",
    template: "<foo-widget layout='row'/>",
    title: "Foo Page""
});

В заголовке появятся слова «Foo Page». (Если состояние не имеет заголовка, заголовок страницы не будет обновлен. Было бы просто обновить приведенный выше код, чтобы предоставить заголовок по умолчанию, если состояние не указывает его.)

Код также позволяет использовать функцию для title. thisИспользуется для вызова функции будет само состояние, а один из аргументов будут параметры состояния, как в этом примере:

$stateProvider.state({
    name: "bar",
    url: "/bar/{code}",
    template: "<bar-widget code='{{code}}' layout='row'/>",
    title: function(params) {
        return `Bar Code ${params.code}`;
    }
});

Для пути URL, /bar/code/123который будет отображать «Штрих-код 123» в качестве заголовка страницы. Обратите внимание, что я использую синтаксис ECMAScript 6 для форматирования строки и извлечения params.code.

Было бы неплохо, если бы кто-нибудь, у кого было время, поместил бы что-то подобное в директиву и опубликовал бы для всех.

Гаррет Уилсон
источник
Использовать dataобъект для пользовательских ключей. titleне существует в StateDeclarationинтерфейсе.
Gaui
5

Присоединение $ state к $ rootcope для использования в любом месте приложения.

app.run(['$rootScope', '$state', '$stateParams',
    function ($rootScope,   $state,   $stateParams) {

        // It's very handy to add references to $state and $stateParams to the $rootScope
        // so that you can access them from any scope within your applications.For example,
        // <li ng-class="{ active: $state.includes('contacts.list') }"> will set the <li>
        // to active whenever 'contacts.list' or one of its decendents is active.
        $rootScope.$state = $state;
        $rootScope.$stateParams = $stateParams;
    }
  ]
)
<title ng-bind="$state.current.name + ' - ui-router'">about - ui-router</title>

симбу
источник
1
В сочетании с добавлением заголовка к каждому штату. Прекрасно работает.
WCByrne
5

Я нашел этот способ очень простым:

  .state('app.staff.client', {
    url: '/client/mine',
    title: 'My Clients'})

а затем в моем HTML вот так:

<h3>{{ $state.current.title }}</h3>
Майк Роуз
источник
5

Просто обновите window.document.title:

.state('login', {
   url: '/login',
   templateUrl: "/Login",
   controller: "loginCtrl",
   onEnter: function($window){$window.document.title = "App Login"; }
})

Таким образом, ng-app не нужно перемещаться вверх к тегу HTML и может оставаться на теле или ниже.

Getsetbro
источник
1
Почему это не лучший ответ? = /
rex
3

Я использую ngMeta , который хорошо работает не только для настройки заголовка страницы, но и для описания. Он позволяет вам установить конкретный заголовок / описание для каждого состояния, значения по умолчанию, когда заголовок / описание не указано, а также суффиксы заголовка по умолчанию (например, '| MySiteName') и значение автора.

$stateProvider
  .state('home', {
    url: '/',
    templateUrl: 'views/home.html',
    controller: 'HomeController',
    meta: {
      'title': 'Home',
      'titleSuffix': ' | MySiteName',
      'description': 'This is my home page description lorem ipsum.'
    },
  })
Дричар
источник
2

Вы действительно очень близки со своим первым ответом / вопросом. Добавьте свой заголовок как объект данных:

.state('home', {
    url: '/home',
    templateUrl : 'views/home.html',
    data : { pageTitle: 'Home' }
})

В вашем index.html привяжите данные непосредственно к заголовку страницы:

<title data-ng-bind="$state.current.data.pageTitle + ' - Optional text'">Failsafe text</title>
Тристан
источник
1

В итоге я получил эту комбинацию ответов Мартина и tasseKATT - просто и без каких-либо вещей, связанных с шаблоном:

$rootScope.$on("$stateChangeSuccess", function (event, toState) {
   $timeout(function () { // Needed to ensure the title is changed *after* the url so that history entries are correct.
     $window.document.title = toState.name; 
   });
});
Роб
источник
Если нет ничего, связанного с шаблоном, как новый разработчик узнает, как менялось название, не спрашивая, как он был изменен?
ton.yeung
если вы используете $ window.document.title $ timeout бесполезно. Я слежу за этим хакерским приемом, чтобы избавиться от тайм-аута $ и цикла $ дайджеста :)
Whisher
1

Почему не просто:

$window.document.title = 'Title';

ОБНОВЛЕНИЕ: Полный код директивы

var DIRECTIVE = 'yourPageTitle';

yourPageTitle.$inject = ['$window'];
function yourPageTitle($window: ng.IWindowService): ng.IDirective {

    return {
        link: (scope, element, attrs) => {

            attrs.$observe(DIRECTIVE, (value: string) => {

                $window.document.title = value;
            });
        }
    }
}

directive(DIRECTIVE, yourPageTitle);

Затем на каждой странице вы просто включите эту директиву:

<section
    your-page-title="{{'somePage' | translate}}">
Мартин
источник
потенциально может быть очень сложно выяснить, почему / как название меняется для тех, кто наследует кодовую базу
ton.yeung
Почему это сложно выяснить? Этот фрагмент должен запускаться из директивы, скажем your-page-titile = "{{'pageTitle' | translate}}. Эта директива будет включена в первый элемент каждой страницы. Красиво и декларативно ясно.
Мартин
о, теперь я понимаю, что вы имеете в виду. я имел в виду, что один лайнер потенциально может быть помещен где угодно, $ rootcope, директива и т.д ...
ton.yeung
0

Если вы используете ES6, это прекрасно работает :).

class PageTitle {
    constructor($compile, $timeout) {
        this.restrict = 'A';
        this._$compile = $compile;
        this.$timeout = $timeout;
    }

    compile(element) {
        return this.link.bind(this);
    }

    link(scope, element, attrs, controller) {
        let defaultTitle = attrs.pageTitle ? attrs.pageTitle : "My Awesome Sauce Site";
        let listener = function(event, toState) {
            let title = defaultTitle;
            if (toState.data && toState.data.title) title = toState.data.title + ' | ' + title;
            $('html head title').text(title);
        };
        scope.$on('$stateChangeStart', listener);
    }
}

export function directiveFactory($compile) {
    return new PageTitle($compile);
}

directiveFactory.injections = ['$compile', '$timeout'];

export default PageTitle;
Т.Гарретт
источник
0

Может быть, вы можете попробовать эту директиву.

https://github.com/afeiship/angular-dynamic-title

Вот пример:

html:

<title dynamic-title>Title</title>

<a href="javascript:;" ui-sref="state1">State1 page</a>
<a href="javascript:;" ui-sref="state2">State2 page</a>

javascript:

var TestModule = angular.module('TestApp', ['ui.router','nx.widget'])
    .config(function ($stateProvider, $urlRouterProvider) {
      //
      // For any unmatched url, redirect to /state1
      $urlRouterProvider.otherwise("/state1");
      //
      // Now set up the states
      $stateProvider
        .state('state1', {
          url: "/state1",
          templateUrl: "partials/state1.html",
          data:{
            pageTitle:'State1 page title11111'
          }
        })
        .state('state2', {
          url: "/state2",
          templateUrl: "partials/state2.html",data:{
            pageTitle:'State2 page title222222'
          }
        });
    })
    .controller('MainCtrl', function ($scope) {
      console.log('initial ctrl!');
    });
Фэй Чжэн
источник
0

Для обновленных версий UI-Router 1.0.0+ ( https://ui-router.github.io/guide/ng1/migrate-to-1_0 )

См. Следующий код

app.directive('pageTitle', [
    '$rootScope',
    '$timeout',
    '$transitions',
    function($rootScope, $timeout,$transitions) {
        return {
            restrict: 'A',
            link: function() {
                var listener = function($transitions) {
                    var default_title = "DEFAULT_TITLE";
                    $timeout(function() {
                        	$rootScope.page_title = ($transitions.$to().data && $transitions.$to().data.pageTitle)
                            ? default_title + ' - ' + $transitions.$to().data.pageTitle : default_title;
                    	
                        
                    });
                };
                $transitions.onSuccess({ }, listener);
            }
        }
    }
])

Добавьте в свой index.html следующее:

<title page-title ng-bind="page_title"></title>

Palsri
источник