I've been researching the best way for running my Angular 2+ App in Visual Studio for a while now. Mainly because I want to use only one IDE, and -more importantly- I want to have only one set of shortcuts ;). I also want to be able to just press F5 to start up all projects I need to debug/develop my projects. I am not against the new cli hype (can I call it that?), but I also want things working together as best as possible. So finding ways for Angular, angular-cli and Visual Studio (ASP.NET Core) to play nice together.
This post is not meant to start a discussion on whether or not that is a good idea. Some of you love the angular-cli, and you're not wrong, it is pretty great. Others like the idea that by using another editor (mostly VSCode I imagine) you almost automatically decouple your frontend to your backend architecture. And maybe even get better TypeScript integration. You're not wrong either! What I want is simply a better integration with Visual Studio and angular 2+, which utilizes WebPack, and because of that is kind of hard to do. And this post describes how I realized exactly that.
The sample code template written for this post can be found here.
First things first
"Researching you say?" might be the first thing that comes to mind in some of the readers who've done some work in this area themselves. Because if you create a new .NET Core web application, it already comes with an angular option that you can run from within Visual Studio.
And well, that's true, you get some integration. This project template works with a WebPack middleware that you can configure to dynamically load your modules. And, basically it does work, but it is a pain to configure. And sometimes -a lot on my machine- the middleware goes completely haywire which forces you to restart your project or rebuild your vendor assets. It works, but in my opinion, not that well. This approach also makes you lose all angular-cli capabilities, which is not ideal.
So, I looked for other solutions and think I found a pretty nice one.
Microsoft's SPA Services
I used a couple of examples and projects to build my own template, including the samples in the ASP.NET Core 2.1 preview version. Much of my solution will probably be included in the ASP.NET Core 2.1 release by default.
In the .NET Core 'SpaServices' module there are ways for you to configure your ASP.NET Core application to work with all kinds of Single Page Applications. If you have a .NET Core application (2.0 or later) you can access this SDK by installing the Microsoft.AspNetCore.SpaServices.Extensions
nuget package.
I've created a template project available on github. To be clear, the application I wrote is designed to be a "dumb" frontend. It isn't even configured to handle MVC calls. It basically just serves your angular App, nothing more, nothing less. The way I like it! :)
Changes to the startup class
In the configure method you'll need to add a couple of things.
Firstly, you'll need to tell your App to serve the static files of angular.
app.UseSpaStaticFiles();
After that you'll need to add the following code:
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
This tells the ASP.NET Core runtime to handle all request from that point in the middleware chain by returning the default page for the configured SPA. If you want to use MVC as well, you have to put this middleware in the chain as late as possible. Using the options we can provide the source path of the angular-cli project, which in this case is 'ClientApp'.
Next you have to configure how .NET Core invokes the angular-cli server itself. Which you will only do in development mode (you never want Webpack to hot-swap modules when in production). spa.UseAngularCliServer(npmScript: "start");
tells your ASP.NET Core runtime how to start your angular-cli.
If you are not running in development mode, you have to specify where the Angular files will be served from. You can do that in the 'ConfigureServices' method:
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
Changing your csproject file
That's basically all you have to do in terms of C# code. The real magic happens in the csproject file. Because you have to treat builds differently. I'm not going to cover all changes I made because most of them speak for themselves. But I am going to explain key parts of this new build system. Take a look at the following code sample:
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --prod" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build:ssr -- --prod" Condition=" '$(BuildServerSideRenderer)' == 'true' " />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
<DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
What's basically going on is that with this configuration you simply tell .NET Core to execute npm install
when you build your project in debug mode. After that npm start
will be served as we configured before with the spa.UseAngularCliServer(npmScript: "start");
command.
If you publish this application, the ASP.NET Core runtime will basically utilize node and Angular to create a production version of your App.
So that's it! You can now just spin up a new angular-cli application in the 'ClientApp' folder and utilize both the power of ASP.NET Core and Angular with it's built in WebPack solution, right in Visual Studio. As I've said earlier, a full working sample project is posted on GitHub here. It works with the basic template of CoreUI. Which is a free (there is a premium version available as well) Bootstrap Admin Template created by Lukasz Holeczek. It is pretty cool because it is available for all popular SPA frameworks. Which makes the process of keeping up with all these new libraries a lot easier.
I'd love to hear your thoughts and ideas on improving the project. Or maybe you have a completely different way of integrating Angular and .NET Core?
I will also try and keep the sample up-to-date. But I could use some help maintaining and updating the sample on a regular basis. So ping me if you want to help, research with me, and work towards a better integration!
Comments?
Leave us your opinion.