413

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"; }
tanguy_k
  • 8,999
  • 4
  • 46
  • 50
Michael Low
  • 23,431
  • 15
  • 75
  • 115
  • This comment maybe late but I want to add. http://www.cssfacts.com/simple-dynamic-meta-tags-in-angularjs/ This can be useful for set dynamic metas. You will just change your $rootScope meta variable. – Kamuran Sönecek Apr 07 '16 at 13:46

22 Answers22

634

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

tronman
  • 8,540
  • 9
  • 41
  • 47
jkoreska
  • 7,190
  • 2
  • 16
  • 20
  • This doesn't allow for dynamic setting of the title based on $scope variables. – Eric Drechsel May 15 '13 at 22:02
  • 1
    @EricDrechsel You can set $rootScope.title from other places in your app, this logic only changes it on $routeChangeSuccess. – jkoreska May 16 '13 at 13:03
  • 11
    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}}'`. – Eric Drechsel May 20 '13 at 20:03
  • Its actually double $ in the property name : current.$$route.title maybe stackoverflow swallowed the other $ – Chris Sattinger Jun 05 '13 at 14:22
  • 10
    @felix you can access title like `current.title` also – Eldelshell Jul 18 '13 at 11:18
  • Or use the `ng-bind-template` directive and you can use the curly braces syntax. From the [docs](http://docs.angularjs.org/api/ng.directive:ngBindTemplate), it looks like this attribute was added for setting the document title. – Shrikant Sharat Sep 08 '13 at 04:13
  • 8
    $rootScope.title = current.$route.title; without doble $$ – david.sansay Sep 12 '13 at 18:58
  • 7
    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` – Tyler Forsythe Mar 18 '14 at 20:18
  • 2
    This is great, BUT, the only thing is that $$route is considered to be "private." – Chad Johnson May 23 '14 at 16:13
  • 1
    curly brackets don't show if you're using [ng-cloak](https://docs.angularjs.org/api/ng/directive/ngCloak) – blockloop Jul 18 '14 at 19:14
  • @EricDrechsel use the `resolve` option when trying to use dynamic variables. `resolve: { blog: function($route, Blog) { $route.current.$$route.title = /*etc*/ } }` – johngeorgewright Nov 28 '14 at 12:43
  • 1
    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. – David Anderson Dec 02 '14 at 02:56
  • 6
    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` ? – jbltx Dec 07 '14 at 21:02
  • The `$location` dependency isn't used/necessary. (Tested it, doesn't need it) – Richard de Wit Feb 19 '15 at 13:43
  • 1
    I like to use this method but i put a fallback in the ng-bind. something like ` ng-bind="title || 'My Cool Site' "` – James Harrington Apr 02 '15 at 18:17
  • 1
    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? – Tomino Apr 14 '15 at 07:43
  • 1
    @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! – Mark Pieszak - Trilon.io May 05 '15 at 17:39
  • Your solution resolved my issue of disappearing of title when reload page(F5). Awesome answer! – Taiwei Tuan Aug 28 '15 at 21:15
  • 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 Feb 18 '16 at 17:02
  • What works well for me: My App – Alex Boisselle Feb 18 '16 at 17:03
  • Nice solutions.... Will this be indexed in google also... as "Fetch as google" shows myApp in its "Downloaded HTTP response:" ... – Asad Ali Khan Jun 18 '16 at 13:14
  • Setting title from the controller (top level) can still be done using $scope.$parent.title = 'My Dynamic Title'; (if you're using angular routing) – Warren Sep 29 '16 at 22:01
  • if you are using stateProvider you could do this $rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) { $rootScope.title = to.title; }); – Cengkuru Michael Feb 07 '17 at 08:24
  • I would have done it like this if it was not for the language translation in my webapp – Jens Alenius Apr 26 '17 at 07:31
342

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

Tosh
  • 35,589
  • 11
  • 64
  • 54
  • Does this work in Firefox for you? I get "Error: No module: ui" in the console – Narretz Sep 20 '12 at 14:25
  • 11
    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. – superjos Mar 06 '13 at 16:31
  • 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 May 15 '13 at 22:00
  • 11
    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? – Arthur Frankel May 17 '13 at 19:25
  • I just posted another approach for when it isn't possible to have the title tag in an ngapp... http://stackoverflow.com/a/17751833/45767 – JeremyWeir Jul 19 '13 at 17:20
  • 52
    @ArthurFrankel Just use ng-bind (e.g. ng-bind="Page.title()") – Pius Uzamere Aug 14 '13 at 00:06
  • 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: https://coderwall.com/p/vcfo4q – ViniciusPires Mar 31 '14 at 15:17
  • 2
    or we can specify controller in title tag, no need for global controller on html header: {{ Page.title() }} – Dmitri Algazin May 14 '14 at 10:12
  • 1
    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. –  May 23 '14 at 09:32
  • Thank you, your example made me realize that I wasn't using services to their full potential. – RachelD Jun 02 '14 at 18:08
  • Page.title() didn't work for me, but instead i just used $scope.pageTitle = Page.title(); and used {{pageTitle}} in template. Works & thanks! – CIF Jul 03 '14 at 14:54
  • 6
    I personally prefer to set the title on the `$rootScope` instead of creating an additional controller. – DDA Jul 18 '14 at 20:40
  • How do you install $locationProvider, when I paste your exact code it errors out – Leon Gaban Oct 10 '14 at 20:54
  • 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??? – Graham Fowles Nov 12 '14 at 09:08
  • I personally think solution by Mr. Hash (here on the same question) is much better one: http://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. – mikhail-t Feb 11 '15 at 18:54
  • Title sets only 'default'. why? – Faradox Aug 07 '15 at 05:00
193

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

broc.seib
  • 19,040
  • 7
  • 56
  • 58
  • 2
    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. – Lukus Jul 30 '13 at 21:21
  • 5
    This was the only way to get it to work on Internet Explorer for me, the other methods worked on other browsers though – Maarten Aug 05 '13 at 07:35
  • 4
    As Maarten mentioned this is the only approach that works in ie7 and ie8 – rob Sep 18 '13 at 15:21
  • 33
    Amazing how people can't step back and see how easily this thing can be done without scopes and factories – redben Mar 27 '14 at 11:04
  • 7
    Unbelievable. This was far simpler than all the shenanigans others were mentioning. Thanks! – Leonard Teo May 01 '14 at 01:05
  • 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. – StackOverflowUser Dec 22 '14 at 10:37
  • 8
    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. – broc.seib Dec 22 '14 at 16:06
  • 1
    $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 :) – sidonaldson Apr 08 '15 at 13:40
  • $document[0].title should work as well. Using angular document and window wrappers is nice for latter testing, prevent jshint warnings, etc. – sgimeno Oct 19 '15 at 10:58
  • 2
    definitly the best answer ! so light weight and straight forward. – Rémi Apr 04 '16 at 17:06
  • 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 Dec 15 '16 at 22:51
  • "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... – MrBoJangles Dec 15 '16 at 22:52
  • I mean one-way binding, i.e., assign a value to a variable and it magically changes the window title. – broc.seib Dec 16 '16 at 00:59
  • Would this be possible (but most importantly, secure) to do with ngStorage? – LatentDenis Apr 26 '17 at 20:27
  • would this affect the seo in anyway ? – Etherealm Aug 17 '17 at 12:47
  • The most useful way for _existing_ apps. – sashaegorov Nov 10 '17 at 09:32
  • Exactly what I was looking for! Thanks – CBB Nov 15 '19 at 21:14
120

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>
Tisho
  • 7,474
  • 5
  • 40
  • 52
Andy Hitchman
  • 1,341
  • 1
  • 8
  • 4
43

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

Martin Atkins
  • 30,327
  • 3
  • 66
  • 78
  • 4
    Can't believe this solution hasn't been upvoted more. Most of the other ones are really bad design choices. – Martin Wawrusch Aug 17 '14 at 14:47
  • 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. – Nate Barbettini Dec 12 '14 at 16:38
  • I endorse this solution ;) – jkoreska Apr 09 '15 at 23:11
  • 3
    I wrote a bit more about angularjs-viewhead and another related idea here on my blog: http://apparently.me.uk/angularjs-view-specific-sidebars/ – Martin Atkins Jun 14 '15 at 01:00
  • 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.:

    Details for {{...}}

    Details for {{...}}

    – anre Jan 29 '16 at 15:57
  • This was most straightforward solution, the best solution from my humble point of view. – Abiel Muren Mar 14 '17 at 04:35
  • 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. – Robycool May 08 '17 at 14:43
32

Here is an adapted solution that works for me which doesn't require injection of $rootScope into controllers for setting resource specific page titles.

In the master template:

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

In the routing config:

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

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

And in the run block:

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

Finally in the controller:

function ProductController($scope) {
    //Load product or use resolve in routing
    $scope.page.setTitle($scope.product.name);
}
Mr Hash
  • 646
  • 7
  • 5
  • 1
    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. – Kristo Aun Feb 08 '15 at 18:15
  • @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 Feb 11 '15 at 18:18
  • 1
    remember to sanitize output like this: `this.title = title.replace('', '>').replace(' & ', ' & ') + ' | Site Name';` – Henrik Stenbæk Aug 06 '15 at 11:23
  • I got undefined error so I changed the last bit to: $rootScope.page.title = current.$$route ? current.$$route.title + ' | Site Name' : 'Site Name'; – Andy Jan 30 '16 at 23:27
16

jkoreska's solution is perfect if you know the titles before hand, but you may need to set the title based on data you get from a resource etc.

My solution requires a single service. Since the rootScope is the base of all DOM elements, we don't need to put a controller on the html element like someone mentioned

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')
// ...

All controllers that need to change title

app.controller('SomeController', function(Page){
    Page.setTitle("Some Title");
});
Deminetix
  • 2,266
  • 20
  • 21
  • 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 – Dmitri Algazin May 14 '14 at 10:22
  • 5
    instead `{{title}}` use `ng-bind='title'` – Faradox Aug 07 '15 at 05:21
  • 1
    Agree with @Faradox... using `ng-bind` prevents the pre-interpolated syntax from displaying before the title actually evaluates. +100 – Seth Sep 24 '15 at 14:20
11

A clean way that allow dynamically setting title or meta description. In example I use ui-router but you can use ngRoute in same way.

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>
...
Alex Soroka
  • 760
  • 9
  • 14
8

Alternatively, if you are using ui-router:

index.html

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

Routing

$stateProvider
  .state('home', {
      url: '/',
      templateUrl: 'views/home.html',
      data: {
        title: 'Welcome Home.'
      }
  }
Nathan Kot
  • 2,294
  • 1
  • 18
  • 30
  • 2
    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? –  Nov 08 '14 at 10:11
  • 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. –  Nov 08 '14 at 10:28
  • this does not work for me and 'title' is not found in the API docs - is this still supported? – GraehamF Jan 13 '16 at 23:07
7

Custom event-based solution

Here is another approach that hasn't been mentioned by the others here (as of this writing).

You can use custom events like so:

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

This approach has the advantage of not requiring extra services to be written and then injected into every controller that needs to set the title, and also doesn't (ab)use the $rootScope. It also allows you to set a dynamic title (as in the code example), which is not possible using custom data attributes on the router's config object (as far as I know at least).

Ehsan88
  • 2,816
  • 4
  • 22
  • 44
Michael Bromley
  • 4,702
  • 4
  • 32
  • 51
5

For scenarios that you don't have an ngApp that contains the title tag, just inject a service to controllers that need to set the window title.

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

Working example... http://jsfiddle.net/8m379/1/

JeremyWeir
  • 23,396
  • 10
  • 89
  • 106
5

If you don't have control over title element (like asp.net web form) here is some thing you can use

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;
        });
    });
Ashish
  • 1,111
  • 11
  • 12
4

Simple and dirty way using $rootScope:

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

In your controllers, when you have the data necessary to create the title, do:

$rootScope.title = 'Page X'
user1338062
  • 9,351
  • 3
  • 54
  • 56
4

None of these answers seemed intuitive enough, so I created a small directive to do this. This way allows you to declare the title in the page, where one would normally do it, and allows it to be dynamic as well.

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

You'll need to of course change the module name to match yours.

To use it, just throw this in your view, much as you would do for a regular <title> tag:

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

You can also just include plain text if you don't need it to by dynamic:

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

Alternatively, you can use an attribute, to make it more IE-friendly:

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

You can put whatever text you want in the tag of course, including Angular code. In this example, it will look for $scope.titleText in whichever controller the custom-title tag is currently in.

Just make sure you don't have multiple page-title tags on your page, or they'll clobber each other.

Plunker example here http://plnkr.co/edit/nK63te7BSbCxLeZ2ADHV. You'll have to download the zip and run it locally in order to see the title change.

MikeyB
  • 59
  • 3
  • 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. – z0r Jun 25 '15 at 00:59
4

Simplistic solution for angular-ui-router :

HTML :

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

App.js > myApp.config block

$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);
   });
}]);
the_mishra
  • 761
  • 8
  • 24
3

Here's a different way to do title changes. Maybe not as scalable as a factory function (which could conceivably handle unlimited pages) but it was easier for me to understand:

In my index.html I started like this:

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

Then I made a partial called "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>

Then I went back to "index.html" and added the nav.html using ng-include and the ng-view for my partials:

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

Notice that ng-cloak? It doesn't have anything to do with this answer but it hides the page until it's done loading, a nice touch :) Learn how here: Angularjs - ng-cloak/ng-show elements blink

Here's the basic module. I put it in a file called "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"});
    });

}());

If you look toward the end of the module, you'll see that I have a critter-detail page based on :id. It's a partial that is used from the Crispy Critters page. [Corny, I know - maybe it's a site that celebrates all kinds of chicken nuggets ;) Anyway, you could update the title when a user clicks on any link, so in my main Crispy Critters page that leads to the critter-detail page, that's where the $root.title update would go, just like you saw in the nav.html above:

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

Sorry so windy but I prefer a post that gives enough detail to get it up and running. Note that the example page in the AngularJS docs is out of date and shows a 0.9 version of ng-bind-template. You can see that it's not that much different.

Afterthought: you know this but it's here for anyone else; at the bottom of the index.html, one must include the app.js with the module:

        <!-- APP -->
        <script type="text/javascript" src="js/app.js"></script>
    </body>
</html>
Community
  • 1
  • 1
noogrub
  • 862
  • 2
  • 12
  • 20
  • 2
    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.. – amit bakle Jul 22 '14 at 10:20
  • 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. – Mark Amery Jan 27 '15 at 17:15
3

When I had to solve this, I couldn't place the ng-app on the page's html tag, so I solved it with a 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;
        }
    };

});
Tom Söderlund
  • 3,972
  • 3
  • 34
  • 53
2

Custom Event Based solution inspired from Michael Bromley

I wasn't able to make it work with $scope, so I tried with rootScope, maybe a bit more dirty... (especially if you make a refresh on the page that do not register the event)

But I really like the idea of how things are loosely coupled.

I'm using 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>

It's working for me :)


Thomas
  • 813
  • 6
  • 20
1

While others may have better methods, I was able to use $rootScope in my controllers, as each of my views/templates has a distinct controller. You will need to inject the $rootScope in each controller. While this may not be ideal, it is functioning for me, so I thought I should pass it along. If you inspect the page, it adds the ng-binding to the title tag.

Example Controller:

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

Example Index.html header:

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

You can also set the pageTitle and pageDescription to dynamic values, such as returning data from a REST call:

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

Again, others may have better ideas on how to approach this, but since I am using a pre-rendering, my needs are being met.

Kode
  • 2,729
  • 15
  • 58
  • 120
0

Thanks to tosh shimayama for his solution.
I thought it was not so clean to put a service straight into the $scope, so here's my slight variation on that: http://plnkr.co/edit/QJbuZZnZEDOBcYrJXWWs

The controller (that in original answer seemed to me a little bit too dumb) creates an ActionBar object, and this one is stuffed into $scope.
The object is responsible for actually querying the service. It also hides from the $scope the call to set the template URL, which instead is available to other controllers to set the URL.

Community
  • 1
  • 1
superjos
  • 10,834
  • 4
  • 79
  • 120
0

Mr Hash had the best answer so far, but the solution below makes it ideal (for me) by adding the following benefits:

  • Adds no watches, which can slow things down
  • Actually automates what I might have done in the controller, yet
  • Still gives me access from the controller if I still want it.
  • No extra injecting

In the router:

  .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)
      ]

In the run block:

.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
])
Community
  • 1
  • 1
Kim Miller
  • 851
  • 7
  • 10
-4

The better and dynamic solution I have found is to use $watch to trace the variable changes and then update the title.

geolyth
  • 89
  • 2
  • 6