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

409

Я использую 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"; }
  • 0
    Этот комментарий может быть поздно, но я хочу добавить. cssfacts.com/simple-dynamic-meta-tags-in-angularjs Это может быть полезно для установки динамических метас. Вы просто измените мета-переменную $ rootScope.
Теги:
templates
angular-routing
partial-views

21 ответ

304
Лучший ответ

Вы можете определить контроллер на уровне <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

  • 0
    Это работает в Firefox для вас? Я получаю «Ошибка: Нет модуля: UI» в консоли
  • 10
    хм ... я не уверен, что размещение сервиса прямо в $ scope считается хорошим в архитектуре AngularJS. Возможно, было бы лучше поместить в $ scope функцию Controller, а затем позволить этой функции запрашивать службу.
Показать ещё 14 комментариев
645

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

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
    Это очень хорошее решение, чистое простое.
  • 57
    +1 за напоминание об использовании ng-bind
Показать ещё 24 комментария
161

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

$window.document.title = someTitleYouCreated;

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

  • 2
    Хороший прямой подход, который напоминает вам об очевидном. Я просто обернул это в своем сервисе 'ui' как функцию setTitle (), внедрил зависимость $ window, и так как мой сервис находится во всех моих контроллерах, теперь он может вызывать ui.setTitle ('title title') везде, где мне нужно в любом контроллер.
  • 5
    Это был единственный способ заставить его работать в Internet Explorer для меня, другие методы работали в других браузерах, хотя
Показать ещё 16 комментариев
112

Объявление 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>
  • 8
    лучший ответ на мой взгляд. В этом случае бесполезно иметь контроллер на уровне ng-приложения, как описано в принятом ответе.
  • 1
    Мне нравится, насколько легким является это решение, и оно избегает использования свойств $$.
Показать ещё 8 комментариев
40

В модуле 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. Это позволяет избежать необходимости загромождать контроллеры этой презентационной логикой. Контроллер должен сделать доступными любые данные, которые будут использоваться для информирования заголовка, но шаблон делает окончательное определение того, как его представить, и может использовать экспрессию-интерполяцию и фильтры для привязки к данным области как обычно.

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

  • 4
    Не могу поверить, что за это решение больше не проголосовали. Большинство других действительно плохих дизайнерских решений.
  • 0
    Согласитесь, это должно быть лучшим решением. Мне это нравится намного лучше, чем объявление контроллера на уровне страницы для установки заголовка. К вашему сведению: используйте это с Angular v1.3.2 и angular-route-сегмента v1.3.3, и это работает как шарм.
Показать ещё 5 комментариев
30

Вот адаптированное решение, которое работает для меня, что не требует вставки $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);
}
  • 1
    Набор заголовков в ProductController ($ scope.page.setTitle) переопределяется $ rootScope. $ On ('$ routeChangeSuccess'. Установка заголовка по умолчанию в $ rootScope. $ On ('$ routeChangeStart' безопаснее в этом отношении).
  • 0
    лучший ответ действительно, работает как шарм!
Показать ещё 3 комментария
15
Решение

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");
});
  • 0
    небольшая проблема: когда вы обновляете страницу, в имени вкладки вы видите «{{title}}», а после рендеринга страницы вы видите только «Some Title». Решение с завода не имеет такого поведения
  • 5
    вместо этого {{title}} используйте ng-bind='title'
Показать ещё 1 комментарий
10

Чистый способ, позволяющий динамически устанавливать заголовок или мета-описание. В примере я использую 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.'
      }
  }
  • 2
    Я не могу получить эту работу .. Я получил ui-router обновления URL и содержания на основе моего состояния , и я не получаю никаких ошибок или предупреждений, но я не могу показаться , чтобы получить доступ к любой части объекта состояния конфигурации через $state.current.[...] . Какую версию ui-router вы использовали для этого?
  • 0
    Мое редактирование «Runtime Config» для ответа решает проблему, о которой я упоминал в своем комментарии выше. :) Я открыт для идей, если есть лучший способ сделать это все же.
Показать ещё 2 комментария
5

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

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

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

// 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. Он также позволяет вам устанавливать динамический заголовок (как в примере кода), что невозможно с использованием пользовательских атрибутов данных в объекте конфигурации маршрутизатора (насколько я знаю, по крайней мере).

5

Если у вас нет элемента управления (например, веб-формы 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;
        });
    });
4

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

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 и запустить его локально, чтобы увидеть изменение названия.

  • 0
    Я придумал что-то подобное. Безусловно, наиболее интуитивно понятен в использовании и не требует установки контроллера на html . В моей директиве я также добавляю необязательную константу pageTitlePrefix .
3

Когда мне пришлось это решить, я не мог поместить 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;
        }
    };

});
3

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

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

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

$rootScope.title = 'Page X'
3

В сценариях, в которых у вас нет 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/

3

Здесь другой способ изменения названия. Возможно, это не так масштабируемо, как функция 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>
  • 2
    Мое мнение, не используйте это. Вы смешиваете данные (информацию) в представлениях (презентации). Позже будет очень трудно найти источники заголовков, разбросанные по вашим HTML-ссылкам, которые могут присутствовать в разных местах представления.
  • 0
    Поскольку заголовок обновляется только после фактического нажатия на ссылку , заголовок устанавливается неправильно, когда пользователь впервые попадает на страницу или когда пользователь обновляется.
1

В то время как другие могут иметь лучшие методы, мне удалось использовать $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;
});

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

0

Упрощенное решение для 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);
   });
}]);
0

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

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

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

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

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

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

-5

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

  • 0
    Пожалуйста, предоставьте пример кода вашего решения!

Ещё вопросы

Сообщество Overcoder
Наверх
Меню