代码之家  ›  专栏  ›  技术社区  ›  r3plica

Angular Universal在订阅中添加元标签

  •  0
  • r3plica  · 技术社区  · 4 年前

    我在尝试动态设置元标签时遇到了一些实际问题。 我可以很容易地在 ngOnInit 方法,但如果我使用 订阅 addTag方法什么也不做。

    export class AppComponent implements OnInit, OnDestroy {
        public page: Page;
        public postSlug: string;
    
        private pages: Page[] = [];
        private url: string = '/';
    
        private routerSubscription: Subscription;
        private pagesSubscription: Subscription;
    
        constructor(
            private router: Router,
            private contentfulService: ContentfulService,
            private pageService: PageService,
            private meta: Meta
        ) {}
    
        ngOnInit(): void {
            this.meta.addTag({ name: 'app', content: 'Set from app component' });
            this.url = this.router.url.split('#')[0]; // For initial page load
    
            this.contentfulService.getPages();
            this.getPages();
            this.onNavigationEnd();
        }
    
        ngOnDestroy(): void {
            if (this.routerSubscription) this.routerSubscription.unsubscribe();
            if (this.pagesSubscription) this.pagesSubscription.unsubscribe();
        }
    
        private onNavigationEnd(): void {
            this.routerSubscription = this.router.events.subscribe((event: any) => {
                if (!(event instanceof NavigationEnd)) return;
    
                this.url = event.urlAfterRedirects.split('#')[0];
    
                this.setPage();
                this.setPost();
            });
        }
    
        private setPost(): void {
            this.postSlug = undefined; // Always reset
    
            if (!this.page || this.url.indexOf('/live-better/') === -1) return;
    
            this.meta.addTag({ name: 'post', content: 'Set post' });
    
            var urlParts = this.url.split('/');
            this.postSlug = urlParts[urlParts.length - 1];
        }
    
        private setPage(): void {
            if (!this.pages.length || !this.url) return;
            this.page = this.pages.find((page: Page) => page.slug === this.url);
    
            if (!this.page) {
                this.page = this.pages.find(
                    (page: Page) => this.url.indexOf(page.slug) === 0
                );
            }
    
            this.meta.addTag({ name: 'page', content: 'Set page' });
    
            console.log(this.page);
    
            this.pageService.setTitle(this.page.title);
            this.pageService.setMetadata(this.page);
        }
    
        private getPages(): void {
            this.pagesSubscription = this.contentfulService.pages.subscribe(
                (pages: Page[]) => {
                    if (!pages.length) return;
                    this.pages = pages;
    
                    this.meta.addTag({ name: 'pages', content: 'Get pages' });
    
                    this.setPage();
                    this.setPost();
                }
            );
        }
    }
    

    其余代码执行完毕,一切正常。如果我查看源代码,我可以看到以下标签 { name: 'app', content: 'Set from app component' } 但我看不见其他人。

    有人知道我是否遗漏了什么吗?


    我认为这一定是视图后数据加载的问题,所以我创建了一个这样的解析器:

    import { Injectable } from '@angular/core';
    import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
    import { ContentfulService } from '../services/contentful.service';
    import { Meta } from '@angular/platform-browser';
    import { mergeMap, first } from 'rxjs/operators';
    import { Observable, of, from } from 'rxjs';
    
    import { Resolve } from '../models/resolve';
    import { Page } from '../models/page';
    
    @Injectable({ providedIn: 'root' })
    export class PageResolver implements Resolve<{ page: Page; postSlug: string }> {
        constructor(
            private contentfulService: ContentfulService,
            private meta: Meta
        ) {}
    
        resolve(
            route: ActivatedRouteSnapshot,
            state: RouterStateSnapshot
        ): Observable<{ page: Page; postSlug: string }> {
            console.log('about to parse');
            return this.getData(state);
        }
    
        private getData(
            state: RouterStateSnapshot
        ): Observable<{ page: Page; postSlug: string }> {
            const currentUrl = state.url.split('#')[0];
            console.log(currentUrl);
    
            this.meta.addTag({ name: 'resolve', content: 'Resolving route' });
            if (!this.contentfulService.current.length) {
                console.log('first load');
                return from(
                    this.contentfulService.getPages().then((pages: Page[]) => {
                        this.meta.addTag({
                            name: 'first',
                            content: 'First resolve hit',
                        });
                        return this.parseData(pages, currentUrl);
                    })
                );
            } else {
                console.log('after load');
                return this.contentfulService.pages.pipe(
                    first(),
                    mergeMap((pages: Page[]) => {
                        this.meta.addTag({
                            name: 'second',
                            content: 'Changed page',
                        });
                        return of(this.parseData(pages, currentUrl));
                    })
                );
            }
        }
    
        private parseData(
            pages: Page[],
            currentUrl: string
        ): { page: Page; postSlug: string } {
            let page = this.setPage(pages, currentUrl);
            let postSlug = this.setPost(page, currentUrl);
            let data: { page: Page; postSlug: string } = {
                page: page,
                postSlug: postSlug,
            };
    
            console.log(data);
            return data;
        }
    
        private setPage(pages: Page[], currentUrl: string): Page {
            if (!pages.length || !currentUrl) throw 'No pages have been loaded';
            let page = pages.find((page: Page) => page.slug === currentUrl);
    
            if (!page) {
                page = pages.find(
                    (page: Page) => currentUrl.indexOf(page.slug) === 0
                );
            }
    
            return page;
        }
    
        private setPost(page: Page, currentUrl: string): string {
            if (!page || currentUrl.indexOf('/live-better/') === -1) return;
    
            let urlParts = currentUrl.split('/');
            let postSlug = urlParts[urlParts.length - 1];
    
            let queryIndex = postSlug.indexOf('?');
            if (queryIndex > -1) postSlug = postSlug.substring(0, queryIndex);
    
            return postSlug;
        }
    }
    

    并将其添加到我的路由中,如下所示:

    const routes: Routes = [
        {
            path: '**',
            component: HomeComponent,
            resolve: { content: PageResolver },
        },
    ];
    

    现在在我的 主页组件 我只是得到这样的数据:

    import { Component, OnInit } from '@angular/core';
    import { ActivatedRoute } from '@angular/router';
    
    import { Page } from '@models';
    import { Meta } from '@angular/platform-browser';
    
    @Component({
        selector: 'sxp-home',
        templateUrl: './home.component.html',
        styleUrls: ['./home.component.scss'],
    })
    export class HomeComponent implements OnInit {
        public page: Page;
        public postSlug: string;
    
        constructor(private route: ActivatedRoute, private meta: Meta) {}
    
        ngOnInit(): void {
            this.meta.addTag({ name: 'home', content: 'Home component loaded' });
            this.route.data.subscribe(
                (data: { content: { page: Page; postSlug: string } }) => {
                    let content = data.content;
                    this.meta.addTag({ name: 'meta', content: 'Subscription hit' });
                    this.page = content.page;
                    this.postSlug = content.postSlug;
                    console.log(content);
                }
            );
        }
    }
    

    如您所见,我使用了 addTag 分解器中3次,分解器上2次 主页组件 ,但当我查看源代码时,实际上只添加了一个:

    this.meta.addTag({ name: 'resolve', content: 'Resolving route' });
    

    我似乎无法在任何订阅背后设置任何元标签。我希望解析器会延迟视图页面源代码实际尝试获取数据,直到所有内容都首先加载到解析器中。

    我在每次之后都添加了控制台日志 addTag 电话。 您可以在此处查看它们:

    https://sxp-develop-marketing.azurewebsites.net/

    0 回复  |  直到 4 年前
        1
  •  0
  •   andsilver    4 年前

    页面源代码是静态html,在浏览器上呈现时将再次评估。此时插入元标签。

    要使它们在页面源代码中可见,您必须启用服务器端呈现- https://angular.io/guide/universal

        2
  •  0
  •   r3plica    4 年前

    因此,元数据设置不正确的原因是没有使用 TransferState .我创建了一个新的服务(TransferHttpService),它看起来像这样:

    import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
    import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
    import {
      TransferState,
      StateKey,
      makeStateKey,
    } from '@angular/platform-browser';
    import { Observable, from } from 'rxjs';
    import { tap } from 'rxjs/operators';
    import { isPlatformBrowser, isPlatformServer } from '@angular/common';
    
    @Injectable({ providedIn: 'root' })
    export class TransferHttpService {
      constructor(
        protected transferState: TransferState,
        private httpClient: HttpClient,
        @Inject(PLATFORM_ID) private platformId: Object
      ) {}
    
      request<T>(
        method: string,
        uri: string | Request,
        options?: {
          body?: any;
          headers?:
            | HttpHeaders
            | {
                [header: string]: string | string[];
              };
          reportProgress?: boolean;
          observe?: 'response';
          params?:
            | HttpParams
            | {
                [param: string]: string | string[];
              };
          responseType?: 'json';
          withCredentials?: boolean;
        }
      ): Observable<T> {
        // tslint:disable-next-line:no-shadowed-variable
        return this.getData<T>(
          method,
          uri,
          options,
          (method: string, url: string, options: any) => {
            return this.httpClient.request<T>(method, url, options);
          }
        );
      }
    
      /**
       * Performs a request with `get` http method.
       */
      get<T>(
        url: string,
        options?: {
          headers?:
            | HttpHeaders
            | {
                [header: string]: string | string[];
              };
          observe?: 'response';
          params?:
            | HttpParams
            | {
                [param: string]: string | string[];
              };
          reportProgress?: boolean;
          responseType?: 'json';
          withCredentials?: boolean;
        }
      ): Observable<T> {
        // tslint:disable-next-line:no-shadowed-variable
        return this.getData<T>(
          'get',
          url,
          options,
          (_method: string, url: string, options: any) => {
            return this.httpClient.get<T>(url, options);
          }
        );
      }
    
      /**
       * Performs a request with `post` http method.
       */
      post<T>(
        url: string,
        body: any,
        options?: {
          headers?:
            | HttpHeaders
            | {
                [header: string]: string | string[];
              };
          observe?: 'response';
          params?:
            | HttpParams
            | {
                [param: string]: string | string[];
              };
          reportProgress?: boolean;
          responseType?: 'json';
          withCredentials?: boolean;
        }
      ): Observable<T> {
        // tslint:disable-next-line:no-shadowed-variable
        return this.getPostData<T>(
          'post',
          url,
          body,
          options,
          // tslint:disable-next-line:no-shadowed-variable
          (_method: string, url: string, body: any, options: any) => {
            return this.httpClient.post<T>(url, body, options);
          }
        );
      }
    
      /**
       * Performs a request with `put` http method.
       */
      put<T>(
        url: string,
        _body: any,
        options?: {
          headers?:
            | HttpHeaders
            | {
                [header: string]: string | string[];
              };
          observe?: 'body';
          params?:
            | HttpParams
            | {
                [param: string]: string | string[];
              };
          reportProgress?: boolean;
          responseType?: 'json';
          withCredentials?: boolean;
        }
      ): Observable<T> {
        // tslint:disable-next-line:no-shadowed-variable
        return this.getPostData<T>(
          'put',
          url,
          _body,
          options,
          (_method: string, url: string, _body: any, options: any) => {
            return this.httpClient.put<T>(url, _body, options);
          }
        );
      }
    
      /**
       * Performs a request with `delete` http method.
       */
      delete<T>(
        url: string,
        options?: {
          headers?:
            | HttpHeaders
            | {
                [header: string]: string | string[];
              };
          observe?: 'response';
          params?:
            | HttpParams
            | {
                [param: string]: string | string[];
              };
          reportProgress?: boolean;
          responseType?: 'json';
          withCredentials?: boolean;
        }
      ): Observable<T> {
        // tslint:disable-next-line:no-shadowed-variable
        return this.getData<T>(
          'delete',
          url,
          options,
          (_method: string, url: string, options: any) => {
            return this.httpClient.delete<T>(url, options);
          }
        );
      }
    
      /**
       * Performs a request with `patch` http method.
       */
      patch<T>(
        url: string,
        body: any,
        options?: {
          headers?:
            | HttpHeaders
            | {
                [header: string]: string | string[];
              };
          observe?: 'response';
          params?:
            | HttpParams
            | {
                [param: string]: string | string[];
              };
          reportProgress?: boolean;
          responseType?: 'json';
          withCredentials?: boolean;
        }
      ): Observable<T> {
        // tslint:disable-next-line:no-shadowed-variable
        return this.getPostData<T>(
          'patch',
          url,
          body,
          options,
          // tslint:disable-next-line:no-shadowed-variable
          (
            _method: string,
            url: string,
            body: any,
            options: any
          ): Observable<any> => {
            return this.httpClient.patch<T>(url, body, options);
          }
        );
      }
    
      /**
       * Performs a request with `head` http method.
       */
      head<T>(
        url: string,
        options?: {
          headers?:
            | HttpHeaders
            | {
                [header: string]: string | string[];
              };
          observe?: 'response';
          params?:
            | HttpParams
            | {
                [param: string]: string | string[];
              };
          reportProgress?: boolean;
          responseType?: 'json';
          withCredentials?: boolean;
        }
      ): Observable<T> {
        // tslint:disable-next-line:no-shadowed-variable
        return this.getData<T>(
          'head',
          url,
          options,
          (_method: string, url: string, options: any) => {
            return this.httpClient.head<T>(url, options);
          }
        );
      }
    
      /**
       * Performs a request with `options` http method.
       */
      options<T>(
        url: string,
        options?: {
          headers?:
            | HttpHeaders
            | {
                [header: string]: string | string[];
              };
          observe?: 'response';
          params?:
            | HttpParams
            | {
                [param: string]: string | string[];
              };
          reportProgress?: boolean;
          responseType?: 'json';
          withCredentials?: boolean;
        }
      ): Observable<T> {
        // tslint:disable-next-line:no-shadowed-variable
        return this.getData<T>(
          'options',
          url,
          options,
          // tslint:disable-next-line:no-shadowed-variable
          (_method: string, url: string, options: any) => {
            return this.httpClient.options<T>(url, options);
          }
        );
      }
    
      // tslint:disable-next-line:max-line-length
      getData<T>(
        method: string,
        uri: string | Request,
        options: any,
        callback: (
          method: string,
          uri: string | Request,
          options: any
        ) => Observable<any>
      ): Observable<T> {
        let url = uri;
    
        if (typeof uri !== 'string') {
          url = uri.url;
        }
    
        const tempKey = url + (options ? JSON.stringify(options) : '');
        const key = makeStateKey<T>(tempKey);
        try {
          return this.resolveData<T>(key);
        } catch (e) {
          //console.log('in catch', key);
          return callback(method, uri, options).pipe(
            tap((data: T) => {
              if (isPlatformBrowser(this.platformId)) {
                // Client only code.
                // nothing;
              }
              if (isPlatformServer(this.platformId)) {
                //console.log('set cache', key);
                this.setCache<T>(key, data);
              }
            })
          );
        }
      }
    
      private getPostData<T>(
        _method: string,
        uri: string | Request,
        body: any,
        options: any,
        callback: (
          method: string,
          uri: string | Request,
          body: any,
          options: any
        ) => Observable<any>
      ): Observable<T> {
        let url = uri;
    
        if (typeof uri !== 'string') {
          url = uri.url;
        }
    
        const tempKey =
          url +
          (body ? JSON.stringify(body) : '') +
          (options ? JSON.stringify(options) : '');
        const key = makeStateKey<T>(tempKey);
    
        try {
          return this.resolveData<T>(key);
        } catch (e) {
          return callback(_method, uri, body, options).pipe(
            tap((data: T) => {
              if (isPlatformBrowser(this.platformId)) {
                // Client only code.
                // nothing;
              }
              if (isPlatformServer(this.platformId)) {
                this.setCache<T>(key, data);
              }
            })
          );
        }
      }
    
      private resolveData<T>(key: StateKey<T>): Observable<T> {
        const data = this.getFromCache<T>(key);
    
        if (!data) {
          throw new Error();
        }
    
        if (isPlatformBrowser(this.platformId)) {
          //console.log('get cache', key);
          // Client only code.
          this.transferState.remove(key);
        }
        if (isPlatformServer(this.platformId)) {
          //console.log('we are the server');
          // Server only code.
        }
    
        return from(Promise.resolve<T>(data));
      }
    
      private setCache<T>(key: StateKey<T>, data: T): void {
        return this.transferState.set<T>(key, data);
      }
    
      private getFromCache<T>(key: StateKey<T>): T {
        return this.transferState.get<T>(key, null);
      }
    }
    

    然后我替换了任何看起来像这样的构造函数:

    constructor(private http: HttpClient) {}
    

    对此:

    constructor(private http: TransferHttpService) {}
    

    然后一切正常运转