Как динамически изменять заголовок на основе частичного просмотра AngularJS?

Я использую ng-view для включения частичных представлений AngularJS, и я хочу обновить теги заголовка страницы и h1 на основе включенного представления. Однако они не входят в объем контроллеров частичных представлений, поэтому я не могу понять, как связать их с данными, установленными в контроллерах.

Если бы это был ASP.NET MVC, вы могли бы использовать @ViewBag для этого, но я не знаю эквивалента в AngularJS. Я искал об общих службах, событиях и т.д., Но до сих пор не могу заставить его работать. Любой способ изменить мой пример, чтобы он работал, был бы очень признателен.

Мой 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>

Мой 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"; }

Ответ 1

Вы можете определить контроллер на уровне <html>.

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

Вы создаете сервис: Page и изменяете его с контроллеров.

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

Вставить Page и вызвать 'Page.setTitle()' из контроллеров.

Вот конкретный пример: http://plnkr.co/edit/0e7T6l

Ответ 2

Я только что открыл хороший способ задать заголовок страницы, если вы используете маршрутизацию:

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

Изменить: используя атрибут ng-bind вместо curlies {{}}, чтобы они не отображались при загрузке

Ответ 3

Обратите внимание, что вы также можете установить заголовок непосредственно с помощью javascript, т.е.

$window.document.title = someTitleYouCreated;

У этого нет привязки данных, но достаточно, если помечать ng-app в теге <html> проблематично. (Например, используя JSP-шаблоны, где <head> определяется точно в одном месте, но у вас есть несколько приложений.)

Ответ 4

Объявление ng-app элемента html предоставляет корневую область для head и body.

Поэтому в вашем контроллере введите $rootScope и установите для него свойство заголовка:

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

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

и на вашей странице:

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

Ответ 5

В модуле angularjs-viewhead показан механизм установки заголовка для каждого представления с использованием только настраиваемой директивы.

Он может быть применен к существующему элементу представления, содержимое которого уже является заголовком вида:

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

... или он может использоваться как отдельный элемент, и в этом случае элемент будет невидим в визуализированном документе и будет использоваться только для установки заголовка вида:

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

Содержимое этой директивы доступно в корневой области как viewTitle, поэтому ее можно использовать в элементе title так же, как и любую другую переменную:

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

Он также может использоваться в любом другом месте, которое может "видеть" область корня. Например:

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

Это решение позволяет задавать заголовок с помощью того же механизма, который используется для управления остальной частью презентации: шаблоны AngularJS. Это позволяет избежать необходимости загромождать контроллеры этой презентационной логикой. Контроллер должен сделать доступными любые данные, которые будут использоваться для информирования заголовка, но шаблон делает окончательное определение того, как его представить, и может использовать экспрессию-интерполяцию и фильтры для привязки к данным области как обычно.

(Отказ от ответственности: я являюсь автором этого модуля, но я ссылаюсь на него здесь только в надежде, что он поможет кому-то решить эту проблему.)

Ответ 6

Вот адаптированное решение, которое работает для меня, что не требует вставки $rootScope в контроллеры для установки титульных заголовков ресурсов.

В шаблоне мастера:

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

В конфигурации маршрутизации:

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

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

И в блоке выполнения:

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

Наконец, в контроллере:

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

Ответ 7

Решение

jkoreska идеально подходит, если вы знаете заголовки перед началом работы, но вам может потребоваться установить заголовок на основе данных, которые вы получаете от ресурса и т.д.

Мое решение требует отдельной службы. Поскольку rootScope является базой всех элементов DOM, нам не нужно помещать контроллер в элемент html, как упоминалось выше,

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

Все контроллеры, которым необходимо изменить заголовок

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

Ответ 8

Чистый способ, позволяющий динамически устанавливать заголовок или мета-описание. В примере я использую ui-router, но вы можете использовать ngRoute таким же образом.

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

Ответ 9

В качестве альтернативы, если вы используете 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.'
      }
  }

Ответ 10

Если у вас нет элемента управления (например, веб-формы asp.net), вы можете использовать

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

Ответ 11

Пользовательское решение на основе событий

Вот еще один подход, который не упоминался другими здесь (на момент написания).

Вы можете использовать пользовательские события, например:

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

Этот подход имеет то преимущество, что не требует, чтобы дополнительные службы записывались, а затем вводились в каждый контроллер, который должен установить заголовок, а также не использует (ab) $rootScope. Он также позволяет вам устанавливать динамический заголовок (как в примере кода), что невозможно с использованием пользовательских атрибутов данных в объекте конфигурации маршрутизатора (насколько я знаю, по крайней мере).

Ответ 12

Ни один из этих ответов не казался достаточно интуитивным, поэтому я создал небольшую директиву для этого. Этот способ позволяет объявить заголовок на странице, где обычно это делается, и также позволяет ему быть динамичным.

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

Вам нужно, конечно, изменить имя модуля, чтобы оно соответствовало вашим.

Чтобы использовать его, просто бросьте это в своем представлении, как и для обычного тега <title>:

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

Вы также можете просто добавить простой текст, если он вам не нужен:

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

В качестве альтернативы вы можете использовать атрибут, чтобы сделать его более удобным для IE:

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

Вы можете поместить любой текст в тег, в том числе Angular. В этом примере он будет искать $scope.titleText в любом контроллере, в котором находится тег пользовательского заголовка.

Просто убедитесь, что на вашей странице нет нескольких тегов на странице, или они будут сжиматься друг с другом.

Пример плунжера здесь http://plnkr.co/edit/nK63te7BSbCxLeZ2ADHV. Вам нужно будет загрузить zip и запустить его локально, чтобы увидеть изменение названия.

Ответ 13

Здесь другой способ изменения названия. Возможно, это не так масштабируемо, как функция factory (которая могла бы обрабатывать неограниченные страницы), но мне было легче понять:

В моем index.html я начал вот так:

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

Затем я сделал частичное имя "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>

Затем я вернулся к "index.html" и добавил nav.html, используя ng-include и ng-view для моих партикулов:

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

Обратите внимание, что ng-cloak? Это не имеет никакого отношения к этому ответу, но он скрывает страницу до тех пор, пока она не загрузится, приятное прикосновение:) Узнайте, как здесь: Элементы Angularjs - ng-cloak/ng-show мигает

Вот базовый модуль. Я положил его в файл под названием "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"});
    });

}());

Если вы посмотрите на конец модуля, вы увидите, что у меня есть страница подробных сведений о критериях: id. Это часть, которая используется на странице Crispy Critters. [Corny, я знаю, может быть, это сайт, который празднует все виды куриных самородков;) Во всяком случае, вы можете обновить заголовок, когда пользователь нажимает на любую ссылку, поэтому на моей главной странице Crispy Critters, которая приводит к странице подробных сведений, что, когда будет обновлено обновление $root.title, так же, как вы видели в nav.html выше:

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

Извините, так ветрено, но я предпочитаю сообщение, которое дает достаточно деталей, чтобы его запустить и запустить. Обратите внимание, что страница примера в документах AngularJS устарела и показывает версию ng-bind-шаблона 0.9. Вы можете видеть, что это не так уж и много.

Последующая мысль: вы знаете это, но здесь для кого-то еще; в нижней части index.html, необходимо добавить app.js с модулем:

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

Ответ 14

В сценариях, в которых у вас нет ngApp, который содержит тег 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;
        }
    };
});

Рабочий пример... http://jsfiddle.net/8m379/1/

Ответ 15

Простой и грязный способ с использованием $rootScope:

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

В ваших контроллерах, когда у вас есть данные, необходимые для создания заголовка, выполните:

$rootScope.title = 'Page X'

Ответ 16

Когда мне пришлось это решить, я не мог поместить ng-app на тег html страницы, поэтому я решил его с помощью службы:

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 cool tagline";
            var newTitle = (title ? title : defaultTitle) + (hideTextLogo ? '' : ' - My App')
            $document[0].title = newTitle;
        }
    };

});

Ответ 17

В то время как другие могут иметь лучшие методы, мне удалось использовать $rootScope в моих контроллерах, так как каждый из моих представлений/шаблонов имеет отдельный контроллер. Вам нужно будет ввести $rootScope в каждый контроллер. Хотя это может быть не идеальным, оно работает для меня, поэтому я решил передать его. Если вы просматриваете страницу, она добавляет ng-привязку к тегу title.

Пример контроллера:

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

Пример заголовка 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>

Вы также можете установить для параметра pageTitle и pageDescription динамические значения, например, вернуть данные из вызова REST:

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

Опять же, у других могут быть лучшие идеи о том, как подходить к этому, но поскольку я использую предварительный рендеринг, мои потребности удовлетворяются.

Ответ 18

Благодаря tosh shimayama для его решения.
Я думал, что это не так чисто, чтобы поместить услугу прямо в $scope, поэтому здесь мои незначительные вариации: http://plnkr.co/edit/QJbuZZnZEDOBcYrJXWWs

Контроллер (который в оригинальном ответе казался мне слишком тупым) создает объект ActionBar, и этот файл заполняется в $scope.
Объект отвечает за фактический запрос службы. Он также скрывает от $scope вызов для установки URL-адреса шаблона, который вместо этого доступен другим контроллерам для установки URL-адреса.

Ответ 19

У г-на Хэша был лучший ответ, но нижеприведенное решение делает его идеальным (для меня), добавляя следующие преимущества:

  • Добавляет часы, которые могут замедлить работу.
  • На самом деле автоматизирует то, что я мог бы сделать в контроллере, но
  • Все еще дает мне доступ с контроллера, если я все еще хочу его.
  • Нет дополнительных инъекций

В маршрутизаторе:

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

В блоке запуска:

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

Ответ 20

Упрощенное решение для angular -ui-router:

HTML:

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

App.js > блок 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);
   });
}]);

Ответ 21

Лучшее и динамичное решение, которое я нашел, - это использовать $watch для отслеживания изменений переменных и обновления заголовка.