0

I'm working on a full-stack app having spring boot v2.7.5 as the backend and Angular v15 as the front end. I use IntelliJ IDEA IDE for development. Locally, spring boot runs on http://localhost:8080 and angular runs on http://localhost:4200. I use Gradle to build the project a single war file and which would be deployed on an external tomcat server.

Following is the project structure:

enter image description here

I have 3 build.gradle files, 1 for frontend , 1 for backend, and 1 for global. When I run the global build.gradle file, it would call call build.gradle from fronend folder which builds angular project and copies all the build files and put them into backend/src/main/resources/static folder. Next, build.gradle from the backend gets called which would build the final war file to be deployed on the external tomcat server.

The reason I'm putting frontend build files (index.html, some .js files) into backend/src/main/resources/static is the fact that Spring Boot Serves static content from that location. more details .

So the static directory looks like this after adding frontend build files:

enter image description here

When I try to access http://localhost:8080, it loads index.html from the static folder. enter image description here

So far it is good. When I click the login button, internally it calls the backend API and moves to the next page (home page i.e., http://localhost:8080/fe/appInstances).

enter image description here

Now if I refresh the page, it gives me the following 404 Whitelabel Error Page. enter image description here

I understand that since this is spring-boot as it is looking for a definition of the http://localhost:8080/fe/appInstances API endpoint in the java code.

To fix this, I have created the following IndexController.java class which should redirect all the frontend rest endpoints to index.html which is present in main/resources/static folder.

IndexController.java

@Controller
public class IndexController {
    @GetMapping("/")
    public String index() {
        return "redirect:/index";
    }

    @GetMapping("/fe/*")
    public String anyFrontEndApi() {
        return "index";
    }
}

But now, I get the following Whitelabel error page about Circular view path [index]: would dispatch back to the current handler URL [/fe/index] again.

enter image description here

I have tried changing @Controller to @RestController and changing the return type to ModelandView or something like this. But irrespective of all, it is still giving me the Whitelabel Error Page about Circular view path...

@RestController
public class IndexController {
    @GetMapping("/")
    public String index() {
        return "redirect:/index";
    }

    @GetMapping("/fe/*")
    public ModelAndView anyFrontEndApi() {
        ModelAndView mv = new ModelAndView();
        mv.setViewName("index");
        return mv;
    }
}

Am I missing something here? Can someone please suggest me a fix for this?

PS: @justthink addressed this situation here. But I don't know how to do reverse proxy way.

Ravi
  • 1,614
  • 4
  • 14
  • 27
  • Might this be a duplicate of https://stackoverflow.com/questions/43913753/spring-boot-with-redirecting-with-single-page-angular2 ? – meriton Nov 28 '22 at 06:12
  • Could you try with hashLocation strategy `RouterModule.forRoot(routes, {useHash: true})` let me know if it works or not? `{useHash: true}` in router module – Vikas Nov 29 '22 at 10:55
  • @Vikas, it could not work – Ravi Nov 30 '22 at 05:08
  • just my guessing try forward:/index.html this will forward to ur page has server side rendering and preserve things if it doesn't work you might need to overwrite WebMvcConfigurer im quite sure it default to jsp resolver dispatcher might be the reason you get that circular view path. Just my 2 cents guessing – DarkVision Nov 30 '22 at 06:19
  • @DarkVision, Sorry, I could not get you fully. Could you please write a separate answer by taking an example? – Ravi Nov 30 '22 at 06:28

2 Answers2

0

We had this situation of page refresh for Angular and Springboot and we resolved this by adding the below Configuration class extending WebMvcConfigurerAdapter

@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");
                    }
                });
    }
}

So basically, we are telling Springboot that if we have the resource, use the same if not then redirect it to index.html.

Now, to handle the path in Angular, it depends on how you would have written your routes. If the path is available, you show the page, if not, display 404 page.

Hope this helps.

Update 1:

WebMvcConfigurerAdapter is deprecated. If this causes any trouble, then instead of extending the class WebMvcConfigurerAdapter, you can implement WebMvcConfigurer

Guruprasad J Rao
  • 29,410
  • 14
  • 101
  • 200
0

If you see the whitelabel error says that "this application has no explicit mapping for /error".

That means if no path is matched with the paths that are defined in controller mappings, it forwards the request to "/error" route. So we can override this default behaviour.

Spring provides ErrorController interface to override this functionality

import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class CustomErrorController implements ErrorController {

    @RequestMapping("/error")
    public String handleError() {
        return "forward:/";
    }
}