Définir le titre de la page à l'aide de UI-Router


101

Je migre mon application basée sur AngularJS pour utiliser ui-router au lieu du routage intégré. Je l'ai configuré comme indiqué ci-dessous

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

Comment puis-je utiliser la variable pageTitle pour définir dynamiquement le titre de la page? En utilisant le routage intégré, je pourrais faire

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

puis liez la variable en HTML comme indiqué ci-dessous

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

Y a-t-il un événement similaire auquel je peux me connecter en utilisant ui-router? J'ai remarqué qu'il existe des fonctions «onEnter» et «onExit», mais elles semblent être liées à chaque état et m'obligeront à répéter le code pour définir la variable $ rootScope pour chaque état.


3
Il existe un événement $ stateChangeSuccess.
Jerrad

Réponses:


108

Utilisez $stateChangeSuccess.

Vous pouvez le mettre dans une directive:

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

Et:

<title update-title></title>

Démo: http://run.plnkr.co/8tqvzlCw62Tl7t4j/#/home

Code: http://plnkr.co/edit/XO6RyBPURQFPodoFdYgX?p=preview

Même avec $stateChangeSuccessle $timeouta été nécessaire pour que l'histoire soit correcte, du moins quand je me suis testé.


Edit: 24 novembre 2014 - Approche déclarative:

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

Et:

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

Démo: http://run.plnkr.co/d4s3qBikieq8egX7/#/credits

Code: http://plnkr.co/edit/NpzQsxYGofswWQUBGthR?p=preview


Super génial. Cela ne pourrait pas être plus facile
Matthew Harwood

3
Cet exemple ne fonctionne pas non plus correctement avec l'historique (au moins dans Chrome 37). Si vous passez entre différents états, puis regardez votre historique, le titre de l'élément d'historique sera la valeur de la page précédente. Si vous allez page1 -> page2 -> page3, puis regardez l'historique, l'URL de la page2 correspondra au titre de la page1.
jkjustjoshing

2
En fait, ce n'est pas tout à fait exact. Le titre de la page change avant que le hachage de l'URL ne change, de sorte que le navigateur pense que le nouveau titre est pour l'ancienne page. L'historique du bouton de retour est alors de 1 page. Emballer l' element.text(title)appel dans un délai d'attente $ a fonctionné pour moi. Modification du message d'origine.
jkjustjoshing

3
Cela ne fonctionnera pas si le titre doit être dynamique en fonction de certains paramètres d'URL.
Kushagra Gour

10
@KushagraGour Si le titre doit être dynamique basé sur $ stateParams, vous pouvez utiliser une fonction dans resolvepour le générer, puis accéder à la valeur "résolue" pendant l'événement $ stateChangeSuccess avec:$state.$current.locals.resolve.$$values.NAME_OF_RESOLVE_FUNCTION .
Claus Conrad

91

Il existe une autre façon de faire cela en combinant déjà la plupart des réponses ici. Je sais que cela a déjà été répondu, mais je voulais montrer comment je change dynamiquement les titres de page avec ui-router.

Si vous jetez un œil à l' exemple d'application ui-router , ils utilisent le bloc angulaire .run pour ajouter la variable $ 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;
}])

Avec cette définition, vous pouvez facilement mettre à jour dynamiquement le titre de votre page avec ce que vous avez publié mais modifié pour utiliser l'état défini:

Configurez l'état de la même manière:

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

Mais modifiez un peu le html ...

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

Je ne peux pas dire que ce soit mieux que les réponses précédentes ... mais c'était plus facile pour moi à comprendre et à mettre en œuvre. J'espère que cela aide quelqu'un!


3
Plus déclaratif que la réponse acceptée. J'aime ça!
Endy Tjahjono

3
Je ne sais pas si je voudrais tout l'objet $ scope dans $ rootScope juste pour le titre de la page ...
Jesús Carrera

Je ne sais pas où l'objet $ scope est référencé @ JesúsCarrera
cwbutler

Ups, désolé, je veux dire l'objet $ state
Jesús Carrera

4
juste pour confirmer github.com/angular-ui/ui-router/wiki/Quick-Reference recommande également cadre $stateet $stateParamsà l' $rootScopeintérieur run.
Mark Peterson

17

Le plugin angular-ui-router-title facilite la mise à jour du titre de la page avec une valeur statique ou dynamique en fonction de l'état actuel. Il fonctionne également correctement avec l'historique du navigateur.


Cela semble être la meilleure solution pour l'avenir. J'ai remarqué plusieurs incohérences avec l'historique du navigateur en utilisant certaines des autres solutions de cette page.

angular-ui-router-title semble être la meilleure solution. Surtout, c'est sans tracas! Merci Stepan.
Dário

C'est un tout petit fichier source.
Tyler Collier

15

$stateChangeSuccessest désormais obsolète dans UI-Router 1.x et désactivé par défaut. Vous devrez maintenant utiliser le nouveau$transition service.

Une solution n'est pas trop difficile une fois que vous comprenez comment $transitionfonctionne. J'ai de l' aide de @troig pour tout comprendre. Voici ce que j'ai proposé pour mettre à jour le titre.

Mettez ceci dans votre application Angular 1.6. Notez que j'utilise la syntaxe ECMAScript 6; si vous ne l'êtes pas, vous devrez par exemple passer 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;
        }
    });

Ensuite, ajoutez simplement une titlechaîne à votre état:

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

Cela fera apparaître les mots "Foo Page" dans le titre. (Si un état n'a pas de titre, le titre de la page ne sera pas mis à jour. Ce serait une chose simple de mettre à jour le code ci-dessus pour fournir un titre par défaut si un état ne l'indique pas.)

Le code vous permet également d'utiliser une fonction pour title. Le thisutilisé pour appeler la fonction sera l'état lui-même, et le seul argument sera les paramètres d'état, comme cet exemple:

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

Pour le chemin de l'URL /bar/code/123qui afficherait «Code à barres 123» comme titre de la page. Notez que j'utilise la syntaxe ECMAScript 6 pour formater la chaîne et extraire params.code.

Ce serait bien si quelqu'un qui en avait le temps mettait quelque chose comme ça dans une directive et la publiait pour que tout le monde l'utilise.


Utilisez un dataobjet pour les clés personnalisées. titlen'existe pas sur l' StateDeclarationinterface.
Gaui

5

Attacher $ state à $ rootscope pour l'utiliser n'importe où dans l'application.

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
Combiné cela avec l'ajout d'un titre à chaque état. Fonctionne parfaitement.
WCByrne

5

J'ai trouvé ce moyen très simple:

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

puis dans mon HTML comme ceci:

<h3>{{ $state.current.title }}</h3>

5

Mettez simplement à jour window.document.title:

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

De cette façon, «ng-app» n'a pas besoin de se déplacer vers la balise HTML et peut rester sur le corps ou plus bas.


1
Pourquoi n'est-ce pas la meilleure réponse? = /
rex

3

J'utilise ngMeta , qui fonctionne bien non seulement pour définir le titre de la page, mais également pour les descriptions. Il vous permet de définir un titre / une description spécifique pour chaque état, les valeurs par défaut lorsqu'un titre / une description n'est pas spécifié, ainsi que les suffixes de titre par défaut (c'est-à-dire «| MySiteName») et la valeur de l'auteur.

$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

Vous êtes en fait très proche de votre première réponse / question. Ajoutez votre titre en tant qu'objet de données:

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

Dans votre index.html, liez les données directement au titre de la page:

<title data-ng-bind="$state.current.data.pageTitle + ' - Optional text'">Failsafe text</title>

1

Je me suis retrouvé avec cette combinaison des réponses de Martin et tasseKATT - simple et sans aucun élément lié au modèle:

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

s'il n'y a aucun élément lié au modèle, comment un nouveau développeur pourrait-il savoir comment le titre a été modifié sans demander comment il a été modifié?
ton.yeung

si vous utilisez $ window.document.title $ timeout est inutile. Je suis ce hackish juste pour me débarrasser de $ timeout et d'un cycle $ digest :)
Whisher

1

Pourquoi pas simplement:

$window.document.title = 'Title';

MISE À JOUR: Code de directive complet

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

Ensuite, dans chaque page, vous incluez simplement cette directive:

<section
    your-page-title="{{'somePage' | translate}}">

pourrait potentiellement être très difficile de savoir pourquoi / comment le titre change pour quiconque hérite de la base de code
ton.yeung

Pourquoi serait-ce difficile à découvrir? Ce snipped devrait être déclenché à partir d'une directive, disons your-page-titile = "{{'pageTitle' | translate}}. Cette directive serait incluse dans le premier élément de chaque page. Agréable et déclaratif clair.
Martin

oh, avec l'édition, je vois ce que tu veux dire maintenant. ce que je voulais dire, c'est que la seule ligne pouvait potentiellement être placée n'importe où, $ rootscope, directive, etc ...
ton.yeung

0

Si vous utilisez ES6, cela fonctionne très bien :).

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

Vous pouvez peut-être essayer cette directive.

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

Voici l'exemple:

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

Pour les versions d'UI-Router 1.0.0+ mises à jour, ( https://ui-router.github.io/guide/ng1/migrate-to-1_0 )

Reportez-vous au code suivant

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

Ajoutez ce qui suit à votre index.html:

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.