How to render Firestore data in server instead of client?

57 views Asked by At

My goal is to render data in server.

The expected result: the data is rendered by the server.

The actual result: the data is rendered by the client.

The pattern Component -> call Use Case -> call Data Source

Component

@Component({
  selector: 'app-experience',
  templateUrl: './experience.component.html',
  styleUrls: ['./experience.component.scss']
})
export class ExperienceComponent implements OnInit {
  isBrowser: boolean;

  slug: string | null;

  experience!: ExperienceEntity | null;

  subscriptions: Subscription = new Subscription();

  constructor(
    @Inject(PLATFORM_ID) platformID: string,
    private route: ActivatedRoute,
    private getExperienceBySlugUseCase: GetExperienceBySlugUseCase,
  ) {
    this.isBrowser = isPlatformBrowser(platformID);
    this.slug = this.route.snapshot.paramMap.get('experience');

    this.getExperienceBySlug();
  }

  ngOnInit(): void {
  }

  getExperienceBySlug() {
    if (this.slug == null) { return }

    let subscription = this.getExperienceBySlugUseCase
      .get(this.slug)
      .subscribe(value => this.experience = value)

    this.subscriptions.add(subscription)
  }

  ngOnDestroy(): void {
    this.subscriptions.unsubscribe();
  }
}

Use Case

@Injectable()
export class GetExperienceBySlugUseCase {

  constructor(private getExperienceBySlugDataSource: GetExperienceBySlugDataSource) { }

  get(slug: string): Observable<ExperienceEntity | null> {
    const aObservable = this.getExperienceBySlugDataSource.get(slug);

    return aObservable.pipe(
      map(val => {
        if (val.length == 0) { return null }

        return {
          title: val[0]['experienceTitle'],
          type: val[0]['experienceType'],
          category: val[0]['experienceCategory'],
          location: {
            city: val[0]['location']['city'],
            country: val[0]['location']['country'],
          },
          coverPhoto: {
            url: val[0]['coverPhoto']
          }
        } as ExperienceEntity
      })
    )
  }
}

Data Source

@Injectable()
export class GetExperienceBySlugDataSource {
  firestore: Firestore = inject(Firestore);

  constructor() { }

  get(slug: string) {
    const aQuery = query(
      collection(this.firestore, "experiences"),
      where("slug", "==", slug)
    )

    return collectionData(aQuery);
  }
}
1

There are 1 answers

0
Radowan Mahmud Redoy On

Angular universal usually makes call from both the server and client. As you are subscribing to your data source from the constructor; it makes a call from the server at the time of rendering. Now, when the rendered page reaches the browser the angular hooks kick in so the calls are again made from the browser, and it appears that the call is only being made from the browser.

A potential fix is to use TransferState API. In the server part you will make the call. Once the data is fetched, make sure to set them in transfer state using a key, once you reach the browser use the key to obtain the data from transfer state, stopping angular from making fetch calls to your API, in this case firestore.

You can implement this transfer state API in an interceptor or even in your service level codes.

This is a similar question already answered in stackoverflow

If you are using latest version of Angular, you can also use Hydration. You can have a look at their official documentation.