angularfire: Route freezes with Resolver getting data from Firestore in SSR

Version info

Angular: 11.0.2

Firebase: 8.1.1

AngularFire: 6.1.2

Other (e.g. Ionic/Cordova, Node, browser, operating system): Chrome 87, Windows 10, Angular Universal, Node 12.14.1

How to reproduce these conditions

  1. Create Angular Universal app with Firebase
  2. Add Resolver that fetches data from Firestore to a lazy route
  3. Try navigating to that route in SSR
    resolve({params}: ActivatedRouteSnapshot): Observable<any> {
        console.log('Resolver');

        return this.firestore
            .doc(params['id'])
            .get()
            .pipe(
                tap(() => console.log('Resolved')),
                catchError(() => this.router.navigate(['']) && EMPTY),
            );
    }

I also added console.log('Constructor') in that route’s component constructor.

Debug output

Console logs print out correctly:

Resolver
Resolved
Constructor

Expected behavior

Route opens with resolved data

Actual behavior

Route is stuck on loading, server does not return the page

Here comes the weird part

There’s a very strange way to kick-start it 😃

  1. Replace doc with collection in Resolver
  2. Add some other request to Firestore in route’s component (doesn’t matter doc or collection)
  3. Use async pipe in template to show that new request’s result

I’m very confused.

It’s hard to provide a reproduction, because it’s SSR. I’ll be happy to show it in my app if somebody is willing to live chat, it’s pretty straightforward.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 9
  • Comments: 15 (3 by maintainers)

Most upvoted comments

@Marcusg62, I was able to make your app serve the route from SSR - the issue is that you have several subscriptions and an angularfire auth subscription in your orderForm service, which is injected in your component. The auth subscription will never complete, and the other subscriptions don’t either.

Instead of using the behavior subject in your service, refactor it so that there are no subscriptions in it, and pass your Restaurant to your initializeOrderObject() method. Only init the formGroup once and then use patchValue() on it in your initializeOrderObject method. Services have no lifecycle hooks so you can’t control subscriptions.

Here’s how you can make it serve the restaurant route (you will need to later inject the service again after you remove all the subscriptions in it):

component:

@Component({
  selector: 'app-restaurant',
  templateUrl: './restaurant.component.html',
  styleUrls: ['./restaurant.component.scss']
})
export class RestaurantComponent implements OnInit {
  restaurant: Observable<Restaurant>;

  constructor(
    private route: ActivatedRoute,
    public afs: AngularFirestore
  ) {}

  ngOnInit(): void {
    const path = this.route.snapshot.url[1].path;
    this.restaurant = this.afs.doc<Restaurant>('restaurants/' + path).valueChanges();
  }

}

template:

<div class="bg" *ngIf="(r | async) as r2">
  <!-- this just shows raw data, but it proves you can server-side render the route -->
  {{ r2 | json }}
</div>

I am having the same issue. I have tried using promises as a workaround but no luck. Here is a link to my github repo with the bug. The route resolver is in src/app/resolvers/restaurant.resolver.ts

The problem is with ssr, so just npm i and npm run dev:ssr Then copy paste this to your browser bar localhost:4200/restaurant/bistroKing

The behavior is exactly as @waterplea describes, you go to the specific route (to be sure it uses SSR), and the browser just spins with no timeout. I don’t know why, but sometimes if you click into the bar again and hit enter while it was previously loading, it will then work without problem. See video:

https://user-images.githubusercontent.com/21998115/103108236-300df780-4603-11eb-920f-a11b83d2ab49.mov

@jamesdaniels what is compat APIs? Is there some place I can read about it? None of the workarounds mentioned are stable. Some of them work on ng serve but stop working once prod build is deployed, some work for doc but not for collection. And even then, I think, I had exactly the same workaround in two places in my app and one worked while another didn’t. Thankfully I really needed the one that worked and I just ditched the second one.

@waterplea I tried running your app, was able to build and serve ssr, but there is a firestore permission issue:

[2021-01-05T16:24:54.497Z]  @firebase/firestore: Firestore (8.1.1): Could not reach Cloud Firestore backend. Connection failed 1 times. Most recent error: FirebaseError: [code=permission-denied]: Permission denied on resource project fir-universal-be375.

Also, there is no firebase ssr/universal function to serve the app. How are you hosting this?

I think the free demo ran out. Looks like a new firebase has to be created. And ssr function is created by default builder during ng deploy.

@inorganik I can’t believe I didn’t realize that the other subscriptions are the culprit. Thank so much!

More news. Looks like my hack doesn’t work once you build and deploy to Firebase 😦 EDIT: False alarm, hack still works, my issue was different.

I have found one other hack - run Resolver outside of Angular with NgZone. It all seems to work fine for doc, doesn’t help with collection. Both SSR and CSR work. Probably not a good idea for some reason, but I’ll have to stick with it until the issue is fixed.