I want render a Blazor component from javascript. See https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-rc-1/ "Render Blazor components from JavaScript"
I have a HTML file:
<script src="/_framework/blazor.server.js"></script>
<div id="counter"></div>
<script>
async function ready() {
let containerElement = document.getElementById('counter');
await Blazor.rootComponents.add(containerElement, 'counter', { incrementAmount: 10 });
}
document.addEventListener("DOMContentLoaded", ready);
</script>
And do
builder.Services.AddServerSideBlazor(options =>
{
options.RootComponents.RegisterForJavaScript<Counter>("counter");
});
Error message (JavaScript):
test.html:14 Uncaught (in promise) Error: Dynamic root components have not been enabled in this application.
at E (blazor.server.js:1)
at Object.add (blazor.server.js:1)
at HTMLDocument.ready (test.html:8)
How can i enable dynamic root components?
The error is happening because of the delay between the document loading and Blazor being "ready" to process your request to add a component.
There doesn't appear to be any official documented solution for this, but something like this is possible
Change Blazor to manual start:
<script src="_framework/blazor.webassembly.js" autostart="false"></script>
<script src="js/script.js" defer></script>
Start Blazor, then try to add components - repeat until success
document.addEventListener("DOMContentLoaded", startBlazor)
function startBlazor() {
Blazor
.start()
.then(() => {
requestAnimationFrame(AddBlazorComponents)
})
.catch((error) => console.error(error));
}
let attempts = 0
const maxAttempts = 120 // arbitrary choice
function AddBlazorComponents() {
if (attempts > maxAttempts) {
// give up after trying too many times
console.error("Could not load Blazor components.")
return
}
const containerElement = document.querySelector('#app')
Blazor.rootComponents.add(containerElement, 'app', {})
.catch(reason => {
if (reason.message === "Dynamic root components have not been enabled in this application.")
requestAnimationFrame(AddBlazorComponents)
else
console.error(reason)
})
attempts++
}
You could, possibly, add a JSInterop call to your .NET code to facilitate this, after the application starts - but it would be different for the different hosting models : Blazor Server / Blazor WebAssembly.
Even better might be to make a PR to the aspnetcore repo with a method of pre-registering components for the framework to load when it is ready.
It turns out there is some currently undocumented functionality that helps here. I have copied my comment from @Swimburger 's github issue below:
The new JavaScript Initializer afterStarted is called (potentially) too soon for Dynamic components, but it turns out you can pass an initializer to be called for a dynamic component - but it is not well documented or intuitive imho.
To make this work, I changed the app startup like this (adding javaScriptInitializer: "loadApp") in program.cs:
builder.RootComponents.RegisterForJavaScript<App>(identifier: "app", javaScriptInitializer: "loadApp");
Then, and this is where it was non-intuitive, I added an export to my module (like afterStarted) called loadApp - but it wasn't called.
It turned out that loadApp is only found if I add it to the window object, so I added this to index.html
<script src="_framework/blazor.webassembly.js"></script>
<script>
window.loadApp = function (component,params) {
let containerElement = document.querySelector('#app')
window.Blazor.rootComponents.add(containerElement, component, params)
}
</script>
This feels like I am missing something in how to export my custom initializer OR it really does need to be added to window directly, which seems odd...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With