Calling Javascript From Blazor -Part 5 : Extracting our component to Razor Class Library

...

Over the course of this series we have looked at the basics of Javascript interop, Importing a Javascript module with Blazors Javascript Isolation, Extracting the code into a reusable component and in the last article how to utilise NPM to bring in 3rd party libraries and use Snowpack so we can bundle them to be used in our component.

Now we are going to take that reuse one stage further and put our component in a Razor Class library. 

We ended the last article with with a Blazor Web Assembly app that had our component in it.

Our first step is to Create a Razor Class Library. So Right click the solution and Add new Project.

Choose Razor Class Library

Name it:

 and then create it, don't tick the pages and views box:

 

Clear all the files except the _Imports.razor:

So we now have an empty class library and we want to move our component to it. You can either drag and drop or right click to Cut and then paste, but Move the following files/folders from our webassembly app to the razor class library.

  1. MyJavascriptComponent.razor (or whatever your component is called)
  2. The sourceJS folder - make sure it copies across the hidden node_modules folder, the src folder, package.json and the snowpack.config.js

You'll get errors in your component as it won't recognise the javascript interop until you add a using statement to your _Imports.razor file, which should now look like

 

@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop

 

Now we need to remove the snowpack generated files from our web assembly app. So delete the contents of the web assembly apps wwwroot/js folder.

Those files got generated as part of a build script in the .csproj, so we'll need to remove that and copy it to our razor class libraries .csproj instead.

They should now look like the following

Web Assembly App:

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
 
  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>
 
 
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.10" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.10" PrivateAssets="all" />
    <PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
  </ItemGroup>
 
 
</Project>

Razor Class Library:

<Project Sdk="Microsoft.NET.Sdk.Razor">
 
  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>
 
 
  
  <ItemGroup>
    <SupportedPlatform Include="browser" />
  </ItemGroup>
 
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.10" />
  </ItemGroup>
 
  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>
 
 
  <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
    <Exec  Command="npm run build"  WorkingDirectory="sourceJS" />
  </Target>
 
</Project>

If we build the solution now, we'll see that Snowpack generates the files in our component library.

We'll get warnings about it not recognising the element <MyJavascriptComponent>. We haven't added a reference to the component library yet. Lets do so. Right click the App project file, select Add > Project Reference and add a reference to our class library.

well need to add a using statement so it recognises the component. We have the control on more that one page, so we'll add it to the _Imports.razor

When we build now we should get no errors or warnings. So lets run the app.

We get an error. Checking the console:

 

we can see that it's failed to load our index.js file. The line it's complaining about is

var module = await JSRuntime.InvokeAsync<IJSObjectReference>(
				"import""/js/index.js");

This worked fine when it was in the Web Assembly App. But that is because it's the Web Assembly App that is actually doing the executing. Our component library, and any others, are loaded into the web assembly apps space. The contents of our class libraries wwwroot folder get placed in a special folder of the web assembly app so that we need to prepend some extra path details in the format of './_content/[COMPONENT_LIBRARY_NAME]'

So  as our Component library name is MyComponentLibrary the line above now becomes

var module = await JSRuntime.InvokeAsync<IJSObjectReference>(
				"import""./_content/MyComponentLibrary/js/index.js");

Now if you run the app it will pick up our index.js file and we'll get our component being displayed again. 

Our Blazor web assembly app just needs a reference to our Razor Class Library and then we can put our component on our page. We could share that library with others and they could use our component and not have to worry about Javascript at all. We've actually got more work to do, we need to do something similar for the css, and videojs requires some initialisation done in javascript to be able to handle multiple instances of our component. I'm not going to cover those. We've actually achieved what we set out to do and now have a way to wrap npm installed javascript files in resusable Blazor components without the client having to add script tags to index.html.

 

I hope this series has been useful.