简体   繁体   English

Blazor OnAfterRenderAsync Javascript

[英]Blazor OnAfterRenderAsync Javascript

So I've been playing around with Blazor WebAssembly and I can't figure out how to solve this problem.所以我一直在玩 Blazor WebAssembly,但我不知道如何解决这个问题。 Basically, I have a NavMenu.razor page that will get an array of NavMenuItem asynchronously from a JSON file.基本上,我有一个 NavMenu.razor 页面,它将从 JSON 文件异步获取 NavMenuItem 数组。 That works fine.这很好用。 Now, what I'm trying to do is add an event listener to each of the anchors that are generated from the <NavLink> tags in the below foreach loop.现在,我想要做的是向从下面的 foreach 循环中的<NavLink>标记生成的每个锚点添加一个事件侦听器。

The NavLink outside of that loop (the one not populated by the async function) successfully has the addSmoothScrollingToNavLinks() function applied to it correctly.该循环之外的 NavLink(异步函数未填充的那个)成功地正确应用了 addSmoothScrollingToNavLinks() 函数。 The other NavLinks do not.其他 NavLink 没有。 It seems as if they are not yet in the DOM.似乎它们还没有在 DOM 中。

I'm guessing there's some weird race condition, but I don't know how to resolve it.我猜有一些奇怪的竞争条件,但我不知道如何解决它。 Any ideas on how I might fix this?关于我如何解决这个问题的任何想法?

NavMenu.razor导航菜单.razor

@inject HttpClient Http
@inject IJSRuntime jsRuntime

@if (navMenuItems == null)
{
    <div></div>
}
else
{
    @foreach (var navMenuItem in navMenuItems)
    {
        <NavLink class="nav-link" href="@navMenuItem.URL" Match="NavLinkMatch.All">
            <div class="d-flex flex-column align-items-center">
                <div>
                    <i class="@navMenuItem.CssClass fa-2x"></i>
                </div>
                <div class="mt-1">
                    <span>@navMenuItem.Text</span>
                </div>
            </div>
        </NavLink>
    }
}

<NavLink class="nav-link" href="#" Match="NavLinkMatch.All">
    <div class="d-flex flex-column align-items-center">
        This works!
    </div>
</NavLink>

@code {
    private NavMenuItem[] navMenuItems;

    protected override async Task OnInitializedAsync()
    {
        navMenuItems = await Http.GetJsonAsync<NavMenuItem[]>("sample-data/navmenuitems.json");
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await jsRuntime.InvokeVoidAsync("MyFunctions.addSmoothScrollingToNavLinks");
        }
    }

    public class NavMenuItem
    {
        public string Text { get; set; }

        public string URL { get; set; }

        public string CssClass { get; set; }
    }
}

index.html (underneath the webassembly.js script tag) index.html (在 webassembly.js 脚本标签下)

 <script>
        function scrollToTop() {
            window.scroll({
                behavior: 'smooth',
                left: 0,
                top: 0
            });
        }

        window.MyFunctions = {
            addSmoothScrollingToNavLinks: function () {
                let links = document.querySelectorAll("a.nav-link");
                console.log(links);
                // links only has 1 item in it here. The one not generated from the async method
                for (const link of links) {
                    link.addEventListener('click', function (event) {
                        event.preventDefault();
                        scrollToTop();
                    })
                }
            }
        }
    </script>

That's because Blazor will NOT wait for the OnInitializedAsync() to complete and will start rendering the view once the OnInitializedAsync has started.这是因为 Blazor不会等待OnInitializedAsync()完成,并且会在OnInitializedAsync启动后开始呈现视图。 See source code on GitHub : 在 GitHub 上查看源代码

private async Task RunInitAndSetParametersAsync()
{
    OnInitialized();
    var task = OnInitializedAsync();       // NO await here!

    if (task.Status != TaskStatus.RanToCompletion && task.Status != TaskStatus.Canceled)
    {
        // Call state has changed here so that we render after the sync part of OnInitAsync has run
        // and wait for it to finish before we continue. If no async work has been done yet, we want
        // to defer calling StateHasChanged up until the first bit of async code happens or until
        // the end. Additionally, we want to avoid calling StateHasChanged if no
        // async work is to be performed.
        StateHasChanged();                  // Notify here! (happens before await)

        try
        {
            await task;                     // await the OnInitializedAsync to complete!
        }
        ...

As you see, the StateHasChanged() might start before OnInitializedAsync() is completed.如您所见, StateHasChanged()可能会在OnInitializedAsync()完成之前启动。 Since you send a HTTP request within the OnInitializedAsync() method, , the component renders before you get the response from the sample-data/navmenuitems.json endpoint.由于您在OnInitializedAsync()方法中发送 HTTP 请求,因此组件会在您从sample-data/navmenuitems.json端点获得响应之前呈现。

How to fix怎么修

You could bind event in Blazor(instead of js), and trigger the handlers written by JavaScript.您可以在 Blazor(而不是 js)中绑定事件,并触发由 JavaScript 编写的处理程序。 For example, if you're using ASP.NET Core 3.1.x (won't work for 3.0, see PR#14509 ):例如,如果您使用的是 ASP.NET Core 3.1.x(不适用于 3.0,请参阅PR#14509 ):

@foreach (var navMenuItem in navMenuItems)
{
<a class="nav-link" href="@navMenuItem.URL" @onclick="OnClickNavLink" @onclick:preventDefault>
    <div class="d-flex flex-column align-items-center">
        <div>
            <i class="@navMenuItem.CssClass fa-2x"></i>
        </div>
        <div class="mt-1">
            <span>@navMenuItem.Text</span>
        </div>
    </div>
</a>
}

@code{
    ...


    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        //if (firstRender)
        //{
        //    await jsRuntime.InvokeVoidAsync("MyFunctions.addSmoothScrollingToNavLinks");
        //}
    }
    private async Task OnClickNavLink(MouseEventArgs e)
    {
        Console.WriteLine("click link!");
        await jsRuntime.InvokeVoidAsync("MyFunctions.smoothScrolling");
    }

}

where the MyFunctions.addSmoothScrollingToNavLinks is:其中MyFunctions.addSmoothScrollingToNavLinks是:

smoothScrolling:function(){
    scrollToTop();
}

I am also trying to get this pattern right, call some async services in OnInitializedAsync and after that, calling some javascript on the OnAfterRenderAsync .我也在尝试正确使用此模式,在OnInitializedAsync调用一些异步服务,然后在OnAfterRenderAsync上调用一些 javascript。 The thing is that the javascript call is there to execute some js that depends on the data returnes by the services (the data must be there to the desired behavior take effect).问题是 javascript 调用在那里执行一些依赖于服务返回的数据的 js(数据必须在那里才能使所需的行为生效)。

What is happening is that the js call is running before the data arrive, is there a way to implement this "dependency" between data from OnInitializedAsync and OnAfterRenderAsync ?发生的事情是在数据到达之前 js 调用正在运行,有没有办法在来自OnInitializedAsyncOnAfterRenderAsync数据之间实现这种“依赖关系”?

Not perfect but works for me, lets you wait for the download不完美但对我有用,让你等待下载

private bool load = false;
protected override async Task OnInitializedAsync()
{
    navMenuItems = await Http.GetJsonAsync<NavMenuItem[]>("sample-data/navmenuitems.json");
    load = true;
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (load)
    {
        await jsRuntime.InvokeVoidAsync("MyFunctions.addSmoothScrollingToNavLinks");
        load = false;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM