web-dev-qa-db-fra.com

Comment changer dynamiquement l'en-tête en fonction de la vue partielle AngularJS?

J'utilise ng-view pour inclure des vues partielles d'AngularJS et je souhaite mettre à jour le titre de la page et les balises d'en-tête h1 en fonction de la vue incluse. Celles-ci sont cependant hors de la portée des contrôleurs de vue partielle et je ne vois donc pas comment les lier à des ensembles de données dans les contrôleurs.

S'il s'agissait d'ASP.NET MVC, vous pouvez utiliser @ViewBag pour le faire, mais je ne connais pas l'équivalent dans AngularJS. J'ai effectué une recherche sur les services partagés, les événements, etc., mais je ne parviens toujours pas à le faire fonctionner. Toute façon de modifier mon exemple afin que cela fonctionne serait très appréciée.

Mon HTML:

<html data-ng-app="myModule">
<head>
<!-- include js files -->
<title><!-- should changed when ng-view changes --></title>
</head>
<body>
<h1><!-- should changed when ng-view changes --></h1>

<div data-ng-view></div>

</body>
</html>

Mon JavaScript:

var myModule = angular.module('myModule', []);
myModule.config(['$routeProvider', function($routeProvider) {
    $routeProvider.
        when('/test1', {templateUrl: 'test1.html', controller: Test1Ctrl}).
        when('/test2', {templateUrl: 'test2.html', controller: Test2Ctrl}).
        otherwise({redirectTo: '/test1'});
}]);

function Test1Ctrl($scope, $http) { $scope.header = "Test 1"; 
                                  /* ^ how can I put this in title and h1 */ }
function Test2Ctrl($scope, $http) { $scope.header = "Test 2"; }
406
mikel

Vous pouvez définir le contrôleur au niveau <html>.

 <html ng-app="app" ng-controller="titleCtrl">
   <head>
     <title>{{ Page.title() }}</title>
 ...

Vous créez le service: Page et le modifiez à partir de contrôleurs.

myModule.factory('Page', function() {
   var title = 'default';
   return {
     title: function() { return title; },
     setTitle: function(newTitle) { title = newTitle }
   };
});

Injectez Page et appelez 'Page.setTitle ()' à partir des contrôleurs.

Voici l'exemple concret: http://plnkr.co/edit/0e7T6l

337
Tosh

Je viens de découvrir un bon moyen de définir le titre de votre page si vous utilisez le routage:

JavaScript:

var myApp = angular.module('myApp', ['ngResource'])

myApp.config(
    ['$routeProvider', function($routeProvider) {
        $routeProvider.when('/', {
            title: 'Home',
            templateUrl: '/Assets/Views/Home.html',
            controller: 'HomeController'
        });
        $routeProvider.when('/Product/:id', {
            title: 'Product',
            templateUrl: '/Assets/Views/Product.html',
            controller: 'ProductController'
        });
    }]);

myApp.run(['$rootScope', function($rootScope) {
    $rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
        $rootScope.title = current.$$route.title;
    });
}]);

HTML:

<!DOCTYPE html>
<html ng-app="myApp">
<head>
    <title ng-bind="'myApp &mdash; ' + title">myApp</title>
...

Edit: utiliser l'attribut ng-bind au lieu de curlies {{}} pour qu'ils ne s'affichent pas au chargement

630
jkoreska

Notez que vous pouvez également définir le titre directement avec javascript, c.-à-d.,

$window.document.title = someTitleYouCreated;

Cela ne crée pas de liaison de données, mais cela suffit lorsque vous mettez ng-app dans la balise <html>, cela pose problème. (Par exemple, en utilisant des modèles JSP où <head> est défini exactement à un endroit, mais vous avez plusieurs applications.)

186
broc.seib

Déclarer ng-app sur l'élément html fournit une étendue racine pour les éléments head et body.

Par conséquent, dans votre contrôleur, injectez $rootScope et définissez une propriété d’en-tête sur ceci:

function Test1Ctrl($rootScope, $scope, $http) { $rootScope.header = "Test 1"; }

function Test2Ctrl($rootScope, $scope, $http) { $rootScope.header = "Test 2"; }

et dans votre page:

<title ng-bind="header"></title>
119
Andy Hitchman

Le module angularjs-viewhead montre un mécanisme permettant de définir le titre par vue à l'aide d'une seule directive personnalisée.

Il peut soit être appliqué à un élément de vue existant dont le contenu est déjà le titre de la vue:

<h2 view-title>About This Site</h2>

... ou peut être utilisé en tant qu'élément autonome, auquel cas l'élément sera invisible dans le document rendu et ne sera utilisé que pour définir le titre de la vue:

<view-title>About This Site</view-title>

Le contenu de cette directive est rendu disponible dans la portée racine en tant que viewTitle, de sorte qu'il peut être utilisé sur l'élément title de la même manière que toute autre variable:

<title ng-bind-template="{{viewTitle}} - My Site">My Site</title>

Il peut également être utilisé dans tout autre emplacement pouvant "voir" la portée racine. Par exemple:

<h1>{{viewTitle}}</h1>

Cette solution permet de définir le titre via le même mécanisme que celui utilisé pour contrôler le reste de la présentation: les modèles AngularJS. Cela évite d'encombrer les contrôleurs avec cette logique de présentation. Le contrôleur doit mettre à disposition toutes les données qui seront utilisées pour informer le titre, mais le modèle détermine de manière définitive la manière de le présenter et peut utilisez l'interpolation d'expression et les filtres pour lier les données de l'étendue comme d'habitude.

(Avertissement: je suis l'auteur de ce module, mais je n'y fais référence que dans l'espoir que cela aidera quelqu'un d'autre à résoudre ce problème.)

42
Martin Atkins

Voici une solution adaptée qui fonctionne pour moi et qui ne nécessite pas l’injection de $ rootScope dans les contrôleurs pour la définition de titres de page spécifiques à une ressource.

Dans le modèle principal:

<html data-ng-app="myApp">
    <head>
    <title data-ng-bind="page.title"></title>
    ...

Dans la configuration de routage:

$routeProvider.when('/products', {
    title: 'Products',
    templateUrl: '/partials/products.list.html',
    controller: 'ProductsController'
});

$routeProvider.when('/products/:id', {
    templateUrl: '/partials/products.detail.html',
    controller: 'ProductController'
});

Et dans le bloc d'exécution:

myApp.run(['$rootScope', function($rootScope) {
    $rootScope.page = {
        setTitle: function(title) {
            this.title = title + ' | Site Name';
        }
    }

    $rootScope.$on('$routeChangeSuccess', function(event, current, previous) {
        $rootScope.page.setTitle(current.$$route.title || 'Default Title');
    });
}]);

Enfin dans le contrôleur:

function ProductController($scope) {
    //Load product or use resolve in routing
    $scope.page.setTitle($scope.product.name);
}
31
Mr Hash

la solution de jkoreska est parfaite si vous connaissez les titres à l'avance, mais vous devrez peut-être définir le titre en fonction des données que vous obtenez d'une ressource, etc.

Ma solution nécessite un seul service. Comme rootScope est la base de tous les éléments du DOM, il n’est pas nécessaire de placer un contrôleur sur l’élément html comme l’a mentionné une personne.

Page.js

app.service('Page', function($rootScope){
    return {
        setTitle: function(title){
            $rootScope.title = title;
        }
    }
});

index.jade

doctype html
html(ng-app='app')
head
    title(ng-bind='title')
// ...

Tous les contrôleurs devant changer de titre

app.controller('SomeController', function(Page){
    Page.setTitle("Some Title");
});
15
Deminetix

Un moyen propre qui permet de définir dynamiquement un titre ou une méta description. Par exemple, j'utilise ui-router mais vous pouvez utiliser ngRoute de la même manière.

var myApp = angular.module('myApp', ['ui.router'])

myApp.config(
    ['$stateProvider', function($stateProvider) {
        $stateProvider.state('product', {
            url: '/product/{id}',
            templateUrl: 'views/product.html',
            resolve: {
                meta: ['$rootScope', '$stateParams', function ($rootScope, $stateParams) {
                    var title = "Product " + $stateParams.id,
                        description = "Product " + $stateParams.id;
                    $rootScope.meta = {title: title, description: description};
                }]

                // Or using server side title and description
                meta: ['$rootScope', '$stateParams', '$http', function ($rootScope, $stateParams, $http) {
                    return $http({method: 'GET', url: 'api/product/ + $stateParams.id'})
                        .then (function (product) {
                            $rootScope.meta = {title: product.title, description: product.description};
                        });
                }]

            }
            controller: 'ProductController'
        });
    }]);

HTML:

<!DOCTYPE html>
<html ng-app="myApp">
<head>
    <title ng-bind="meta.title + ' | My App'">myApp</title>
...
11
Alex Soroka

Alternativement, si vous utilisez i-router :

index.html

<!DOCTYPE html>
<html ng-app="myApp">
<head>
    <title ng-bind="$state.current.data.title || 'App'">App</title>

Routage

$stateProvider
  .state('home', {
      url: '/',
      templateUrl: 'views/home.html',
      data: {
        title: 'Welcome Home.'
      }
  }
9
Nathan Kot

Solution personnalisée basée sur des événements

Voici une autre approche qui n’a pas été mentionnée par les autres ici (à ce jour).

Vous pouvez utiliser des événements personnalisés comme suit:

// your index.html template
<html ng-app="app">
<head>
<title ng-bind="pageTitle">My App</title>

// your main app controller that is declared on the <html> element
app.controller('AppController', function($scope) {
    $scope.$on('title-updated', function(newTitle) {
        $scope.pageTitle = newTitle;
    });
});

// some controller somewhere deep inside your app
mySubmodule.controller('SomeController', function($scope, dynamicService) {
    $scope.$emit('title-updated', dynamicService.title);
});

Cette approche présente l’avantage de ne pas nécessiter de services supplémentaires pour être écrits, puis injectés dans tous les contrôleurs devant définir le titre, et elle n’utilise pas non plus le $rootScope. Cela vous permet également de définir un titre dynamique (comme dans l'exemple de code), ce qui n'est pas possible à l'aide d'attributs de données personnalisés sur l'objet config du routeur (pour autant que je sache au moins).

7
Michael Bromley

Pour les scénarios pour lesquels vous n'avez pas d'application ngApp contenant la balise title, injectez simplement un service aux contrôleurs devant définir le titre de la fenêtre.

var app = angular.module('MyApp', []);

app.controller('MyController', function($scope, SomeService, Title){
    var serviceData = SomeService.get();
    Title.set("Title of the page about " + serviceData.firstname);
});

app.factory('SomeService', function ($window) {
    return {
        get: function(){
            return { firstname : "Joe" };
        }
    };
});

app.factory('Title', function ($window) {
    return {
        set: function(val){
            $window.document.title = val;
        }
    };
});

Exemple de travail ... http://jsfiddle.net/8m379/1/

5
JeremyWeir

Si vous n’avez pas le contrôle sur l’élément title (comme le formulaire Web asp.net), vous pouvez utiliser quelque chose.

var app = angular.module("myApp")
    .config(function ($routeProvider) {
                $routeProvider.when('/', {
                                            title: 'My Page Title',
                                            controller: 'MyController',
                                            templateUrl: 'view/myView.html'
                                        })
                            .otherwise({ redirectTo: '/' });
    })
    .run(function ($rootScope) {
        $rootScope.$on("$routeChangeSuccess", function (event, currentRoute, previousRoute) {
            document.title = currentRoute.title;
        });
    });
5
Ashish

Manière simple et sale en utilisant $rootScope:

<html ng-app="project">
<head>
<title ng-bind="title">Placeholder title</title>

Dans vos contrôleurs, lorsque vous disposez des données nécessaires pour créer le titre, procédez comme suit:

$rootScope.title = 'Page X'
4
user1338062

Aucune de ces réponses ne semblant assez intuitive, j'ai donc créé une petite directive pour le faire. De cette façon, vous pouvez déclarer le titre dans la page, normalement, et le rendre dynamique.

angular.module('myModule').directive('pageTitle', function() {
    return {
        restrict: 'EA',
        link: function($scope, $element) {
            var el = $element[0];
            el.hidden = true; // So the text not actually visible on the page

            var text = function() {
                return el.innerHTML;
            };
            var setTitle = function(title) {
                document.title = title;
            };
            $scope.$watch(text, setTitle);
        }
    };
});

Vous devrez bien sûr changer le nom du module pour qu'il corresponde au vôtre.

Pour l'utiliser, jetez-le à votre vue, comme vous le feriez pour une balise <title> normale:

<page-title>{{titleText}}</page-title>

Vous pouvez également simplement inclure du texte brut si vous n'en avez pas besoin par dynamique:

<page-title>Subpage X</page-title>

Vous pouvez également utiliser un attribut pour le rendre plus convivial pour IE:

<div page-title>Title: {{titleText}}</div>

Vous pouvez bien sûr insérer le texte de votre choix dans la balise, y compris le code Angular. Dans cet exemple, il recherchera $scope.titleText dans le contrôleur dans lequel se trouve actuellement la balise de titre personnalisé.

Assurez-vous simplement que votre page ne comporte pas plusieurs balises de titre de page, sinon elles se chevaucheront.

Exemple Plunker ici http://plnkr.co/edit/nK63te7BSbCxLeZ2ADHV . Vous devrez télécharger le fichier Zip et l'exécuter localement afin de voir le changement de titre.

4
MikeyB

Voici une façon différente de modifier le titre. Peut-être pas aussi évolutif qu'une fonction d'usine (qui pourrait éventuellement gérer des pages illimitées) mais il était plus facile pour moi de comprendre:

Dans mon index.html j'ai commencé comme ça:

    <!DOCTYPE html>
      <html ng-app="app">
        <head>
          <title ng-bind-template="{{title}}">Generic Title That You'll Never See</title>

Puis j'ai fait un partiel appelé "nav.html":

<div ng-init="$root.title = 'Welcome'">
    <ul class="unstyled">
        <li><a href="#/login" ng-click="$root.title = 'Login'">Login</a></li>
        <li><a href="#/home" ng-click="$root.title = 'Home'">Home</a></li>
        <li><a href="#/admin" ng-click="$root.title = 'Admin'">Admin</a></li>
        <li><a href="#/critters" ng-click="$root.title = 'Crispy'">Critters</a></li>
    </ul>
</div>

Ensuite, je suis retourné à "index.html" et j'ai ajouté le fichier nav.html utilisant ng-include et ng-view pour mes partiels:

<body class="ng-cloak" ng-controller="MainCtrl">
    <div ng-include="'partials/nav.html'"></div>
    <div>
        <div ng-view></div>
    </div>

Remarquez que ng-cloak? Cela n’a rien à voir avec cette réponse mais il cache la page jusqu’à ce que le chargement soit terminé, une jolie touche :) Apprenez comment ici: Angularjs - Les éléments ng-cloak/ng-show clignotent

Voici le module de base. Je le mets dans un fichier appelé "app.js":

(function () {
    'use strict';
    var app = angular.module("app", ["ngResource"]);

    app.config(function ($routeProvider) {
        // configure routes
        $routeProvider.when("/", {
            templateUrl: "partials/home.html",
            controller:"MainCtrl"
        })
            .when("/home", {
            templateUrl: "partials/home.html",
            controller:"MainCtrl"
        })
            .when("/login", {
            templateUrl:"partials/login.html",
            controller:"LoginCtrl"
        })
            .when("/admin", {
            templateUrl:"partials/admin.html",
            controller:"AdminCtrl"
        })
            .when("/critters", {
            templateUrl:"partials/critters.html",
            controller:"CritterCtrl"
        })
            .when("/critters/:id", {
            templateUrl:"partials/critter-detail.html",
            controller:"CritterDetailCtrl"
        })
            .otherwise({redirectTo:"/home"});
    });

}());

Si vous regardez vers la fin du module, vous verrez que j’ai une page de détail sur la créature basée sur: id. C'est un partiel utilisé dans la page Crispy Critters. [Corny, je sais - c’est peut-être un site qui célèbre toutes sortes de pépites de poulet;) Quoi qu’il en soit, vous pouvez mettre à jour le titre quand un utilisateur clique sur n’importe quel lien; c'est là que la mise à jour $ root.title irait, comme vous l'avez vu dans le fichier nav.html ci-dessus:

<a href="#/critters/1" ng-click="$root.title = 'Critter 1'">Critter 1</a>
<a href="#/critters/2" ng-click="$root.title = 'Critter 2'">Critter 2</a>
<a href="#/critters/3" ng-click="$root.title = 'Critter 3'">Critter 3</a>

Désolé si venteux mais je préfère un post qui donne suffisamment de détails pour le rendre opérationnel. Notez que la page d'exemple dans la documentation AngularJS est obsolète et affiche une version 0.9 de ng-bind-template. Vous pouvez voir que ce n'est pas si différent.

Après coup: vous le savez, mais il est là pour tout le monde. au bas du fichier index.html, il faut inclure le fichier app.js avec le module:

        <!-- APP -->
        <script type="text/javascript" src="js/app.js"></script>
    </body>
</html>
3
noogrub

Solution simpliste pour angular-ui-router:

HTML:

<html ng-app="myApp">
  <head>
     <title ng-bind="title"></title>
     .....
     .....  
  </head>
</html>

App.js> bloc myApp.config

$stateProvider
    .state("home", {
        title: "My app title this will be binded in html title",
        url: "/home",
        templateUrl: "/home.html",
        controller: "homeCtrl"
    })

App.js> myApp.run

myApp.run(['$rootScope','$state', function($rootScope,$state) {
   $rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
    $rootScope.title = $state.current.title;
    console.log($state);
   });
}]);
3
the_mishra

Lorsque j'ai dû résoudre ce problème, je ne pouvais pas placer le ng-app sur la balise html de la page. Je l'ai donc résolu avec un service:

angular.module('myapp.common').factory('pageInfo', function ($document) {

    // Public API
    return {
        // Set page <title> tag. Both parameters are optional.
        setTitle: function (title, hideTextLogo) {
            var defaultTitle = "My App - and my app's cool tagline";
            var newTitle = (title ? title : defaultTitle) + (hideTextLogo ? '' : ' - My App')
            $document[0].title = newTitle;
        }
    };

});
3
Tom Söderlund

Solution personnalisée basée sur les événements inspirée de Michael Bromley

Je ne pouvais pas le faire fonctionner avec $ scope, alors j'ai essayé avec rootScope, peut-être un peu plus sale ... (surtout si vous effectuez une actualisation sur la page qui n'enregistre pas l'événement)

Mais j'aime beaucoup l'idée de la façon dont les choses sont couplées sans serrer.

J'utilise angularjs 1.6.9

index.run.js

angular
.module('myApp')
.run(runBlock);

function runBlock($rootScope, ...)
{
  $rootScope.$on('title-updated', function(event, newTitle) {
    $rootScope.pageTitle = 'MyApp | ' + newTitle;
  });
}

anyController.controller.js

angular
.module('myApp')
.controller('MainController', MainController);

function MainController($rootScope, ...)
{
  //simple way :
  $rootScope.$emit('title-updated', 'my new title');

  // with data from rest call
  TroncQueteurResource.get({id:tronc_queteur_id}).$promise.then(function(tronc_queteur){
  vm.current.tronc_queteur = tronc_queteur;

  $rootScope.$emit('title-updated', moment().format('YYYY-MM-DD') + ' - Tronc '+vm.current.tronc_queteur.id+' - ' +
                                             vm.current.tronc_queteur.point_quete.name + ' - '+
                                             vm.current.tronc_queteur.queteur.first_name +' '+vm.current.tronc_queteur.queteur.last_name
  );
 });

 ....}

index.html

<!doctype html>
<html ng-app="myApp">
  <head>
    <meta charset="utf-8">
    <title ng-bind="pageTitle">My App</title>

Ça marche pour moi :)


2
Thomas

Bien que d'autres aient de meilleures méthodes, j'ai été en mesure d'utiliser $ rootScope dans mes contrôleurs, car chacun de mes vues/modèles possède un contrôleur distinct. Vous devrez injecter $ rootScope dans chaque contrôleur. Bien que cela puisse ne pas être idéal, cela fonctionne pour moi, alors j'ai pensé que je devrais le transmettre. Si vous inspectez la page, il ajoute la liaison ng à la balise de titre.

Exemple de contrôleur:

myapp.controller('loginPage', ['$scope', '$rootScope', function ($scope, $rootScope) {

// Dynamic Page Title and Description
$rootScope.pageTitle = 'Login to Vote';
$rootScope.pageDescription = 'This page requires you to login';
}]);

Exemple d'en-tête Index.html:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="description" content="{{pageDescription}}">
<meta name="author" content="">
<link rel="shortcut icon" href="../../assets/ico/favicon.ico">
<base href="/">
<title>{{pageTitle}}</title>

Vous pouvez également définir les propriétés pageTitle et pageDescription sur des valeurs dynamiques, telles que le renvoi de données à partir d'un appel REST:

    $scope.article = restCallSingleArticle.get({ articleID: $routeParams.articleID }, function() {
    // Dynamic Page Title and Description
    $rootScope.pageTitle = $scope.article.articletitle;
    $rootScope.pageDescription = $scope.article.articledescription;
});

Encore une fois, d’autres auront peut-être de meilleures idées sur la façon d’aborder cela, mais comme je me sers d’un pré-rendu, mes besoins sont satisfaits.

1
Kode

Mr Hash avait la meilleure réponse jusqu'à présent, mais la solution ci-dessous le rend idéal (pour moi) en ajoutant les avantages suivants:

  • N'ajoute aucune montre, ce qui peut ralentir les choses
  • Automatise en fait ce que j’aurais pu faire dans le contrôleur, mais
  • Me donne toujours accès au contrôleur si je le veux toujours.
  • Pas d'injection supplémentaire

Dans le routeur:

  .when '/proposals',
    title: 'Proposals',
    templateUrl: 'proposals/index.html'
    controller: 'ProposalListCtrl'
    resolve:
      pageTitle: [ '$rootScope', '$route', ($rootScope, $route) ->
        $rootScope.page.setTitle($route.current.params.filter + ' ' + $route.current.title)
      ]

Dans le bloc d'exécution:

.run(['$rootScope', ($rootScope) ->
  $rootScope.page =
    prefix: ''
    body: ' | ' + 'Online Group Consensus Tool'
    brand: ' | ' + 'Spokenvote'
    setTitle: (prefix, body) ->
      @prefix = if prefix then ' ' + prefix.charAt(0).toUpperCase() + prefix.substring(1) else @prifix
      @body = if body then ' | ' + body.charAt(0).toUpperCase() + body.substring(1) else @body
      @title = @prefix + @body + @brand
])
0
Kim Miller

Merci à tosh shimayama pour sa solution.
Je pensais que ce n'était pas si propre de mettre un service directement dans le $scope, alors voici ma légère variation à ce sujet: http://plnkr.co/edit/QJbuZZnZEDOBcYrJXWWs =

Le contrôleur (dans la réponse originale me semblait un peu trop stupide) crée un objet ActionBar, et celui-ci est bourré dans $ scope.
L’objet est responsable de l’interrogation du service. Il masque également de l'appel $ scope pour définir l'URL du modèle, qui est à la place disponible pour les autres contrôleurs afin de définir l'URL.

0
superjos