ljbc1994 / BlazorIntersectionObserver

🔎 Intersection Observer API wrapper for Blazor applications
MIT License
62 stars 12 forks source link

Doesn't work with prerendering #27

Open Xeevis opened 3 years ago

Xeevis commented 3 years ago

When using ASP.NET Core hosted Blazor WebAssembly app it's possible to make use of server prerendering. With this technique first user request is responded to by server instantaneously with full page html. This is useful for SEO and fast content delivery while app is still downloading in the background. Since prerendering happens only on the server with no connection to the browser, it's invalid to call any JS Interop at that time.

- InvalidOperationException: JavaScript interop calls cannot be issued during server-side prerendering, 
- because the page has not yet loaded in the browser. Prerendered components must wrap any JavaScript 
- interop calls in conditional logic to ensure those interop calls are not attempted during prerendering.

- Blazor.IntersectionObserver.IntersectionObserverService..ctor(IJSRuntime jsRuntime) in IntersectionObserverService.cs
-    27. this.moduleTask = jsRuntime.InvokeAsync<IJSObjectReference>("import", this.scriptPath).AsTask();

Steps to reproduce the behavior:

https://docs.microsoft.com/en-us/aspnet/core/blazor/components/prerendering-and-integration?view=aspnetcore-5.0&pivots=webassembly

Expected behavior

Import JS module only when component is rendered in the browser.

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        if (module == null)
        {
            module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "...");            
        }
    }
}
ljbc1994 commented 3 years ago

Hey thanks for raising..

Is this coming from the IntersectionObserve component or a custom component?

We do this here:

https://github.com/ljbc1994/BlazorIntersectionObserver/blob/6c69abe42796f5f6875842bee5ee041b81090abc/src/Blazor.IntersectionObserver/IntersectionObserve.cs#L37

We also have it as a task for invocation:

https://github.com/ljbc1994/BlazorIntersectionObserver/blob/6c69abe42796f5f6875842bee5ee041b81090abc/src/Blazor.IntersectionObserver/IntersectionObserverService.cs#L27

Xeevis commented 3 years ago

Hello, I've looked at this more closely and in my case it's the IIntersectionObserverService, which can't be injected directly because of JsInterop call in constructor

@inject IIntersectionObserverService ObserverService

So what I did instead was to inject IServiceProvider and retrieve this service in OnAfterRender()

private IIntersectionObserverService observerService;

[Inject]
private IServiceProvider ServiceProvider { get; set; }

public IntersectionObserver Observer { get; internal set; }

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        this.observerService ??= this.ServiceProvider.GetRequiredService<IIntersectionObserverService>();
        this.Observer = await this.observerService.Observe(this.elementRef, (entries) =>
        {
            //(...)
        });        
    }
}

Perhaps it would be worth a mention in README?

ljbc1994 commented 3 years ago

Thanks for looking into this @Xeevis.

I'll spend some more time checking this out to see if there's anything I can do. If not, I'll add this to the readme.

Thanks!