BlazorJSComponents 1.0.0
dotnet add package BlazorJSComponents --version 1.0.0
NuGet\Install-Package BlazorJSComponents -Version 1.0.0
<PackageReference Include="BlazorJSComponents" Version="1.0.0" />
paket add BlazorJSComponents --version 1.0.0
#r "nuget: BlazorJSComponents, 1.0.0"
// Install BlazorJSComponents as a Cake Addin #addin nuget:?package=BlazorJSComponents&version=1.0.0 // Install BlazorJSComponents as a Cake Tool #tool nuget:?package=BlazorJSComponents&version=1.0.0
BlazorJSComponents
This library enables the use of:
- Any JavaScript file
- In any Blazor component
- Using any render mode (including static server rendering!)
Here are some of the main features:
- Fetching/importing JavaScript dynamically
- Optionally inferring the script path from a collocated JavaScript file
- Defining "JS components"
Getting started
Start by installing the package from NuGet:
dotnet add package BlazorJSComponents --prerelease
Next, in your Blazor app's Program.cs
, add the required services:
builder.Services.AddJSComponents();
Then, in your _Imports.razor
file, add a using
for BlazorJSComponents
:
@using BlazorJSComponents
If you haven't already, add some JavaScript to your app to be dynamically loaded. For example, add this hello-world.js
script to your app's wwwroot
folder:
console.log('Hello, world!');
Finally, render a JS
component from the Blazor component requiring your script:
<JS Src="./hello-world.js" />
When the JS
component first renders, the referenced script will get dynamically loaded into the document, and "Hello, world!"
will be logged to the browser console!
[!NOTE] You might notice that
"Hello, world!"
only gets logged once per full page load. This is by design. To define JavaScript that runs in sync with Blazor's lifecycle methods, see JS Components.
Collocated JS discovery
[!NOTE] See the official Blazor docs for information about JS collocation.
When a component has a collocated JS file, it can be discovered automatically without having to hard-code a value for the Src
parameter. To enable this:
- Add the
[DiscoverCollocatedJS]
attribute in the component's.razor
file - Replace the
Src
parameter with theFor
parameter, passingthis
as the argument
For example:
@attribute [DiscoverCollocatedJS]
<JS For="this" />
[!IMPORTANT] The
[DiscoverCollocatedJS]
attribute must be placed in the.razor
file associated with the type of the component passed as theFor
parameter. The[DiscoverCollocatedJS]
attribute cannot be placed in an_Imports.razor
file, and it cannot be placed in a code-behind (.razor.cs
) file.
JS Components
In addition to dynamically loading JavaScript, BlazorJSComponents
allows for writing "JS components", which are like Blazor components with a JavaScript implementation. JS components enable the following functionality:
- Implementing Blazor lifecycle events in JavaScript
- Associating JavaScript state with a Blazor component
- Can be optionally persisted between enhanced navigations and in the transition to interactivity
- Managing JS event listeners on elements rendered from Blazor
- Passing arguments from .NET to JS, including:
- Element references
- Complex objects
- Invoking JS component methods from .NET
Anatomy of a JS component
Here's how a typical JS component might look. Note that all callbacks are optional, but JS components must extend the BlazorJSComponents.Component
base class.
// Module-level state can be declared here.
export default class extends BlazorJSComponents.Component {
attach(blazor) {
// Called when the JS component first gets added to the page.
// You can use this to initialize JS component state and
// call methods on the provided 'blazor' object.
// For example:
// blazor.addEventListener('enhancedload', ...);
}
setParameters(...args) {
// Called when the parent component re-renders.
// You can use this to add event listeners to rendered elements
// or modify content on the page.
}
dispose() {
// Called after the parent component gets disposed.
// You can use this to perform additional cleanup logic.
}
}
Basic usage example
Let's make an interactive counter! Start by adding a file called Counter.razor
(if it doesn't already exist), and give it the following content:
@page "/counter"
<button>Click me!</button>
<span>Current count: 0</span>
Now, let's define some JS logic to increment the count when the button gets clicked. Add a Counter.razor.js
file in the same directory as Counter.razor
:
export default class extends BlazorJSComponents.Component {
// Called when the component gets added to the page.
attach() {
this.currentCount = 0;
}
// Called when the parent component re-renders.
setParameters(incrementAmount, { button, label }) {
this.label = label;
this.setEventListener(button, 'click', () => {
this.currentCount += incrementAmount;
this.render();
});
this.render();
}
// A helper to apply changes to DOM content.
// Not called automatically.
render() {
this.label.innerText = `Current count: ${this.currentCount}`;
}
}
Here, we define a JS component that maintains its own state (the currentCount
), and updates the page to reflect it. It expects multiple arguments to be provided from .NET:
- The increment amount
- Rendered elements:
- The button to be clicked
- The label displaying the increment amount
[!NOTE] The above
setParameters()
implementation callsthis.setEventListener(button, ...)
instead ofbutton.addEventListener(...)
. ThesetEventListener()
method, which is defined in theBlazorJSComponents.Component
base class, has the following advantages:
- It ensures that the component only has one listener for a given element and event. Calling
setEventListener()
multiple times on same element/event combination overwrites the previous event listener.- Upon component disposal, the event listener is automatically removed.
Let's update the Blazor component to render the JS component and pass arguments to it:
@page "/counter"
@attribute [DiscoverCollocatedJS]
@inject JSElementReferenceScope Refs
<button data-ref="@Refs["button"]">Click me!</button>
<span data-ref="@Refs["label"]">Current count: 0</span>
<JS For="this" Args="[IncrementAmount, Refs]" />
@code {
const int IncrementAmount = 7;
}
Here are the notable changes:
- We inject a
JSElementReferenceScope
into the component to define element references scoped to the current component. This ensures that if there are multiple instances of our component at a time, each JS component receives element references for its correct parent Blazor component. - Element references are named via the
data-ref
attribute. We use the indexer on theJSElementReferenceScope
to specify the name of the property in JavaScript representing the element. - The
Args
parameter lets us specify the list of arguments to pass to the JS component. In this case, we pass theIncrementAmount
as the first argument, and theJSElementReferenceScope
as the 2nd argument.
Now we can run the app, navigate to the /counter
page, and increment the counter!
[!TIP] If you make the Blazor component interactive, you'll notice that the JS counter continues to work, even if the Blazor component re-renders. You can even add a .NET counter right next to the JS component, and they'll both work. Pretty nifty!
Advanced scenarios
This section contains some additional details relevant to advanced users.
Persisting JS component state
By default, JS components get reinitialized when their parent component statically re-renders or becomes interactive. To persist JS component state between enhanced page updates or in the transition to interactivity, specify a unique Key
parameter:
<JS For="this" Key="MyUniqueKey" />
Specifying a Key
allows the library to map statically-rendered JS components to existing JS components, even if the content on the page changes dramatically between renders.
Invoking JS component methods from .NET
In interactive scenarios, you can invoke methods on JS components. Here's an example of how this can be done:
@attribute [DiscoverCollocatedJS]
@rendermode InteractiveServer
<JS For="this" @ref="js" />
@code {
JS? js;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var result = await js!.InvokeVoidAsync<string>("someMethod", "someArg");
}
}
}
Alternative method for loading the Component
base type
By default, the library defines a global BlazorJSComponents.Component
type on the window
object so it can be accessed anywhere. An alternative is to import the Component
type directly:
import { Component } from '/_content/BlazorJSComponents/component.mjs';
export default class extends Component {
// ...
}
If you want to disable the default behavior that sets window.BlazorJSComponents.Component
, you can do so by providing startup options to Blazor.start()
:
<script src="_framework/blazor.web.js" autostart="false"></script>
<script>
Blazor.start({
jsComponents: {
disableGlobalProperties: true,
},
});
</script>
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. |
-
net9.0
- Microsoft.AspNetCore.Components.Web (>= 9.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.0.0 | 305 | 12/10/2024 |
0.1.0-preview.1 | 613 | 10/11/2024 |