How to dynamically change header based on AngularJS partial view?

268,087

Solution 1

You could define controller at the <html> level.

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

You create service: Page and modify from controllers.

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

Inject Page and Call 'Page.setTitle()' from controllers.

Here is the concrete example: http://plnkr.co/edit/0e7T6l

Solution 2

I just discovered a nice way to set your page title if you're using routing:

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: using the ng-bind attribute instead of curlies {{}} so they don't show on load

Solution 3

Note that you can also set the title directly with javascript, i.e.,

$window.document.title = someTitleYouCreated;

This does not have data binding, but it suffices when putting ng-app in the <html> tag is problematic. (For example, using JSP templates where <head> is defined in exactly one place, yet you have more than one app.)

Solution 4

Declaring ng-app on the html element provides root scope for both the head and body.

Therefore in your controller inject $rootScope and set a header property on this:

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

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

and in your page:

<title ng-bind="header"></title>

Solution 5

The module angularjs-viewhead shows a mechanism to set the title on a per-view basis using only a custom directive.

It can either be applied to an existing view element whose content is already the view title:

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

...or it can be used as a standalone element, in which case the element will be invisible in the rendered document and will only be used to set the view title:

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

The content of this directive is made available in the root scope as viewTitle, so it can be used on the title element just like any other variable:

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

It can also be used in any other spot that can "see" the root scope. For example:

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

This solution allows the title to be set via the same mechanism that is used to control the rest of the presentation: AngularJS templates. This avoids the need to clutter controllers with this presentational logic. The controller needs to make available any data that will be used to inform the title, but the template makes the final determination on how to present it, and can use expression interpolation and filters to bind to scope data as normal.

(Disclaimer: I am the author of this module, but I'm referencing it here only in the hope that it will help someone else to solve this problem.)

Share:
268,087
Michael Low
Author by

Michael Low

Updated on May 05, 2020

Comments

  • Michael Low
    Michael Low almost 4 years

    I am using ng-view to include AngularJS partial views, and I want to update the page title and h1 header tags based on the included view. These are out of scope of the partial view controllers though, and so I can't figure out how to bind them to data set in the controllers.

    If it was ASP.NET MVC you could use @ViewBag to do this, but I don't know the equivalent in AngularJS. I've searched about shared services, events etc but still can't get it working. Any way to modify my example so it works would be much appreciated.

    My 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>
    

    My 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"; }
    
  • Narretz
    Narretz over 11 years
    Does this work in Firefox for you? I get "Error: No module: ui" in the console
  • superjos
    superjos about 11 years
    uhmm... I'm not sure if placing a service straight into the $scope is considered nice in the AngularJS architecture. Maybe it could be better to put in $scope a Controller function, and then let this function query the service.
  • Eric Drechsel
    Eric Drechsel almost 11 years
    this solution is full of win! it clarifies for me how to manage state at the page level while using ng-view and ngRouter. Thanks!
  • Eric Drechsel
    Eric Drechsel almost 11 years
    This doesn't allow for dynamic setting of the title based on $scope variables.
  • jkoreska
    jkoreska almost 11 years
    @EricDrechsel You can set $rootScope.title from other places in your app, this logic only changes it on $routeChangeSuccess.
  • Arthur Frankel
    Arthur Frankel almost 11 years
    This example was terrific. I have one followup though, on initial load you can see the {{ Page.title() }} text in the title (very quickly). I don't think you can use ng-cloak since it's not in the body. Any suggestions to avoid this?
  • Eric Drechsel
    Eric Drechsel almost 11 years
    right, but your example doesn't show how to change the title on $routeChangeSuccess parameterized by $scope variables, which @tosh's example using a Page service does. So you can set title = "Blog" but not title = '{{"Blog post " + post.title}}'.
  • Chris Sattinger
    Chris Sattinger almost 11 years
    Its actually double $ in the property name : current.$$route.title maybe stackoverflow swallowed the other $
  • Eldelshell
    Eldelshell almost 11 years
    @felix you can access title like current.title also
  • JeremyWeir
    JeremyWeir almost 11 years
    I just posted another approach for when it isn't possible to have the title tag in an ngapp... stackoverflow.com/a/17751833/45767
  • Lukus
    Lukus almost 11 years
    Nice direct approach that reminds you of the obvious. I simple wrapped this in my 'ui' service as setTitle() function, injected the $window dependency, and since my service is in all my controllers, now can call ui.setTitle('example title') wherever I need to in any controller.
  • Maarten
    Maarten over 10 years
    This was the only way to get it to work on Internet Explorer for me, the other methods worked on other browsers though
  • Pius Uzamere
    Pius Uzamere over 10 years
    @ArthurFrankel Just use ng-bind (e.g. ng-bind="Page.title()")
  • sharat87
    sharat87 over 10 years
    Or use the ng-bind-template directive and you can use the curly braces syntax. From the docs, it looks like this attribute was added for setting the document title.
  • david.sansay
    david.sansay over 10 years
    $rootScope.title = current.$route.title; without doble $$
  • rob
    rob over 10 years
    As Maarten mentioned this is the only approach that works in ie7 and ie8
  • Tyler Forsythe
    Tyler Forsythe about 10 years
    I just upgraded my Angular version several versions (1.0.5 to 1.2.7) and this broke me for in my code. I was using current.$route in the old code and it was working. With the upgrade, the double $ on route is needed. current.$$route
  • redben
    redben about 10 years
    Amazing how people can't step back and see how easily this thing can be done without scopes and factories
  • ViniciusPires
    ViniciusPires about 10 years
    Useful and upvoted. The only problem is when you have up to 20 or more controllers, the code doesn't scale well, and holds tight the responsibility of changing the title to the controllers. See this example for a more scalable solution: coderwall.com/p/vcfo4q
  • Leonard Teo
    Leonard Teo about 10 years
    Unbelievable. This was far simpler than all the shenanigans others were mentioning. Thanks!
  • Dmitri Algazin
    Dmitri Algazin almost 10 years
    or we can specify controller in title tag, no need for global controller on html header: <title ng-controller="titleCtrl">{{ Page.title() }}</title>
  • Dmitri Algazin
    Dmitri Algazin almost 10 years
    small problem, when you refresh a page, in your tab name you see '{{ title }}' and after page was rendered, you see 'Some Title' only. solution with factory does not have that behavior
  • Admin
    Admin almost 10 years
    I must admit, although I up-voted this answer, using the ng-bind-template={{pageTitle}} attribute on the title element is much cleaner. It prevents the browser title showing "{{pageTitle}}" until the app has loaded.
  • Chad Johnson
    Chad Johnson almost 10 years
    This is great, BUT, the only thing is that $$route is considered to be "private."
  • Nicolas Janel
    Nicolas Janel almost 10 years
    best answer on my opinion. To have a controller on ng-app level as described in accepted answer is useless in this case.
  • RachelD
    RachelD almost 10 years
    Thank you, your example made me realize that I wasn't using services to their full potential.
  • CIF
    CIF almost 10 years
    Page.title() didn't work for me, but instead i just used $scope.pageTitle = Page.title(); and used {{pageTitle}} in template. Works & thanks!
  • blockloop
    blockloop almost 10 years
    curly brackets don't show if you're using ng-cloak
  • DDA
    DDA almost 10 years
    I personally prefer to set the title on the $rootScope instead of creating an additional controller.
  • amit bakle
    amit bakle almost 10 years
    My opinion, don't use this. You are mixing data(information) in views(presentation). Later on it will be very difficult to find title sources scattered all over your HTML links that may be present at various places in the view..
  • Martin Wawrusch
    Martin Wawrusch over 9 years
    Can't believe this solution hasn't been upvoted more. Most of the other ones are really bad design choices.
  • tedwards947
    tedwards947 over 9 years
    i love how lightweight this solution is, and it avoids using $$ properties
  • Leon Gaban
    Leon Gaban over 9 years
    How do you install $locationProvider, when I paste your exact code it errors out
  • Admin
    Admin over 9 years
    I can't get this to work.. I've got ui-router updating URL and content based on my state and I get no errors or warnings, but I can't seem to access any part of the state config object through $state.current.[...]. What version of ui-router did you use to do this?
  • Admin
    Admin over 9 years
    My "Runtime Config" edit to the answer solves the problem I mentioned in my comment above. :) I'm open to ideas if there's a better way to do this though.
  • special0ne
    special0ne over 9 years
    The accepted answer adds unnecessary complication and risks. This version makes it as simple as setting a variable.
  • Graham Fowles
    Graham Fowles over 9 years
    I implemented this solution for title and description and it works fine except when i check my page on woorank it shows as title missing and description is {{Page.getDescription()}}. Does this mean that search engines will also get the same???
  • Natus Drew
    Natus Drew over 9 years
    This answer worked the best. The most elegant solution.
  • johngeorgewright
    johngeorgewright over 9 years
    @EricDrechsel use the resolve option when trying to use dynamic variables. resolve: { blog: function($route, Blog) { $route.current.$$route.title = /*etc*/ } }
  • David Anderson
    David Anderson over 9 years
    Anyone having troubles getting this to work in Angular 1.3.4? The event fires, the values are correct, but the binding never updates the text. In my case, I have the binding on a span tag.
  • Michael J. Calkins
    Michael J. Calkins over 9 years
    If you're dead set on using $rootScope I'd at least extract this out to a service so you don't have $rootScope in your controller.
  • jbltx
    jbltx over 9 years
    In the answser when can see '/Product/:id'. Is there any way to have the :id value with this method ? I tried title: function(params){return params.id;} but doesn't work... Maybe using resolve ?
  • remarsh
    remarsh over 9 years
    I want to use this solution but I am curious what the advantages of using it are over document.title = "App";
  • Nate Barbettini
    Nate Barbettini over 9 years
    Agreed, this should be the top solution. I like this a lot better than declaring a controller at the page level for setting the title. FYI: using this with Angular v1.3.2 and angular-route-segment v1.3.3 and it's working like a charm.
  • StackOverflowUser
    StackOverflowUser over 9 years
    I was able to do this successfully from the controller after retrieving data from a web api call, and it was the only technique that worked for me - but it only worked when I did not begin the line with the dollar sign. I had to use window.document.title rather than $window.document.title.
  • broc.seib
    broc.seib over 9 years
    Using plain 'window' is fine -- that's directly acting on the DOM. '$window' is an angular thing, and you have to inject it to use it. Either way will work.
  • Mark Amery
    Mark Amery over 9 years
    Because the title only gets updated upon actually clicking a link, this doesn't set the title correctly when the user first lands on a page, or when the user refreshes.
  • Kristo Aun
    Kristo Aun about 9 years
    The title set in ProductController ($scope.page.setTitle) is being overridden by $rootScope.$on('$routeChangeSuccess'. Setting a default title in $rootScope.$on('$routeChangeStart' is safer in this respect.
  • mikhail-t
    mikhail-t about 9 years
    @mr-hash: here is small adjustment I suggest, perfect for existing angular projects with many routes, but without titles. It'll generate title from controller's name, if no title is defined on the route: $rootScope.page.setTitle(current.$$route.title || current.$$route.controller.replace('Ctrl', ''));
  • mikhail-t
    mikhail-t about 9 years
    I personally think solution by Mr. Hash (here on the same question) is much better one: stackoverflow.com/a/17898250/448816 It allows for page title to be generated dynamically based on route and insert custom title from controller if needed.
  • Richard de Wit
    Richard de Wit about 9 years
    The $location dependency isn't used/necessary. (Tested it, doesn't need it)
  • Rober
    Rober about 9 years
    Does your solution work in this scenario? stackoverflow.com/questions/28760904/…
  • TechCrunch
    TechCrunch about 9 years
    Good enough for a small application with handful partial views.
  • James Harrington
    James Harrington about 9 years
    I like to use this method but i put a fallback in the ng-bind. something like ` ng-bind="title || 'My Cool Site' "`
  • sidonaldson
    sidonaldson about 9 years
    $window is only a wrapper so you can integrate and manipulate it for automated testing purposed. If you just want to set something do it directly :)
  • jkoreska
    jkoreska about 9 years
    I endorse this solution ;)
  • Tomino
    Tomino about 9 years
    This solution works great... until you use .otherwise({redirectTo: '/'});. In this case title is empty and title value from '/' route is not used. Does anybody knows solution for this case?
  • Mark Pieszak - Trilon.io
    Mark Pieszak - Trilon.io almost 9 years
    @Tomino Just do this within your .run code $rootScope.title = current.$$route !== undefined ? current.$$route.title : 'your home page title here'; That'll make sure there even is a $$route before applying it. Fixes the issue!
  • Martin Atkins
    Martin Atkins almost 9 years
    I wrote a bit more about angularjs-viewhead and another related idea here on my blog: apparently.me.uk/angularjs-view-specific-sidebars
  • z0r
    z0r almost 9 years
    I came up with something similar. By far the most intuitive to use, and doesn't require putting a controller on html. In my directive I also inject an optional pageTitlePrefix constant.
  • Henrik Stenbæk
    Henrik Stenbæk over 8 years
    remember to sanitize output like this: this.title = title.replace('<', '&lt;').replace('>', '&gt;').replace(' & ', ' &amp; ') + ' | Site Name';
  • Faradox
    Faradox over 8 years
    Title sets only 'default'. why?
  • Faradox
    Faradox over 8 years
    instead {{title}} use ng-bind='title'
  • Taiwei Tuan
    Taiwei Tuan over 8 years
    Your solution resolved my issue of disappearing of title when reload page(F5). Awesome answer!
  • Seth
    Seth over 8 years
    Agree with @Faradox... using ng-bind prevents the pre-interpolated syntax from displaying before the title actually evaluates. +100
  • sgimeno
    sgimeno over 8 years
    $document[0].title should work as well. Using angular document and window wrappers is nice for latter testing, prevent jshint warnings, etc.
  • GraehamF
    GraehamF over 8 years
    this does not work for me and 'title' is not found in the API docs - is this still supported?
  • anre
    anre over 8 years
    If reusing the same view at top-level and at sub-level view, one can still use view-title with a ng-if, e.g.: <h4 ng-if="$state.includes('some-state')" view-title>Details for {{...}}</h4> <h4 ng-if="!$state.includes('some-state')">Details for {{...}}</h4>
  • Andy
    Andy over 8 years
    I got undefined error so I changed the last bit to: $rootScope.page.title = current.$$route ? current.$$route.title + ' | Site Name' : 'Site Name';
  • Eolis
    Eolis about 8 years
    the answer I ended up using; though I use a single factory to match all the urls and set the title/description as to not require $rootScope in all controllers
  • Alex Boisselle
    Alex Boisselle about 8 years
    This is absolutely the best way to do it. If your app takes any time to load at all, the other solution will show the pre-parsed {{ var }} in the tab before angular runs. Therefore using ng-bind is the best solution. Nicely done.
  • Alex Boisselle
    Alex Boisselle about 8 years
    What works well for me: <title ng-bind="'My App | ' + LANG['MENU_' + $state.current.name.replace('dashboard.', '').toUpperCase()]">My App</title>
  • 0xtuytuy
    0xtuytuy about 8 years
    definitly the best answer ! so light weight and straight forward.
  • Asad Ali Khan
    Asad Ali Khan almost 8 years
    Nice solutions.... Will this be indexed in google also... as "Fetch as google" shows <title ng-bind="'myApp &mdash; ' + title">myApp</title> in its "Downloaded HTTP response:" ...
  • Warren
    Warren over 7 years
    Setting title from the controller (top level) can still be done using $scope.$parent.title = 'My Dynamic Title'; (if you're using angular routing)
  • MrBoJangles
    MrBoJangles over 7 years
    I had to set the title inside my controller with a category name. I just set the title after I had the value, one line of code, done.
  • MrBoJangles
    MrBoJangles over 7 years
    "This does not have binding,..." I'd be curious as to the scenario where title binding would need to be two-way. I'm sure there is a scenario, I just don't know what it would be. Perhaps candybox 3...
  • broc.seib
    broc.seib over 7 years
    I mean one-way binding, i.e., assign a value to a variable and it magically changes the window title.
  • Cengkuru Michael
    Cengkuru Michael about 7 years
    if you are using stateProvider you could do this $rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) { $rootScope.title = to.title; });
  • Abiel Muren
    Abiel Muren about 7 years
    This was most straightforward solution, the best solution from my humble point of view.
  • Jens Alenius
    Jens Alenius about 7 years
    I would have done it like this if it was not for the language translation in my webapp
  • LatentDenis
    LatentDenis about 7 years
    Would this be possible (but most importantly, secure) to do with ngStorage?
  • Robycool
    Robycool almost 7 years
    So clean and elegant ! The only solution where you can have thousands of different titles that will automatically sync when changing languages, users, etc. without needing to bother about it.
  • Etherealm
    Etherealm over 6 years
    would this affect the seo in anyway ?
  • sashaegorov
    sashaegorov over 6 years
    The most useful way for existing apps.
  • Saurabh Solanki
    Saurabh Solanki over 5 years
    one of the simpleset way
  • Admin
    Admin over 4 years
    Exactly what I was looking for! Thanks