专栏首页全栈修仙之路Angular Meta Service 详解

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:
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
  • http-equiv:相当于文件的头作用,用于向浏览器传递一些有用的信息,以帮助正确地显示网页内容,与之对应的属性为 content。
<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 服务:

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 类型定义如下:

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

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

@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
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 标签:

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 对象:

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(可选):选择器
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 标签:

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

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

removeTagElement()

removeTagElement(meta: HTMLMetaElement): void

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

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

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

Meta Service 源码简析

Meta Service 类及构造函数

// 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() 工厂方法创建对应的实例。

import {Inject, Injectable, inject} from '@angular/core';

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

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

addTag()

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

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

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

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

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

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

内部会转换为:

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

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

updateTag()

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

removeTag(attrSelector: string): void { 
    this.removeTagElement(this.getTag(attrSelector) !);
}

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

参考资源

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 你不知道的 WeakMap

    相信很多读者对 ES6 引入的 Map 已经不陌生了,其中的一部分读者可能也听说过 WeakMap。既生 Map 何生 WeakMap?带着这个问题,本文将围绕...

    阿宝哥
  • TypeScript 函数中的 this 参数

    从 TypeScript 2.0 开始,在函数和方法中我们可以声明 this 的类型,实际使用起来也很简单,比如:

    阿宝哥
  • TypeScript 可选链

    在前后端分离的系统中,前端页面一般通过调用 REST API 来获取服务端提供的与页面相关的数据。这里我们以获取用户基本信息的接口为例,假设该接口会返回以下数据...

    阿宝哥
  • Java枚举(enum)七种常见的用法

    DK1.5引入了新的类型——枚举。在 Java 中它虽然算个“小”功能,却给我的开发带来了“大”方便。 用法一:常量 在JDK1.5 之前,我们定义常量都是:...

    Spark学习技巧
  • Java 枚举7常见种用法

    Tanyboye
  • Java枚举(enum)七种常见的用法

    DK1.5引入了新的类型——枚举。在 Java 中它虽然算个“小”功能,却给我的开发带来了“大”方便。

    Java团长
  • 枚举常见的七种用法

     JDK1.5引入了新的类型——枚举。在 Java 中它虽然算个“小”功能,却给我的开发带来了“大”方便。

    MonroeCode
  • 同时定位与地图创建综述

    SLAM包含两个主要任务,定位和建图。这是移动机器人自主完成作业任务需要解决的基本问题,特别是在未知环境的情况下,移动机器人既要确定自身在环境中的位姿,又要根据...

    3D视觉工坊
  • 【WebApp开发必知】移动游览器私有Meta属性

    Meta属性在移动端可以说是不得不知道的使用得非常频繁的技术。下面就给大家整理一下在移动端的各大浏览器一些私有的Meta属性。

    用户5997198
  • 白话 CRC

    我们经常碰到 CRC 这个概念,尤其是在通信领域。但是 CRC 的原理是什么呢?我们有必要了解一下。

    Dabelv

扫码关注云+社区

领取腾讯云代金券