Definir o título da página usando UI-Router

101

Estou migrando meu aplicativo baseado em AngularJS para usar o ui-router em vez do roteamento integrado. Eu tenho configurado como mostrado abaixo

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

Como posso usar a variável pageTitle para definir dinamicamente o título da página? Usando o roteamento integrado, eu poderia fazer

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

e vincule a variável em HTML conforme mostrado abaixo

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

Existe um evento semelhante que posso conectar usando o ui-router? Percebi que há funções 'onEnter' e 'onExit', mas elas parecem estar vinculadas a cada estado e exigirão que eu repita o código para definir a variável $ rootScope para cada estado.


fonte
3
Existe um evento $ stateChangeSuccess.
Jerrad

Respostas:

108

Use $stateChangeSuccess.

Você pode colocá-lo em uma diretiva:

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

E:

<title update-title></title>

Demo: http://run.plnkr.co/8tqvzlCw62Tl7t4j/#/home

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

Mesmo com $stateChangeSuccesso $timeoutfoi necessário para que o histórico estivesse correto, pelo menos quando eu me testei.


Edit: 24 de novembro de 2014 - Abordagem declarativa:

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

E:

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

Demo: http://run.plnkr.co/d4s3qBikieq8egX7/#/credits

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

tasseKATT
fonte
Muito otímo. Isso não poderia ficar mais fácil
Matthew Harwood
3
Este exemplo também não funciona corretamente com o histórico (pelo menos no Chrome 37). Se você for entre vários estados e, em seguida, olhar para o seu histórico, o título do item de histórico será o valor da página anterior. Se você for página1 -> página2 -> página3, então olhe para o histórico, o url da página2 será correspondido com o título da página1.
jkjustjoshing 02 de
2
Na verdade, isso não é muito preciso. O título da página muda antes que o hash da URL mude, então o navegador pensa que o novo título é para a página antiga. O histórico do botão Voltar está 1 página fora. Encerrar a element.text(title)chamada em $ timeout funcionou para mim. Editando a postagem original.
jkjustjoshing 02 de
3
Isso não funcionará se o título precisar ser dinâmico com base em alguns parâmetros de url.
Kushagra Gour
10
@KushagraGour Se as necessidades de título para ser dinâmico com base nos $ stateParams, você poderia usar uma função no resolvepara gerá-la, em seguida, acessar o valor "resolvidos" durante o evento stateChangeSuccess $ com: $state.$current.locals.resolve.$$values.NAME_OF_RESOLVE_FUNCTION.
Claus Conrad
91

Existe uma outra maneira de fazer isso combinando a maioria das respostas aqui já. Sei que isso já foi respondido, mas gostaria de mostrar como altero dinamicamente os títulos das páginas com o ui-router.

Se você der uma olhada no aplicativo de exemplo ui-router , eles usam o bloco angular .run para adicionar a variável $ 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;
}])

Com isso definido, você pode facilmente atualizar dinamicamente o título da página com o que postou, mas modificou para usar o estado definido:

Configure o estado da mesma maneira:

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

Mas edite o html um pouco ...

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

Não posso dizer que isso seja melhor do que as respostas anteriores ... mas foi mais fácil para mim entender e implementar. Espero que isso ajude alguém!

cwbutler
fonte
3
Mais declarativa do que a resposta aceita. Eu gosto disso!
Endy Tjahjono
3
Não tenho certeza se eu gostaria de todo o objeto $ scope em $ rootScope apenas para o título da página ...
Jesús Carrera
Não tenho certeza de onde o objeto $ scope está sendo referenciado @ JesúsCarrera
cwbutler
Ups, desculpe, quero dizer o objeto $ state
Jesús Carrera,
4
apenas para confirmar github.com/angular-ui/ui-router/wiki/Quick-Reference também recomenda a configuração $statee $stateParamsa $rootScopepartir de dentro run.
Mark Peterson
17

O plugin angular-ui-router-title facilita a atualização do título da página para um valor estático ou dinâmico baseado no estado atual. Também funciona corretamente com o histórico do navegador.

Stepan Riha
fonte
Esta parece ser a melhor solução daqui para a frente. Percebi várias inconsistências com o histórico do navegador usando algumas das outras soluções nesta página.
angular-ui-router-title parece ser a melhor solução. Acima de tudo, é sem complicações! Obrigado Stepan.
Dário
É um arquivo fonte muito pequeno.
Tyler Collier
15

$stateChangeSuccessagora está obsoleto no UI-Router 1.xe desativado por padrão. Agora você precisará usar o novo $transitionserviço.

Uma solução não é muito difícil, uma vez que você entenda como $transitionfunciona. Recebi ajuda de @troig para entender tudo. Aqui está o que eu criei para atualizar o título.

Coloque isso em seu aplicativo Angular 1.6. Observe que estou usando a sintaxe ECMAScript 6; se não estiver, você precisará, por exemplo, mudar letpara 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;
        }
    });

Em seguida, basta adicionar uma titlestring ao seu estado:

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

Isso fará com que as palavras "Foo Page" apareçam no título. (Se um estado não tiver título, o título da página não será atualizado. Seria uma coisa simples atualizar o código acima para fornecer um título padrão se um estado não o indicar.)

O código também permite que você use uma função para title. O thisusado para chamar a função será o próprio estado, e o único argumento serão os parâmetros de estado, como este exemplo:

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

Para o caminho do URL /bar/code/123que mostraria "Código de barras 123" como título da página. Observe que estou usando a sintaxe ECMAScript 6 para formatar a string e extrair params.code.

Seria bom se alguém com tempo colocasse algo assim em uma diretiva e publicasse para que todos usassem.

Garret Wilson
fonte
Use o dataobjeto para chaves personalizadas. titlenão existe na StateDeclarationinterface.
Gaui
5

Anexando $ state a $ rootscope para usar em qualquer lugar no aplicativo.

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
fonte
1
Combinado com a adição de título a cada estado. Funciona perfeitamente.
WCByrne
5

Achei essa maneira muito fácil:

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

e então no meu HTML assim:

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

Basta atualizar window.document.title:

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

Dessa forma, 'ng-app' não precisa se mover para cima na tag HTML e pode permanecer no corpo ou abaixo.

getsetbro
fonte
1
Por que esta não é a melhor resposta? = /
rex
3

Estou usando o ngMeta , que funciona bem não apenas para definir o título da página, mas também as descrições. Ele permite que você defina um título / descrição específico para cada estado, padrões para quando um título / descrição não for especificado, bem como sufixos de título padrão (isto é, '| MySiteName') e valor do 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
fonte
2

Você está realmente muito perto de sua primeira resposta / pergunta. Adicione seu título como um objeto de dados:

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

Em seu index.html vincule os dados diretamente ao título da página:

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

Eu terminei com esta combinação das respostas de Martin e tasseKATT - simples e sem qualquer coisa relacionada a template:

$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; 
   });
});
Roubar
fonte
Se não houver nenhum material relacionado a template, como um novo desenvolvedor saberia como o título estava sendo alterado sem perguntar como ele estava sendo alterado?
ton.yeung
se você usar $ window.document.title $ timeout é inútil. Estou seguindo este truque apenas para me livrar de $ timeout e de um ciclo de $ digest :)
Whisher
1

Por que não apenas:

$window.document.title = 'Title';

ATUALIZAÇÃO: Código de diretriz 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);

Então, em cada página, você apenas incluiria esta diretiva:

<section
    your-page-title="{{'somePage' | translate}}">
Martin
fonte
pode ser potencialmente muito difícil descobrir por que / como o título está mudando para quem herda a base do código
ton.yeung
Por que isso seria difícil de descobrir? Este recorte deve ser acionado a partir de uma diretiva, digamos your-page-titile = "{{'pageTitle' | translate}}. Essa diretiva seria incluída no primeiro elemento de cada página. Agradável e claro declarativo.
Martin
oh, com a edição, eu vejo o que você quer dizer agora. o que eu quis dizer é que o único forro pode ser colocado em qualquer lugar, $ rootscope, diretiva, etc ...
ton.yeung
0

Se você estiver usando ES6, isso funciona muito bem :).

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

Talvez você possa tentar esta diretiva.

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

Aqui está o exemplo:

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

Para versões atualizadas do UI-Router 1.0.0+, ( https://ui-router.github.io/guide/ng1/migrate-to-1_0 )

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

Adicione o seguinte ao seu index.html:

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

Palsri
fonte