Как динамически изменить заголовок на основе частичного представления 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"; }
22 ответа
Вы можете определить контроллер на <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
Я только что нашел хороший способ установить заголовок страницы, если вы используете маршрутизацию:
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 — ' + title">myApp</title>
...
Редактировать: используя ng-bind
атрибут вместо фигурных скобок {{}}
чтобы они не показывались при загрузке
Обратите внимание, что вы также можете установить заголовок непосредственно с помощью JavaScript, т. Е.
$window.document.title = someTitleYouCreated;
Это не имеет привязки данных, но достаточно при установке ng-app
в <html>
тег проблемный. (Например, используя шаблоны JSP где <head>
определяется только в одном месте, но у вас есть несколько приложений.)
Декларирование 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>
Модуль 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. Это позволяет избежать необходимости загромождать контроллеры этой логикой представления. Контроллер должен предоставлять любые данные, которые будут использоваться для информирования заголовка, но шаблон окончательно определяет, как его представить, и может использовать интерполяцию выражений и фильтры для привязки к данным области как обычно.
(Отказ от ответственности: я являюсь автором этого модуля, но я ссылаюсь на него здесь только в надежде, что он поможет кому-то еще решить эту проблему.)
Вот адаптированное решение, которое работает для меня, которое не требует внедрения $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);
}
Решение 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");
});
Чистый способ, который позволяет динамически устанавливать заголовок или мета-описание. В примере я использую 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>
...
В качестве альтернативы, если вы используете ui-router:
index.html
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title ng-bind="$state.current.data.title || 'App'">App</title>
маршрутизация
$stateProvider
.state('home', {
url: '/',
templateUrl: 'views/home.html',
data: {
title: 'Welcome Home.'
}
}
Индивидуальное решение на основе событий
Вот еще один подход, который не был упомянут другими здесь (на момент написания статьи).
Вы можете использовать пользовательские события, например, так:
// 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
, Это также позволяет вам установить динамический заголовок (как в примере кода), что невозможно при использовании пользовательских атрибутов данных в объекте конфигурации маршрутизатора (насколько я знаю, по крайней мере).
Если вы не можете контролировать элемент title (например, веб-форму 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;
});
});
Для сценариев, в которых у вас нет 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/
Упрощенное решение для 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);
});
}]);
Ни один из этих ответов не казался достаточно интуитивным, поэтому я разработал небольшую директиву для этого. Этот способ позволяет вам объявить заголовок на странице, где обычно это делается, и позволяет ему быть динамичным.
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>
Вы можете поместить любой текст в тег, который вы хотите, включая угловой код. В этом примере он будет искать $scope.titleText
в каком бы контроллере ни находился тэг custom-title.
Просто убедитесь, что на вашей странице нет нескольких тегов заголовка страницы, иначе они будут заглушать друг друга.
Пример плунжера здесь http://plnkr.co/edit/nK63te7BSbCxLeZ2ADHV. Вам нужно скачать zip и запустить его локально, чтобы увидеть изменение названия.
Простой и грязный способ использования $rootScope
:
<html ng-app="project">
<head>
<title ng-bind="title">Placeholder title</title>
В ваших контроллерах, когда у вас есть данные, необходимые для создания заголовка, выполните:
$rootScope.title = 'Page X'
Когда я должен был решить это, я не мог поставить 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's cool tagline";
var newTitle = (title ? title : defaultTitle) + (hideTextLogo ? '' : ' - My App')
$document[0].title = newTitle;
}
};
});
Вот другой способ сделать изменения названия. Возможно, не так масштабируемо, как фабричная функция (которая могла бы обрабатывать неограниченное количество страниц), но мне было легче понять:
В моем 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>
Заметьте, что нг-плащ? Он не имеет ничего общего с этим ответом, но он скрывает страницу до тех пор, пока она не загрузится, приятное прикосновение:) Узнайте, как здесь: 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. [Корни, я знаю - может быть, это сайт, который празднует все виды куриных самородков;) В любом случае, вы можете обновить заголовок, когда пользователь нажимает на любую ссылку, поэтому на моей главной странице 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-template 0,9. Вы можете видеть, что это не так сильно отличается.
Запоздалая мысль: вы знаете это, но это здесь для всех остальных; В нижней части index.html необходимо включить app.js с модулем:
<!-- APP -->
<script type="text/javascript" src="js/app.js"></script>
</body>
</html>
Специальное решение на основе событий, вдохновленное Майклом Бромли
Я не смог заставить его работать с $ scope, поэтому я попытался с rootScope, возможно, немного более грязным... (особенно если вы делаете обновление на странице, которая не регистрирует событие)
Но мне очень нравится идея о том, как вещи слабо связаны.
Я использую 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>
Это работает для меня:)
В то время как другие могут иметь лучшие методы, я смог использовать $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;
});
Опять же, у других могут быть лучшие идеи о том, как подойти к этому, но так как я использую предварительный рендеринг, мои потребности удовлетворяются.
Mr Hash был лучший ответ, но приведенное ниже решение делает его идеальным (для меня), добавляя следующие преимущества:
- Не добавляет часов, которые могут замедлить ход событий
- На самом деле автоматизирует то, что я мог бы сделать в контроллере, пока
- По-прежнему дает мне доступ с контроллера, если я все еще хочу это.
- Никаких дополнительных инъекций
В роутере:
.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
])
Спасибо Tosh за его решение.
Я думал, что было не так чисто поставить службу прямо в $scope
так вот мое небольшое изменение на этом: http://plnkr.co/edit/QJbuZZnZEDOBcYrJXWWs
Контроллер (который в первоначальном ответе показался мне слишком тупым) создает объект ActionBar, и этот объект помещается в $scope.
Объект отвечает за фактический запрос сервиса. Он также скрывает от $ scope вызов для установки URL-адреса шаблона, который вместо этого доступен другим контроллерам для установки URL-адреса.
Лучшее и динамичное решение, которое я нашел, - это использовать $watch для отслеживания изменений переменных, а затем обновлять заголовок.