Integrate Angular in Spring Boot with Gradle
Having a Angular HTML5 single page application and a Spring Boot application, we would like to serve the complete Angular app from Spring Boot. This blog shows you a couple simple steps to get everything up and running: run NPM from Gradle, integrate the Gradle frontend build in the main build and support HTML5 mode in the ResourceHandler of Spring Boot.
Run NPM from Gradle
Create a subdirectory called frontend
with the frontend code and build scripts (webpack, npm).
Let's assume our npm start
and npm run watch
output to the /frontend/dist/
directory.
First we need to make sure the frontend code is build when we run gradle build
on our project.
We can use the plugin gradle-node-plugin for this.
Go ahead and create a /frontend/build.gradle
file.
plugins {
id "com.moowork.node" version "0.12"
}
version '0.0.1'
node {
version = '6.8.0'
npmVersion = '3.10.8'
download = true
workDir = file("${project.buildDir}/node")
nodeModulesDir = file("${project.projectDir}")
}
task build(type: NpmTask) {
args = ['run', 'build']
}
build.dependsOn(npm_install)
Now, if we run a gradle build
from our frontend
subdirectory:
- node will be downloaded
npm install
will be executednpm run build
will be executed
Run integrated Gradle build
To run the frontend submodule integrated from our root project, all we need to do is include a settings.gradle
at the root of the project.
include 'frontend'
rootProject.name = 'my-project-name'
Go ahead and run gradle build
from the root of our project and see that npm is downloaded and the expected npm tasks are run.
We need to include the distribution of the frontend build in the JAR.
Thus the frontend:build
task needs to be run before we process the resources of the JAR.
Go ahead and add the following snippet to /build.gradle
.
jar {
from('frontend/dist') {
//Public is a default supported Spring Boot resources directory.
into 'public'
}
}
//frontend:build will be run before the processResources
processResources.dependsOn('frontend:build')
Support Angular HTML5 mode
Now all we need to do is create support for HTML5 mode in Angular. Angular is a single page application and subroutes of the application are default served with a '#' hashtag separator. If we want to have regular paths, we can enable HTML5 mode. The problem in serving this Angular HTML5 application from Spring Boot is that the Spring Boot ResourceHandler cannot find these resources, since the real resources is the index.html with the JavaScript files. With the next code snippet we instruct Spring Boot to look for the index.html as well. This is inspired by http://stackoverflow.com/questions/24837715/spring-boot-with-angularjs-html5mode
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.resource.PathResourceResolver;
import java.io.IOException;
import java.util.Arrays;
@Configuration
@EnableConfigurationProperties({ResourceProperties.class})
public class StaticResourcesConfiguration extends WebMvcConfigurerAdapter {
static final String[] STATIC_RESOURCES = new String[]{
"/**/*.css",
"/**/*.html",
"/**/*.js",
"/**/*.json",
"/**/*.bmp",
"/**/*.jpeg",
"/**/*.jpg",
"/**/*.png",
"/**/*.ttf",
"/**/*.eot",
"/**/*.svg",
"/**/*.woff",
"/**/*.woff2"
};
@Autowired
private ResourceProperties resourceProperties = new ResourceProperties();
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//Add all static files
Integer cachePeriod = resourceProperties.getCachePeriod();
registry.addResourceHandler(STATIC_RESOURCES)
.addResourceLocations(resourceProperties.getStaticLocations())
.setCachePeriod(cachePeriod);
//Create mapping to index.html for Angular HTML5 mode.
String[] indexLocations = getIndexLocations();
registry.addResourceHandler("/**")
.addResourceLocations(indexLocations)
.setCachePeriod(cachePeriod)
.resourceChain(true)
.addResolver(new PathResourceResolver() {
@Override
protected Resource getResource(String resourcePath, Resource location) throws IOException {
return location.exists() && location.isReadable() ? location : null;
}
});
}
private String[] getIndexLocations() {
return Arrays.stream(resourceProperties.getStaticLocations())
.map((location) -> location + "index.html")
.toArray(String[]::new);
}
}
Happy coding!