Angular SSR 元标记未在承诺中加载
Angular9 SSR meta tags not loading inside of promises
我正在为我的 SSR 使用 Angular Universal 和 NestJS。当我 运行 npm run build:ssr
并部署它时,我没有得到任何错误,但是在链接网站时不会显示 promises 内的元标记,但是我在根级别设置元标记的路由链接ngOnInit 会在链接网站时按预期加载元标记。
设置元标记的代码。
generateTags({ title = '', description = '', image = '' }) {
this.title.setTitle(title);
this.meta.addTags([
// Open Graph
{ name: 'og:url', content: `https://firestarter.fireship.io${this.router.url}` },
{ name: 'og:title', content: title },
{ name: 'og:description', content: description },
{ name: 'og:image', content: image },
// Twitter Card
{ name: 'twitter:card', content: 'summary' },
{ name: 'twitter:site', content: '@fireship_dev' },
]);
}
Ek 不加载元标记的代码示例
this.db.firestore.collection('users').doc(this.customerId).get().then((userRef) => {
this.seo.generateTags({
title: userRef.data().displayName,
description: userRef.data().about,
image: userRef.data().photoURL,
})
})
不加载元标记的代码示例
this.seo.generateTags({
title: userRef.data().displayName,
description: userRef.data().about,
image: userRef.data().photoURL,
})
包含完整组件的示例:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AngularFirestore } from '@angular/fire/firestore';
import { SeoService } from 'src/app/services/seo.service';
@Component({
selector: 'app-profileviewer',
templateUrl: './profileviewer.component.html',
styleUrls: ['./profileviewer.component.css']
})
export class ProfileviewerComponent implements OnInit {
customerId: string;
constructor(
private route: ActivatedRoute,
private db: AngularFirestore,
private seo: SeoService) { }
ngOnInit() {
this.customerId = this.route.snapshot.paramMap.get('id');
this.db.firestore.collection('users').doc(this.customerId).get().then((userRef) => {
this.seo.generateTags({
title: userRef.data().displayName,
description: userRef.data().about,
image: userRef.data().photoURL,
})
})
}
}
谁可以使用 Angular9 和 NestJS 在我的元标记中显示来自例如 firebase 的内容?
我在 GitHub 上创建了一个 dummy project,但出现了这个错误。要在环境文件和 运行 npm run serve:ssr
中重现附加 firebase 项目(如果出现错误,可能 npm run build:ssr
)并在 chrome 中的源代码中看到元标记是未渲染。
编辑:
我已经尝试使用 resolve 来解决这个问题,但它仍然不能用于 promises。这是我使用的解析脚本:
import { Injectable } from '@angular/core';
import { AngularFirestore } from '@angular/fire/firestore';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Http } from '@angular/http';
@Injectable({
providedIn: 'root'
})
export class LoadSeoService implements Resolve<any> {
constructor (private http: Http, private db: AngularFirestore) {
}
resolve (route: ActivatedRouteSnapshot, rstate: RouterStateSnapshot) {
// Works
return "test"
// Does not work
// return this.db.firestore.collection("articles").doc(route.params['id'].substr(route.params['id'].length - 20)).get();
}
}
我在组件中使用的代码:
this.route.data.subscribe(data => {
console.log(data.cres)
this.seo.generateTags({
title: data.cres,
description: data.cres,
image: data.cres,
})
});
您对 firestore 的调用是异步的,因此 SSR 在 promise 解析之前完成。所有异步功能都需要在路由器级别解决,因此 angular 等待它们完成。
尝试将 firestore 请求添加到路由解析器,并在 ngOnInit() 函数中使用来自解析器的数据。
在此处查看更多信息:https://angular.io/api/router/Resolve
最后,经过大量研究和调试,我发现为什么您在 index.html 中看不到元标记。
- 确保添加了元标记。添加标签后,我在控制台中打印了元对象。你也可以检查一下。打印它:-
在输出中进入 _doc>documentChild>innerHtml
在上图中的 innerHTML 中,您可以注意到您的元标记。
- 为什么它们不是 index.html 的一部分,当它显示时,是因为 firebase promise 在连接到 firebase 并接收数据时会自动解析。但因为它是你的一部分。您的 ssr 不会生成标签。当您的承诺是 resolved.Any 页面加载后不会显示在页面视图中的正在更改的内容时,这些标签将稍后动态添加 source.If 您想要检查您需要检查的更新元标记元素并检查那里。
为了证明我的第二点,这里是 index.html 网络选项卡中文件的屏幕截图。
元素检查器的屏幕截图显示您的元标记已成功添加,但在运行时。
如果您将创建一个普通的 promise 并在其中调用您的生成标签方法,它将显示在源本身中,因为您将只在那里解决该 promise。但是在 firebase 的情况下,该承诺在获取数据之前不会得到解决,因此您的 ssr 无法预渲染它。
要使用 ssr 启用 firebase,您可以在此处找到信息:-
Angular SSR 在将响应发送给用户之前会等待 ngInit() 中的所有承诺完成。但是我发现 ngInit 并没有使用 firestore 的 get() 方法等待。所以我绑定了 valueChanges() 并且它确实按预期工作,另一个好处是它 returns 一个直接带有数据的 Observable,所以你的 ngInit 看起来像:
ngOnInit() {
this.customerId = this.route.snapshot.paramMap.get('id');
this.db.collection('users').doc(this.customerId).valueChanges().subscribe(user => {
this.seo.generateTags({
title: user.displayName,
description: user.about,
image: user.photoURL,
});
});
}
另一件您可能会发现有用的事情是使用 TransferState 将服务器已解析的内容传输到浏览器,这样它就不会在浏览器完全加载页面时再次尝试获取信息。所以你的组件最终会是这样的:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AngularFirestore } from '@angular/fire/firestore';
import { SeoService } from 'src/app/services/seo.service';
import { TransferState, makeStateKey } from '@angular/platform-browser';
const USER_TAGS_KEY = makeStateKey('ProfileviewerComponent_UserTagsState');
@Component({
selector: 'app-profileviewer',
templateUrl: './profileviewer.component.html',
styleUrls: ['./profileviewer.component.css']
})
export class ProfileviewerComponent implements OnInit {
customerId: string;
tags = [];
constructor(
private route: ActivatedRoute,
private db: AngularFirestore,
private state: TransferState,
private seo: SeoService) { }
ngOnInit() {
this.customerId = this.route.snapshot.paramMap.get('id');
this.tags = this.state.get(USER_TAGS_KEY, null);
if (!this.tags) {
this.db.collection('users').doc(this.customerId).valueChanges().subscribe(user => {
this.tags = {
title: user.displayName,
description: user.about,
image: user.photoURL,
};
this.state.set(USER_TAGS_KEY, this.tags);
this.seo.generateTags(this.tags);
});
}
}
}
我正在为我的 SSR 使用 Angular Universal 和 NestJS。当我 运行 npm run build:ssr
并部署它时,我没有得到任何错误,但是在链接网站时不会显示 promises 内的元标记,但是我在根级别设置元标记的路由链接ngOnInit 会在链接网站时按预期加载元标记。
设置元标记的代码。
generateTags({ title = '', description = '', image = '' }) {
this.title.setTitle(title);
this.meta.addTags([
// Open Graph
{ name: 'og:url', content: `https://firestarter.fireship.io${this.router.url}` },
{ name: 'og:title', content: title },
{ name: 'og:description', content: description },
{ name: 'og:image', content: image },
// Twitter Card
{ name: 'twitter:card', content: 'summary' },
{ name: 'twitter:site', content: '@fireship_dev' },
]);
}
Ek 不加载元标记的代码示例
this.db.firestore.collection('users').doc(this.customerId).get().then((userRef) => {
this.seo.generateTags({
title: userRef.data().displayName,
description: userRef.data().about,
image: userRef.data().photoURL,
})
})
不加载元标记的代码示例
this.seo.generateTags({
title: userRef.data().displayName,
description: userRef.data().about,
image: userRef.data().photoURL,
})
包含完整组件的示例:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AngularFirestore } from '@angular/fire/firestore';
import { SeoService } from 'src/app/services/seo.service';
@Component({
selector: 'app-profileviewer',
templateUrl: './profileviewer.component.html',
styleUrls: ['./profileviewer.component.css']
})
export class ProfileviewerComponent implements OnInit {
customerId: string;
constructor(
private route: ActivatedRoute,
private db: AngularFirestore,
private seo: SeoService) { }
ngOnInit() {
this.customerId = this.route.snapshot.paramMap.get('id');
this.db.firestore.collection('users').doc(this.customerId).get().then((userRef) => {
this.seo.generateTags({
title: userRef.data().displayName,
description: userRef.data().about,
image: userRef.data().photoURL,
})
})
}
}
谁可以使用 Angular9 和 NestJS 在我的元标记中显示来自例如 firebase 的内容?
我在 GitHub 上创建了一个 dummy project,但出现了这个错误。要在环境文件和 运行 npm run serve:ssr
中重现附加 firebase 项目(如果出现错误,可能 npm run build:ssr
)并在 chrome 中的源代码中看到元标记是未渲染。
编辑: 我已经尝试使用 resolve 来解决这个问题,但它仍然不能用于 promises。这是我使用的解析脚本:
import { Injectable } from '@angular/core';
import { AngularFirestore } from '@angular/fire/firestore';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Http } from '@angular/http';
@Injectable({
providedIn: 'root'
})
export class LoadSeoService implements Resolve<any> {
constructor (private http: Http, private db: AngularFirestore) {
}
resolve (route: ActivatedRouteSnapshot, rstate: RouterStateSnapshot) {
// Works
return "test"
// Does not work
// return this.db.firestore.collection("articles").doc(route.params['id'].substr(route.params['id'].length - 20)).get();
}
}
我在组件中使用的代码:
this.route.data.subscribe(data => {
console.log(data.cres)
this.seo.generateTags({
title: data.cres,
description: data.cres,
image: data.cres,
})
});
您对 firestore 的调用是异步的,因此 SSR 在 promise 解析之前完成。所有异步功能都需要在路由器级别解决,因此 angular 等待它们完成。
尝试将 firestore 请求添加到路由解析器,并在 ngOnInit() 函数中使用来自解析器的数据。
在此处查看更多信息:https://angular.io/api/router/Resolve
最后,经过大量研究和调试,我发现为什么您在 index.html 中看不到元标记。
- 确保添加了元标记。添加标签后,我在控制台中打印了元对象。你也可以检查一下。打印它:- 在输出中进入 _doc>documentChild>innerHtml
在上图中的 innerHTML 中,您可以注意到您的元标记。
- 为什么它们不是 index.html 的一部分,当它显示时,是因为 firebase promise 在连接到 firebase 并接收数据时会自动解析。但因为它是你的一部分。您的 ssr 不会生成标签。当您的承诺是 resolved.Any 页面加载后不会显示在页面视图中的正在更改的内容时,这些标签将稍后动态添加 source.If 您想要检查您需要检查的更新元标记元素并检查那里。
为了证明我的第二点,这里是 index.html 网络选项卡中文件的屏幕截图。
元素检查器的屏幕截图显示您的元标记已成功添加,但在运行时。
如果您将创建一个普通的 promise 并在其中调用您的生成标签方法,它将显示在源本身中,因为您将只在那里解决该 promise。但是在 firebase 的情况下,该承诺在获取数据之前不会得到解决,因此您的 ssr 无法预渲染它。
要使用 ssr 启用 firebase,您可以在此处找到信息:-
Angular SSR 在将响应发送给用户之前会等待 ngInit() 中的所有承诺完成。但是我发现 ngInit 并没有使用 firestore 的 get() 方法等待。所以我绑定了 valueChanges() 并且它确实按预期工作,另一个好处是它 returns 一个直接带有数据的 Observable,所以你的 ngInit 看起来像:
ngOnInit() {
this.customerId = this.route.snapshot.paramMap.get('id');
this.db.collection('users').doc(this.customerId).valueChanges().subscribe(user => {
this.seo.generateTags({
title: user.displayName,
description: user.about,
image: user.photoURL,
});
});
}
另一件您可能会发现有用的事情是使用 TransferState 将服务器已解析的内容传输到浏览器,这样它就不会在浏览器完全加载页面时再次尝试获取信息。所以你的组件最终会是这样的:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AngularFirestore } from '@angular/fire/firestore';
import { SeoService } from 'src/app/services/seo.service';
import { TransferState, makeStateKey } from '@angular/platform-browser';
const USER_TAGS_KEY = makeStateKey('ProfileviewerComponent_UserTagsState');
@Component({
selector: 'app-profileviewer',
templateUrl: './profileviewer.component.html',
styleUrls: ['./profileviewer.component.css']
})
export class ProfileviewerComponent implements OnInit {
customerId: string;
tags = [];
constructor(
private route: ActivatedRoute,
private db: AngularFirestore,
private state: TransferState,
private seo: SeoService) { }
ngOnInit() {
this.customerId = this.route.snapshot.paramMap.get('id');
this.tags = this.state.get(USER_TAGS_KEY, null);
if (!this.tags) {
this.db.collection('users').doc(this.customerId).valueChanges().subscribe(user => {
this.tags = {
title: user.displayName,
description: user.about,
image: user.photoURL,
};
this.state.set(USER_TAGS_KEY, this.tags);
this.seo.generateTags(this.tags);
});
}
}
}