How to dynamically change header based on AngularJS partial view?

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

Solution 1:

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

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