qrcode

马上订阅,开启修仙之旅

Angular Meta Service 详解

Meta 标签

The

tag provides metadata about the HTML document. Metadata will not be displayed on the page, but will be machine parsable.

Metadata 中文名叫元数据,是用于描述数据的数据。它不会显示在页面上,但是机器却可以识别。meta 常用于定义页面的说明,关键字,最后修改日期,和其它的元数据。这些元数据将服务于浏览器,搜索引擎和其它网络服务。

meta 标签共有两个属性,分别是 name 属性和 http-equiv 属性:

  • name:主要用于描述网页,比如网页的关键词,网站描述等。与之对应的属性值为 content,content 中的内容是对 name 填入类型的具体描述,便于搜索引擎抓取。比如我们常见的 viewport:
1
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
  • http-equiv:相当于文件的头作用,用于向浏览器传递一些有用的信息,以帮助正确地显示网页内容,与之对应的属性为 content。
1
<meta http-equiv="X-UA-Compatible" content="IE=edge">

以上代码告诉 IE 浏览器,IE8/9 及以后的版本都会以最高版本 IE 来渲染页面。关于 HTML meta 标签的相关知识,这里就不再继续展开,感兴趣的同学可以阅读 HTML meta标签总结与属性使用介绍 这篇文章。

Meta Service 简介

为了让开发者能够方便地操作页面中的 Meta 信息,Angular 为我们提供 Meta 服务。该服务支持以下的方法:

首先要使用 Meta 服务,我们需要从 @angular/platform-browser 库导入 Meta 类,然后利用 Angular 依赖注入的机制,通过构造注入的方式注入 Meta 服务:

1
2
3
4
5
6
7
8
9
import { Injectable } from '@angular/core';
import { Meta } from '@angular/platform-browser';

@Injectable({
providedIn: 'root'
})
export class MetaService {
constructor(private meta: Meta) { }
}

addTag()

addTag(tag: MetaDefinition, forceCreation: boolean = false): HTMLMetaElement | null

该方法用于在页面上添加一个 HTML Meta 标签,它接收两个参数:

  • tag:MetaDefinition 类型的对象
  • forceCreation:是否强制创建,默认为 false

tag 参数对应的 MetaDefinition 类型定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export type MetaDefinition = {
charset?: string;
content?: string;
httpEquiv?: string;
id?: string;
itemprop?: string;
name?: string;
property?: string;
scheme?: string;
url?: string;
} &
{
// TODO(IgorMinar): this type looks wrong
[prop: string]: string;
};

了解完上述的内容,我们来动手实践一下:

1
2
3
4
5
6
7
8
9
10
11
@Injectable({
providedIn: "root"
})
export class MetaService {
constructor(private meta: Meta) { }

addTag() {
this.meta.addTag({ name: 'description', content: 'Angular Meta Service' });
this.meta.addTag({ name: 'keywords', content: 'Angular, RxJS, TypeScript' });
}
}

上述代码我们通过调用 meta 服务的 addTag() 方法,创建了两个 Meta 标签。如果需要一次添加多个 meta 标签,我们可以调用 addTags() 方法。

addTags()

addTags(tags: MetaDefinition[], forceCreation: boolean = false): HTMLMetaElement[]

该方法用于一次性添加多个 HTML Meta 标签,它接收两个参数:

  • tags:MetaDefinition 类型的对象数组
  • forceCreation:是否强制创建,默认为 false
1
2
3
4
5
6
addTags() {
this.meta.addTags([
{ name: 'description', content: 'Angular Meta Service' },
{ name: 'keywords', content: 'Angular, RxJS, TypeScript' }
]);
}

在创建完 HTML meta 标签,我们可以通过 getTag() 方法来获取对应的 HTMLMetaElement 对象。

getTag()

getTag(attrSelector: string): HTMLMetaElement | null

该方法用于获取 attrSelector 属性选择器对应的 HTMLMetaElement 对象,它接收一个参数,即属性选择器,比如我们需要获取 keywords meta 标签:

1
2
3
4
getMetaTag(){
let metaEl: HTMLMetaElement = this.meta.getTag('name="keywords"');
console.log(`Get keywords meta tag: ${metaEl}`);
}

当 getTag() 方法匹配不了 attrSelector 属性选择器时,会返回 null 对象。与 setTag() 类似,getTag() 方法也存在一个 getTags() 方法。

getTags()

getTags(attrSelector: string): HTMLMetaElement[]

该方法用于获取所有匹配 attrSelector 选择器的所有 HTMLMetaElement 对象:

1
2
3
4
5
6
7
8
getMetaTags() {
let els: HTMLMetaElement[] = this.meta.getTags('name');
els.forEach(el => {
console.log(el);
console.log(el.name);
console.log(el.content);
});
}

这时我们已经介绍完了如何创建和查找 HTMLMetaElement 对象,有时候在创建完 HTMLMetaElement 对象后,我们可能需要修改 HTMLMetaElement 对象,此时我们需要使用 updateTag() 方法。

updateTag()

updateTag(tag: MetaDefinition, selector?: string): HTMLMetaElement | null

该方法用于更新 HTML Meta 标签的信息,它接收两个参数:

  • tag:MetaDefinition 类型的对象
  • selector(可选):选择器
1
2
3
4
5
6
7
updateMetaTags() {
this.meta.updateTag({
name: 'description',
content: 'Updated: Angular Meta Service'
});
this.meta.updateTag({ name: 'keywords', content: 'Node.js, Angular' });
}

除了更新 HTML Meta 标签之外,我们也可以移除指定的 HTML Meta 标签。

removeTag()

removeTag(attrSelector: string): void

该方法用于移除匹配 attrSelector 属性选择器的 HTML Meta 标签:

1
2
3
4
removeMetaTags() {
this.meta.removeTag('name="description"');
this.meta.removeTag('name="keywords"');
}

最后我们来介绍 removeTagElement() 方法。

removeTagElement()

removeTagElement(meta: HTMLMetaElement): void

该方法用于移除 meta 参数对应的 HTML Meta 标签:

1
2
3
4
removeTagElement() {
let keywords: HTMLMetaElement = this.meta.getTag('name="keywords"');
this.meta.removeTagElement(keywords);
}

感兴趣的同学,可以浏览 Stackblitz 线上示例。下面我们来简单分析一下 Meta Service 的源码。

Meta Service 源码简析

Meta Service 类及构造函数

1
2
3
4
5
6
7
8
// packages/platform-browser/src/browser/meta.ts
@Injectable({providedIn: 'root', useFactory: createMeta, deps: []})
export class Meta {
private _dom: DomAdapter;
constructor(@Inject(DOCUMENT) private _doc: any) {
this._dom = getDOM(); // 获取DOM适配器
}
}

通过观察 Injectable 装饰器的 Meta 元信息,我们知道 Meta 服务将被注册在根级注入器中,当首次获取 Meta 服务时,将使用 createMeta() 工厂方法创建对应的实例。

1
2
3
4
5
import {Inject, Injectable, inject} from '@angular/core';

export function createMeta() {
return new Meta(inject(DOCUMENT));// 注意这里是小写的inject的哦
}

接下来我们从最简单的 addTag() 方法开始分析。

addTag()

1
2
3
4
addTag(tag: MetaDefinition, forceCreation: boolean = false): HTMLMetaElement|null {
if (!tag) return null;
return this._getOrCreateElement(tag, forceCreation);
}

这时我们知道其实在 addTag() 方法内部,最终是调用内部的私有方法 _getOrCreateElement() 来执行具体操作。_getOrCreateElement() 方法的具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private _getOrCreateElement(meta: MetaDefinition, forceCreation: boolean = false):
HTMLMetaElement {
if (!forceCreation) { // 非强制模式
const selector: string = this._parseSelector(meta); // 解析选择器
const elem: HTMLMetaElement = this.getTag(selector) !; // 获取选择器匹配的Meta元素
// It's allowed to have multiple elements with the same name so it's not enough to
// just check that element with the same name already present on the page.
// We also need to check if element has tag attributes
if (elem && this._containsAttributes(meta, elem)) return elem;
}
// 调用Dom适配器的createElement()方法创建meta元素
const element: HTMLMetaElement = this._dom.createElement('meta') as HTMLMetaElement;
this._setMetaElementAttributes(meta, element);
// 获取head元素,添加新建的meta元素并返回该元素
const head = this._dom.getElementsByTagName(this._doc, 'head')[0];
this._dom.appendChild(head, element);
return element;
}

// 解析选择器
private _parseSelector(tag: MetaDefinition): string {
const attr: string = tag.name ? 'name' : 'property';
return `${attr}="${tag[attr]}"`;
}

// 设置Meta元素的属性
private _setMetaElementAttributes(tag: MetaDefinition, el: HTMLMetaElement):
HTMLMetaElement {
Object.keys(tag).forEach((prop: string) =>
this._dom.setAttribute(el, prop, tag[prop]));
return el;
}

简单分析完 addTag(),我们再来看一下与它对应的 getTag() 方法。

getTag()

1
2
3
4
getTag(attrSelector: string): HTMLMetaElement|null {
if (!attrSelector) return null;
return this._dom.querySelector(this._doc, `meta[${attrSelector}]`) || null;
}

该方法内部的实现也很简单,就是通过 DOM 适配器的 querySelector API 来实现元素匹配。对于前面的示例来说:

1
let metaEl: HTMLMetaElement = this.meta.getTag('name="keywords"');

内部会转换为:

1
return this._dom.querySelector(this._doc, "meta[name='keywords')" || null;

新增和查询的方法介绍完,我们来继续分析一下 updateTag() 方法。

updateTag()

1
2
3
4
5
6
7
8
9
updateTag(tag: MetaDefinition, selector?: string): HTMLMetaElement|null {
if (!tag) return null;
selector = selector || this._parseSelector(tag); // 解析选择器
const meta: HTMLMetaElement = this.getTag(selector) !; // 获取选择器对应的 Meta 元素
if (meta) { // 若已存在,则更新对应的属性
return this._setMetaElementAttributes(tag, meta);
}
return this._getOrCreateElement(tag, true); // 否则在force模式下,创建 Meta 元素
}

最后再来看一下 removeTag() 方法,顾名思义就是用来移除指定的 Meta 元素。

removeTag()

1
2
3
4
5
6
7
8
9
removeTag(attrSelector: string): void { 
this.removeTagElement(this.getTag(attrSelector) !);
}

removeTagElement(meta: HTMLMetaElement): void {
if (meta) {
this._dom.remove(meta);
}
}

参考资源


欢迎小伙伴们订阅前端修仙之路,及时阅读 Angular、RxJS、TypeScript 和 Node.js 最新文章。

qrcode