0

I am working on an Angular Blog app, I have implemented Server Side Rendering in my blog application. Problem I have encountered is, on my blog home page I can see all the blogs with its Source Code content by Clicking Ctrl + u that is coming from Node.js API, after clicking on selected blog it will redirect to that blog and show its content but not the HTML Source Code (which is stored in the Local Storage for reducing API calls, and I fetch it from Local Storage to show the selected Blog content).

I also read that SSR doesn't support window,document and location objects, and Local Storage is a window property so for using that i have follow this link https://stackoverflow.com/a/57781883.

After following all the tutorials and docs, I have tried every possible way but didn't worked for me. Help will be appreciated. Thanks in advance.

Below is my code attached

Home Component.html

<owl-carousel-o [options]="bannerSlider">
            <ng-template carouselSlide>
                <div class="sliderblogbox">
                    <img class="penci-image-holder" src="{{recentPost[0]?.featured_image}}">
                    <div class="sliderblogbox-text">
                        <h3><a (click)="showDetails(recentPost[0])">{{recentPost[0]?.title}}</a></h3> <span
                            class="daytime">{{recentPost[0]?.post_date * 1000 | date:'mediumDate'}}</span>
                    </div>
                </div>
            </ng-template>
            <ng-template carouselSlide>
                <div class="sliderblogbox">
                    <img class=" penci-image-holder" src={{recentPost[1]?.featured_image}}>
                    <div class="sliderblogbox-text">
                        <h3><a (click)="showDetails(recentPost[1])">{{recentPost[1]?.title}}</a></h3> <span
                            class="daytime">{{recentPost[1]?.post_date * 1000 | date:'mediumDate'}}</span>
                    </div>
                </div>
            </ng-template>
            <ng-template carouselSlide>
                <div class="sliderblogbox">
                    <img class=" penci-image-holder" src={{recentPost[0]?.featured_image}}>
                    <div class="sliderblogbox-text">
                        <h3><a (click)="showDetails(recentPost[0])">{{recentPost[0]?.title}}</a></h3> <span
                            class="daytime">{{recentPost[0]?.post_date * 1000 | date:'mediumDate'}}</span>
                    </div>
                </div>
            </ng-template>
            <ng-template carouselSlide>
                <div class="sliderblogbox">
                    <img class=" penci-image-holder" src={{recentPost[1]?.featured_image}}>
                    <div class="sliderblogbox-text">
                        <h3><a (click)="showDetails(recentPost[1])">{{recentPost[1]?.title}}</a></h3> <span
                            class="daytime">{{recentPost[1]?.post_date * 1000 | date:'mediumDate'}}</span>
                    </div>
                </div>
            </ng-template>
  </owl-carousel-o>

Home Component.ts

bannerSlider: OwlOptions = {
    loop: false,
    navText: [''],
    nav: true,
    margin: 10,
    dots: false,
    responsive: {
      0: {
        items: 1
      },
      600: {
        items: 2
      },
      1000: {
        items: 2
      }
    }
  };
  relatedPost: OwlOptions = {
    loop: true,
    navText: [''],
    nav: true,
    margin: 15,
    dots: false,
    responsive: {
      0: {
        items: 1
      },
      600: {
        items: 2
      },
      1000: {
        items: 3
      }
    }
  };

  constructor(private _service: ServiceService, private router: Router) { }
  recentPost = [];
  ngOnInit(): void {
    this.getRecentPost()
  }
  getRecentPost() {
    let key = 'slider';
    this._service.getRecentPostSlider(key).subscribe(res => {
      if (!res.error) {
        this.recentPost = res.data.data;
      }
    })

  }

  showDetails(post) {
    localStorage.setItem('post', JSON.stringify(post));
    this.router.navigate([`/${post.title_abbr}/`]);
  }

BlogPost component.html

<div class="largeblog-area">
                        <div class="largeblog-content blogdetailtop">
                            <h6><a href=""> {{post?.category}} </a> </h6>
                            <h2>{{post?.title}}</h2>
                            <h5><span>written by {{post?.author}} </span> |
                                <span>{{post?.post_date * 1000 | date:'mediumDate'}}</span> </h5>
                        </div>


                        <div class="largeblogimg"><img alt="img" src="{{post?.featured_image}}"></div>
                        <div class="largeblog-content">
                            <p [innerHTML]="post?.description"></p>

                            <div class="post-tags">
                                <a href="#">fresh</a>
                                <a href="#">life</a>
                                <a href="#">style</a>
                                <a href="#">travel</a>
                            </div>


                        </div>

                        <div class="largeblog-bottombox">
                            <div class="largeblog-date">
                                <span><i class="fa fa-clock-o" aria-hidden="true"></i> July 5, 2017</span>
                            </div>
                            <div class="largeblog-social">
                                <ul>
                                    <li><a href="" title="Like"><i class="fa fa-heart-o" aria-hidden="true"></i></a>
                                    </li>
                                    <li><a href="" title="Facebook"><i class="fa fa-facebook"
                                                aria-hidden="true"></i></a> </li>
                                    <li><a href="" title="Twitter"><i class="fa fa-twitter" aria-hidden="true"></i></a>
                                    </li>
                                    <li><a href="" title="Pinterest"><i class="fa fa-pinterest"
                                                aria-hidden="true"></i></a> </li>
                                </ul>
                            </div>


                        </div>
 </div>

BlogPost Component.ts

post: any;

  constructor(private meta: Meta, private router: Router, private titleService: Title,
    private globle: Globals, private _interaction: CommunicationService, private route: ActivatedRoute) {
    this.post = JSON.parse(localStorage.getItem('post'));
    if (this.post && this.post.meta_description !== '') {
      this.meta.addTag({ name: 'title:', content: this.post.meta_description });
    }
  }
  ngOnInit(): void {
    console.log(this.post);

    this.router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        this.post = JSON.parse(localStorage.getItem('post'));
      }
    });
  }

2 Answers2

0

SSR will show the view source only if your express server isn't crashing. SSR doesn't support window and document object, which is available in the browser.

If you're using service worker for caching, even then it won't show the view source because it'll be serving from service worker.

So you've to check in 2 places, if it's getting served from service worker or not, then check whether your server is showing the generated HTML in terminal.

  • My HTML source code gets displayed now when I use service to fetch the unique selected blog in that component but while using local storage It was not working. I was using local storage due to following reasons: 1. to reduce API calls 2. I want my abbrevated title in my route. – Amit Khatri Sep 10 '20 at 10:23
  • Localstorage solution won't work with SSR, you've to come up with a different strategy. You can embed data in your URL instead – Rahul Sachdeva Sep 10 '20 at 10:27
  • If you can provide some example links of the strategy that you are suggesting that would be very helpful. – Amit Khatri Sep 10 '20 at 10:35
  • I won't be able to share an example a this a problem to your use case. what you can do is creating a fallback to your service which is fetching blog details. check first whether local storage is available if it's then you can use that else use the service – Rahul Sachdeva Sep 10 '20 at 10:40
0

Use routeReuseStrategy to get the data on view source after navigate or use resolvers to view the updated content on view source

Resolver

A data provider class can be used with the router to resolve data during navigation. The interface defines a resolve() method that is invoked right after the ResolveStart router event. The router waits for the data to be resolved before the route is finally activated.

How it works

Create a resolver within your component / architecture based. In your General-Landing-Component.ts subscribe to the resolver function and invoke the API calls to view page on SSR.

General-Landing-Resolver.ts

import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { ActivatedRouteSnapshot,  Resolve,  RouterStateSnapshot,  Router,  ActivatedRoute,} from '@angular/router';

import { Observable, of } from 'rxjs';
import { ContentfulService } from '../../contentful/services/contentful.service';
import { ContentfulResponse } from '../../contentful/interfaces/contentful-response';
import { map } from 'rxjs/operators';
import { isPlatformBrowser } from '@angular/common';
@Injectable({
  providedIn: 'root',
})
export class generalLandingResolver implements Resolve<ContentfulResponse> {
  urlPath: any;
  responseData: any;
  private isBrowser: boolean = false;
  constructor(
    private contentfulService: ContentfulService,
    private router: Router,
    private activatedRoute: ActivatedRoute,
    @Inject(PLATFORM_ID) private platformId: Object
  ) {
    this.isBrowser = isPlatformBrowser(platformId);
  }
  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<any> {
    const tree = this.router.parseUrl(state.url);
    const children = tree.root.children.primary;
    const segments = children.segments[1].path;
    this.urlPath = segments;

    return this.contentfulService.generalLandingPageBySlug(this.urlPath).pipe(
      map((response) => {
        if (this.isBrowser) {
          sessionStorage.setItem('response', response);
        }
      })
    );
  }
}

General-Landing-Component.ts

import { Component, OnInit, Inject, PLATFORM_ID } from '@angular/core';
import { Router } from '@angular/router';
import { combineLatest, EMPTY, Observable } from 'rxjs';
import { ContentfulResponse } from '../../contentful/interfaces/contentful-response';
import { ContentfulService } from '../../contentful/services/contentful.service';
import { DOCUMENT, isPlatformBrowser } from '@angular/common';

@Component({
  selector: 'app-general-landing-page',
  templateUrl: './general-landing-page.component.html',
  styleUrls: ['./general-landing-page.component.scss'],
})
export class GeneralLandingPageComponent implements OnInit {
  contentfulResponse$!: Observable<ContentfulResponse>;
  componentschoosen: any;
  componentsResults: any;
  urlPath: any;
  siteURL: any;
  siteURLpath: any;
  private isBrowser: boolean = false;

  constructor(
    private contentfulService: ContentfulService,
    private router: Router,
    @Inject(DOCUMENT) private dom: Document,
    @Inject(PLATFORM_ID) private platformId: Object
  ) {
    this.isBrowser = isPlatformBrowser(platformId);
  }

  ngOnInit(): void {
    this.siteURL = new URL(this.dom.URL);
    this.siteURLpath = this.siteURL?.pathname;
    this.urlPath = this.siteURLpath.split('/')[2];

    this.contentfulService
      .generalLandingPageBySlug(this.urlPath)
      .subscribe((_) => {
        if (this.contentfulService.generalLandingResponse == null) {
          this.contentfulService.generalLandingResponse = sessionStorage.getItem(
            'response'
          );
        }

        if (
          this.contentfulService.generalLandingResponse.includes(
            '__typename ...'
          )
        ) {
          this.contentfulResponse$ = this.contentfulService.generalLandingPageProcessedData(
            this.contentfulService.generalLandingResponse,
            this.urlPath
          );
        }
      });

    this.router.routeReuseStrategy.shouldReuseRoute = function () {
      return false;
    };
  }

  ngAfterContentChecked(): void {
    if (this.isBrowser) {
      sessionStorage.removeItem('response');
    }
  }
}

Import the resolver file

General-Landing-Component.module.ts

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CoreModule } from '../../core/core.module';
import { GeneralLandingPageComponent } from './general-landing-page.component';
import { generalLandingResolver } from './general-landing-page.resolver';

const routes: Routes = [
  {
    path: '',
    component: GeneralLandingPageComponent,
    resolve: { generalContent: generalLandingResolver },
  },
];

@NgModule({
  declarations: [GeneralLandingPageComponent],
  imports: [CommonModule, CoreModule, [RouterModule.forChild(routes)]],
})
export class GeneralLandingPageModule {}

General-Landing-Component.html

<ng-container *ngIf="contentfulResponse$ | async as data">
  <ng-container *ngFor="let component of data?.components">
    <app-contentful
      [template]="component.pageData.__typename"
      [component]="component.pageData"
    ></app-contentful>
  </ng-container>
</ng-container>
Surya R Praveen
  • 3,393
  • 1
  • 24
  • 25