Establecer el título de la página usando UI-Router

101

Estoy migrando mi aplicación basada en AngularJS para usar ui-router en lugar del enrutamiento integrado. Lo tengo configurado como se muestra a continuación

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

¿Cómo puedo usar la variable pageTitle para establecer dinámicamente el título de la página? Usando el enrutamiento integrado, podría hacer

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

y luego enlazar la variable en HTML como se muestra a continuación

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

¿Hay un evento similar al que pueda conectarme usando ui-router? Me di cuenta de que hay funciones 'onEnter' y 'onSalir', pero parecen estar vinculadas a cada estado y requerirán que repita el código para establecer la variable $ rootScope para cada estado.


fuente
3
Hay un evento $ stateChangeSuccess.
Jerrad

Respuestas:

108

Utilice $stateChangeSuccess.

Puedes ponerlo en una directiva:

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

Y:

<title update-title></title>

Demostración: http://run.plnkr.co/8tqvzlCw62Tl7t4j/#/home

Código: http://plnkr.co/edit/XO6RyBPURQFPodoFdYgX?p=preview

Incluso con $stateChangeSuccessel $timeoutha sido necesario para que el historial sea correcto, al menos cuando me he puesto a prueba.


Edición: 24 de noviembre de 2014 - Enfoque declarativo:

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

Y:

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

Demostración: http://run.plnkr.co/d4s3qBikieq8egX7/#/credits

Código: http://plnkr.co/edit/NpzQsxYGofswWQUBGthR?p=preview

tasseKATT
fuente
Super grandioso. Esto no podría ser más fácil
Matthew Harwood
3
Este ejemplo tampoco funciona correctamente con el historial (al menos en Chrome 37). Si pasa de un estado a otro, mire su historial, el título del elemento del historial será el valor de la página anterior. Si va a página1 -> página2 -> página3, luego mira el historial, la URL de la página2 coincidirá con el título de la página1.
jkjustjoshing
2
En realidad, eso no es del todo exacto. El título de la página cambia antes de que cambie el hash de la URL, por lo que el navegador cree que el nuevo título es para la página anterior. El historial del botón de retroceso está a una página de retroceso. Envolver la element.text(title)llamada en un $ timeout funcionó para mí. Editando la publicación original.
jkjustjoshing
3
Esto no funcionará si el título debe ser dinámico en función de algunos parámetros de URL.
Kushagra Gour
10
@KushagraGour Si las necesidades de título a ser dinámico basado en los $ stateParams, podría utilizar una función en resolvegenerarla, a continuación, acceder al valor "resuelto" durante el evento de $ stateChangeSuccess con: $state.$current.locals.resolve.$$values.NAME_OF_RESOLVE_FUNCTION.
Claus Conrad
91

Hay otra forma de hacer esto combinando la mayoría de las respuestas aquí. Sé que esto ya está respondido, pero quería mostrar la forma en que cambio dinámicamente los títulos de las páginas con ui-router.

Si echa un vistazo a la aplicación de muestra ui-router , usan el bloque angular .run para agregar la variable $ state a $ 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;
}])

Con esto definido, puede actualizar fácilmente y dinámicamente el título de su página con lo que ha publicado pero modificado para usar el estado definido:

Configure el estado de la misma manera:

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

Pero edite un poco el html ...

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

No puedo decir que esto sea mejor que las respuestas anteriores ... pero fue más fácil de entender e implementar para mí. ¡Espero que esto ayude a alguien!

cwbutler
fuente
3
Más declarativo que la respuesta aceptada. ¡Me gusta esto!
Endy Tjahjono
3
No estoy seguro de si me gustaría todo el objeto $ scope en $ rootScope solo para el título de la página ...
Jesús Carrera
No estoy seguro de dónde se hace referencia al objeto $ scope @ JesúsCarrera
cwbutler
Ups, lo siento, me refiero al objeto $ state
Jesús Carrera
4
sólo para confirmar github.com/angular-ui/ui-router/wiki/Quick-Reference también recomienda ajuste $statey $stateParamsel $rootScopede dentro run.
Mark Peterson
17

El complemento angular-ui-router-title facilita la actualización del título de la página a un valor estático o dinámico según el estado actual. También funciona correctamente con el historial del navegador.

Stepan Riha
fuente
Esta parece ser la mejor solución en el futuro. He notado varias inconsistencias con el historial del navegador usando algunas de las otras soluciones en esta página.
angular-ui-router-title parece ser la mejor solución. Sobre todo, ¡no tiene complicaciones! Gracias Stepan.
Dário
Es un archivo fuente muy pequeño.
Tyler Collier
15

$stateChangeSuccessahora está en desuso en UI-Router 1.xy deshabilitado por defecto. Ahora deberá utilizar el nuevo $transitionservicio.

Una solución no es demasiado difícil una vez que comprende cómo $transitionfunciona. Recibí ayuda de @troig para entenderlo todo. Esto es lo que se me ocurrió para actualizar el título.

Pon esto en tu aplicación Angular 1.6. Tenga en cuenta que estoy usando la sintaxis ECMAScript 6; si no es así, necesitará, por ejemplo, cambiar leta 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;
        }
    });

Luego simplemente agregue una titlecadena a su estado:

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

Eso hará que las palabras "Foo Page" aparezcan en el título. (Si un estado no tiene título, el título de la página no se actualizará. Sería muy sencillo actualizar el código anterior para proporcionar un título predeterminado si un estado no indica uno).

El código también le permite usar una función para title. El thisusado para llamar a la función será el estado mismo, y el único argumento serán los parámetros de estado, como este ejemplo:

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

Para la ruta de URL /bar/code/123que mostraría "Código de barras 123" como título de la página. Tenga en cuenta que estoy usando la sintaxis ECMAScript 6 para formatear la cadena y extraerla params.code.

Sería bueno si alguien que tuviera tiempo pusiera algo como esto en una directiva y lo publicara para que todos lo usen.

Garret Wilson
fuente
Usar dataobjeto para claves personalizadas. titleno existe en la StateDeclarationinterfaz.
Gaui
5

Adjuntando $ state a $ rootscope para usar en cualquier lugar de la aplicación.

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>

simbu
fuente
1
Combinado esto con la adición de un título a cada estado. Funciona perfecto.
WCByrne
5

Encontré esta forma realmente fácil:

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

y luego en mi HTML así:

<h3>{{ $state.current.title }}</h3>
Mike Rouse
fuente
5

Simplemente actualice window.document.title:

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

De esa manera, 'ng-app' no necesita subir a la etiqueta HTML y puede permanecer en el cuerpo o más abajo.

getsetbro
fuente
1
¿Por qué no es esta la mejor respuesta? = /
rex
3

Estoy usando ngMeta , que funciona bien no solo para configurar el título de la página, sino también para las descripciones. Le permite establecer un título / descripción específico para cada estado, valores predeterminados para cuando no se especifica un título / descripción, así como los sufijos de título predeterminados (es decir, '| MySiteName') y el valor del autor.

$stateProvider
  .state('home', {
    url: '/',
    templateUrl: 'views/home.html',
    controller: 'HomeController',
    meta: {
      'title': 'Home',
      'titleSuffix': ' | MySiteName',
      'description': 'This is my home page description lorem ipsum.'
    },
  })
drichar
fuente
2

En realidad, estás muy cerca de tu primera respuesta / pregunta. Agregue su título como un objeto de datos:

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

En su index.html, vincule los datos directamente al título de la página:

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

Terminé con esta combinación de las respuestas de Martin y tasseKATT, simple y sin ningún material relacionado con la plantilla:

$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; 
   });
});
Robar
fuente
Si no hay nada relacionado con la plantilla, ¿cómo sabría un nuevo desarrollador cómo se estaba cambiando el título sin preguntar cómo se estaba cambiando?
ton.yeung
si usa $ window.document.title $ timeout es inútil. Estoy siguiendo este truco solo para deshacerme de $ timeout y un ciclo de $ digest :)
Whisher
1

Por qué no solo:

$window.document.title = 'Title';

ACTUALIZACIÓN: Código de directiva completo

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

Luego, en cada página, solo incluiría esta directiva:

<section
    your-page-title="{{'somePage' | translate}}">
Martín
fuente
potencialmente podría ser muy difícil averiguar por qué / cómo el título está cambiando para cualquiera que herede el código base
ton.yeung
¿Por qué sería difícil de averiguar? Este recorte debe activarse desde una directiva, digamos your-page-titile = "{{'pageTitle' | translate}}. Esta directiva se incluiría en el primer elemento de cada página. Agradable y claro declarativo.
Martin
oh, con la edición, veo lo que quieres decir ahora. lo que quise decir fue que el delineador podría potencialmente colocarse en cualquier lugar, $ rootscope, directive, etc ...
ton.yeung
0

Si está utilizando ES6, esto funciona 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;
TGarrett
fuente
0

Quizás puedas probar esta directiva.

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

Aquí está el ejemplo:

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!');
    });
Fei Zheng
fuente
0

Para las versiones actualizadas de UI-Router 1.0.0+, ( https://ui-router.github.io/guide/ng1/migrate-to-1_0 )

Consulte el siguiente código

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

Agregue lo siguiente a su index.html:

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

Palsri
fuente