Forcing Angular to Wait on Your Async Function
Tags: angular, rxjs, zonejs, async
Published: 2021-10-07 00:39:15

## Update 7/9/24
I highly recommend you just use a `resolver` to load your async data there; it is by far the easiest and best practice. However, [PendingTasks](https://dev.to/jdgamble555/forcing-angular-ssr-to-wait-in-2024-4lc9) is probably better for you in 2024 if you must do it in a component.
____
## Original Post
___

For SSR and node.js usage of Angular, we may need to have a Promise complete before the page is loaded. This is especially true when we need to create meta tags for SEO. Yes, our app is slower, but we have to get it indexable.

Every wonder why your meta tags seem to work sometimes, but not other times? It is because `ngOnInit` is NOT an async function, even with `async`, neither is a constructor which must return `this`, and neither is an `async pipe` in your template. Sometimes the fetches return on time, other times they don't. So, I added this post:

___

`ngOnInit` [does NOT wait](https://stackoverflow.com/a/39492163/271450) for the promise to complete. You can make it an async function if you feel like using await like so:

```typescript
import { take } from 'rxjs/operators';

async ngOnInit(): Promise<any> {
  const data = await this.service.getData().pipe(take(1)).toPromise();
  this.data = this.modifyMyData(data);
}
```
However, if you're using `ngOnInit` instead of the constructor to wait for a function to complete, you're basically doing the equivalent of this:

```typescript
import { take } from 'rxjs/operators';

constructor() {
  this.service.getData().pipe(take(1)).toPromise()
    .then((data => {;
      this.data = this.modifyMyData(data);
    });
}
```
It will run the async function, but it WILL NOT wait for it to complete. If you notice sometimes it completes and sometimes it doesn't, it really just depends on the timing of your function.

Using the ideas from [this post](https://stackoverflow.com/a/50065783/271450), you can basically run outside `zone.js`. `NgZone` does not include `scheduleMacroTask`, but `zone.js` is imported already into angular.

## Solution

```typescript
import { isObservable, Observable } from 'rxjs';
import { take } from 'rxjs/operators';

declare const Zone: any;

async waitFor<T>(prom: Promise<T> | Observable<T>): Promise<T> {
  if (isObservable(prom)) {
    prom = firstValueFrom(prom);
  }
  const macroTask = Zone.current
    .scheduleMacroTask(
      `WAITFOR-${Math.random()}`,
      () => { },
      {},
      () => { }
    );
  return prom.then((p: T) => {
    macroTask.invoke();
    return p;
  });
}
```

I personally put this function in my `core.module.ts`, although you can put it anywhere.

Use it like so:

```typescript
constructor(private cm: CoreModule) {
  const p = this.service.getData();
  this.post = this.cm.waitFor(p);
}
```

You could also check for [isBrowser](https://stackoverflow.com/a/46893433/271450) to keep your observable, or wait for results.

Conversely, you could also import `angular-zen` and use it like in [this post](https://dev.to/meldiron/appwrite-with-angular-ssr-ph), although you will be importing more than you need.

I believe this has been misunderstood for a while now, so I hope I am understanding this correctly now. 

I should also add you don’t always want to do this if you’re app loads in time without it. Basically you’re app is faster without it using simultaneous loading, but a lot of the time we have to have it. For seo, do html testing to make sure it loads as expected every time.

Let me know if it solves your problem.

Here is my [stackoverflow](https://stackoverflow.com/a/58474585/271450) post on this.

J