We are using an SPA approach in our JSF 2.2 + PrimeFaces enabled application. The basic idea was originally described very well here:
Refreshing dynamic content with AJAX in JSF using SPA approach
But, as we know, using this SPA approach has a drawback when using @ViewScoped beans.
As we are actually always staying in the same JSF View, @ViewScoped beans are not removed from memory, when we replace the content of a panel group with the new SPA content.
I have found a solution, but I would like to know if it's a correct approach, and/or if there is anything missing.
Basically, in our NavigationService bean, which holds the name of the page to be rendered during the SPA AJAX request, we always execute the following code:
private void clearViewScopedBeans() {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
for(Iterator<Map.Entry<String, Object>> it = viewMap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Object> entry = it.next();
it.remove();
}
}
This should ensure that, after the new SPA snippet is rendered, all the previous existing @ViewScoped beans are removed.
However, I think the above code only removes the View Scoped beans, but not the related View States. Is that correct ?
I found an old blog entry which seems to do a little more logic: http://javaevangelist.blogspot.sg/2014/08/jsf-21-tip-of-day-clearing-viewscope.html
but I dunno if it's correct as well.
Additionally, if we want to support multiple window tabs, our NavigationService bean, holding the current SPA snippet page name, must be @ViewScoped as well, and this introduces a small problem:
When running the above code for removing all the existing @ViewScoped beans... we have to exclude the NavigationService bean itself !! Otherwise we end up loading always the same page, because a new instance of the NavigationService is instantiated, with a default SPA page name, instead of the new one.
So, all in all, our code looks finally like this, where we keep a Map of "excluded" bean names, that we don't want to remove on a SPA page refresh (namely the NavigationService bean holding the SPA page name)
private void clearViewScopedBeans() {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
for(Iterator<Map.Entry<String, Object>> it = viewMap.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Object> entry = it.next();
if(!exclusionViewScopedBeans.contains(entry.getKey())) {
logger.info("Removing an instance of a @ViewScoped bean -> " + entry.getKey());
it.remove();
}
}
}
Now the question ... is this the correct approach for handling these kind of SPA situations? Are we missing something here?
Any feedback would be greatly appreciated... thanks a lot in advance !