angularangular-universalangular-ssr

Is there a way to tell Firestore to fetch and wait, so Angular SSR will render the response data in the server instead of in the client?


my goal is to get data from the Firestore and render the response data in the server.

The problem is, Firestore somehow manage to tell the component not to wait and just send the HTML to the client.

If I add setTimeout(() => {}, 1000) in the ngOnInit, the response data will be rendered in the server. But, obviously, this is a hack and not guaranteed.

I am using Angular 17.

@Component({
  selector: 'app-experience',
  standalone: true,
  imports: [
    NgIf,
    AsyncPipe,
    RouterLink,
  ],
  templateUrl: './experience.page.html',
  styleUrl: './experience.page.scss'
})
export class ExperiencePage implements OnInit {
  documentId: Signal<string | null>;
  
  slug: Signal<string | null>;

  experience: Promise<Experience | null> = Promise.resolve(null);

  constructor(
    route: ActivatedRoute,
    getExperience: GetExperienceUseCaseService,
    private firestore: Firestore,
  ) {
    const queryParamMap = route.queryParamMap;

    // RxJs Interop
    // see https://angular.io/guide/rxjs-interop
    this.slug = toSignal(
      queryParamMap.pipe(map((params) => params.get("slug"))),
      { initialValue: null }
    );
    
    this.documentId = toSignal(
      queryParamMap.pipe(map((params) => params.get("documentId"))),
      { initialValue: null }
    );

    const collectionReference = collection(this.firestore, 'experiences');
    const documentReference = doc(collectionReference, "CKfKFpCimmBxZpUJktud");
    const documentSnapshot = getDoc(documentReference);

    documentSnapshot.then((snapshot) => {
      if (!snapshot.exists()) { return; }

      const data = snapshot.data();

      this.experience = Promise.resolve({
        title: data["experienceTitle"],
        type: data["experienceType"],
        category: data["experienceCategory"],
        rating: data["rating"],
      });
    })
  }

  ngOnInit() {
    setTimeout(() => {
      console.log("wait");
    }, 1000)
  }
}

Solution

  • the problem is in getDoc. It tell JavaScript to not wait and send the rendered HTML immediately.

    The solution: Adding setTimeout fixed it. However, now you have a fixed time out, it will be a problem. What you can do is to use waitUntil. It will immediately clear the time out after getDoc return a value.

    export async function waitUntil<T>(callback: () => Promise<T>, ms: number): Promise<T> {
      let timeoutId = setTimeout(() => { }, ms);
    
      try {
        return await callback();
      } finally {
        clearTimeout(timeoutId);
      }
    }
    

    use it like this

    await waitUntil(() => getDoc(documentReference), 1000);