I believe this is a simple question, but I couldn't find an answer or at least use the correct terms in the search.
I am setting up Angular2
and Springboot
together. By default, Angular
will use paths like localhost:8080\dashboard
and localhost:8080\dashboard\detail
.
I'd like to avoid using path as hashs, if possible. As Angular documentation states:
The router's provideRouter function sets the LocationStrategy to the PathLocationStrategy, making it the default strategy. We can switch to the HashLocationStrategy with an override during the bootstrapping process if we prefer it.
And then...
Almost all Angular 2 projects should use the default HTML 5 style. It produces URLs that are easier for users to understand. And it preserves the option to do server-side rendering later.
The issue is that when I try to access localhost:8080\dashboard
, Spring will look for some controller mapping to this path, which it won't have.
Whitelabel Error Page
There was an unexpected error (type=Not Found, status=404).
No message available
I thought initially to make all my services to be under localhost:8080\api
and all my static under localhost:8080\app
. But how do I tell Spring to ignore requests to this app
path?
Is there a better solution with either Angular2 or Boot?
In my Spring Boot applications (version 1 and 2), my static resources are at a single place :
src/main/resources/static
static
being a folder recognized by Spring Boot to load static resources.
Then the idea is to customize the Spring MVC configuration.
The simpler way is using Spring Java configuration.
I implement WebMvcConfigurer
to override addResourceHandlers()
.
I add in a single ResourceHandler
to the current ResourceHandlerRegistry
.
The handler is mapped on every request and I specify classpath:/static/
as resource location value (you may of course adding others if required).
I add a custom PathResourceResolver
anonymous class to override getResource(String resourcePath, Resource location)
.
And the rule to return the resource is the following : if the resource exists and is readable (so it is a file), I return it. Otherwise, by default I return the index.html
page. Which is the expected behavior to handle HTML 5 urls.
Spring Boot 1.X Application :
Extending org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
is the way.
The class is an adapter of the WebMvcConfigurer
interface
with empty methods allowing sub-classes to override only the methods they're interested in.
Here is the full code :
import java.io.IOException;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
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;
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**/*")
.addResourceLocations("classpath:/static/")
.resourceChain(true)
.addResolver(new PathResourceResolver() {
@Override
protected Resource getResource(String resourcePath,
Resource location) throws IOException {
Resource requestedResource = location.createRelative(resourcePath);
return requestedResource.exists() && requestedResource.isReadable() ? requestedResource
: new ClassPathResource("/static/index.html");
}
});
}
}
Spring Boot 2.X Application :
org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
was deprecated.
Implementing directly WebMvcConfigurer
is the way now as it is still an interface but it has now default methods (made possible by a Java 8 baseline) and can be implemented directly without the need for the adapter.
Here is the full code :
import java.io.IOException;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.PathResourceResolver;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**/*")
.addResourceLocations("classpath:/static/")
.resourceChain(true)
.addResolver(new PathResourceResolver() {
@Override
protected Resource getResource(String resourcePath,
Resource location) throws IOException {
Resource requestedResource = location.createRelative(resourcePath);
return requestedResource.exists() && requestedResource.isReadable() ? requestedResource
: new ClassPathResource("/static/index.html");
}
});
}
}
EDIT to address some comments :
For those that store their static resources at another location as src/main/resources/static
, change the value of the var args parameter of addResourcesLocations()
consequently.
For example if you have static resources both in static
and in the public
folder (no tried) :
registry.addResourceHandler("/**/*")
.addResourceLocations("classpath:/static/", "/public")
I have a solution for you, you can add a ViewController
to forward requests to Angular from Spring boot.
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ViewController {
@RequestMapping({ "/bikes", "/milages", "/gallery", "/tracks", "/tracks/{id:\\w+}", "/location", "/about", "/tests","/tests/new","/tests/**","/questions","/answers" })
public String index() {
return "forward:/index.html";
}
}
here I have redirected all my angular2 ("/bikes", "/milages", "/gallery", "/tracks", "/tracks/{id:\w+}", "/location", "/about", "/tests","/tests/new","/tests/**","/questions","/answers") to my SPA You can do the same for your preject and you can also redirect your 404 error page to the index page as a further step. Enjoy!
You can forward all not found resources to your main page by providing custom ErrorViewResolver. All you need to do is to add this to your @Configuration class:
@Bean
ErrorViewResolver supportPathBasedLocationStrategyWithoutHashes() {
return new ErrorViewResolver() {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
return status == HttpStatus.NOT_FOUND
? new ModelAndView("index.html", Collections.<String, Object>emptyMap(), HttpStatus.OK)
: null;
}
};
}
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