The source for this project can be found on GitHub.
I recently became a big fan of JHipster. For those of you not familiar, JHipster is a development platform for generating Spring Boot and Angular Web applications. With JHipster you can set up a robust production-ready Web application with a sleek, modern UI in the matter of minutes. If you aren’t familiar, I strongly recommend you check out the site to learn more. The documentation is really good and there are a some great working examples as well.
One of the initial challenges I had with JHipster is that the generated interface did not support a true master-detail UI. The good news is that JHipster supports relationships in your entities very well, it just takes a few minor tweaks to get the UI to display the relationship on a single page. The purpose of this blog post is to show you the changes I made to display a one-to-many relationship on a single page, with a master-detail UI.
What Is A Master Detail Relationship?
For the sake of this tutorial, I am going to use a very simple relationship between an Owner and a Pet. An Owner is a simple entity with only one property, a name. A Pet is a slightly more complex (although not really) entity that takes a name and species. These two entities have a one-to-many relationship from Owner to Pet. That is an Owner may have more than one Pet, and a Pet must have one, and only one Owner. In this case, the Owner is the master record or entity, and Pet is the detail record or entity.
Generating The Initial Application
So to get started, I’m going to walk you through the process of generating the application in JHipster. This is by no means an exhaustive JHipster tutorial, so for more information on generating the application, see the JHipster documentation. I’m also assuming you have JHipster installed already.
Run JHipster to Generate the App
This step is easy enough, from the command line run the JHipster command:
Andrews-MBP:master-detail andrew$ jhipster

This command will walk you through the process of selecting the technologies you want to use in your application stack. For the sake of this tutorial generate a monolith with a relational database using AngularJS. You can use the defaults for the rest of the options for this demo case, although you may want to chose a persistent h2 database for your development environment.
Generate The Entities
After you’ve generated the application the next step is adding the entities. Entities are your database object, in this case the Owner and Pet objects that are the subject of our master-detail relationship.
Using JDL
There are several ways to generate entities and they are documented well. You can use the jhipster entity command to generate them from the command line but I’ve found the online JDL-Studio tool to be a very fast and easy way to generate your entities and relationships.
I used the following JDL to define my entities:
entity Owner { name String required } entity Pet { name String required, species String required } relationship OneToMany { Owner{pet} to Pet{owner} }
Notice the OneToMany relationship in the above JDL. This is what defines the underlying relationship and is key to getting the master-detail relationship to work quickly. The above example states that a Owner has many Pets and a Pet has a single Owner.
Importing JDL
You can download your JDL file from JDL-Studio (or create your own locally). Once this is done you will need to import the entities into your JHipster application. This is done with the jhipstor import-jdl command. Running this command will ask you to overwrite your existing entity definitions. In this case it is safe to overwrite them.
Run The Application For The First Time
At this point you have a fully functioning app. Go ahead and start it up for the first time to see how cool JHipster is. The default address will be http://localhost:8080. You’ve already got a completely working CRUD interface for your entities!

Take some time to move around and get comfortable with the standard interface. Once you log in you will have access to the entites menu. In here you can find pages to list, add, edit, and delete your entities.

The above screenshot is an example of the default owner listing. In the default app, clicking on the view or edit button would only show details on the Owner entity. To create Pets for this Owner you would need to go to the Pets entity and select the correct Owner as you create new ones as in the image below.

The next step is to start making modifications to the code to create your master-detail UI.
Modifying The Generated Code
JHipster uses Spring Boot on the backend, automatically creating JPA repositories to talk to our data source and setting up repositories to establish our REST API that is used by the AngularJS frontend. Most of the framework is there so we need to make a few minor modifications to get our master-detail relationship working.
Updating The Repository
Check out the Swagger API documentation under the Administration -> API menu item. You can see in the pet-resource that we have no way to easily query Pet entities by Owner. This is a key component in getting this relationship to work.

So, the first thing we need to do is make the Pet data easily available based on the Owner. There are a few different ways to go about this but I decided to add a new REST end point called /api/pets/owners/{ownerId}. I chose this route because I wanted to keep the Owner and Pet data sources separate in my Resource classes.
Make The Data Available From The Repository
So first we need to provide a way to query the data source for all Pets given a specific Owner. Fortunately for us JPA makes this very easy. So load up the generated application in your favorite editor (I’m using IntelliJ Community here). We are going to modify the PetRepository class to expose the data we need.

So a couple of notes here. First, JHipster uses a specific directory structure so you will find the repositories under src/main/java//repository. Second, JPA uses a very specific syntax in order to query the data source without us having to write any SQL. In the following method specification we are adding:
List<Pet> findOwnerById(Long Id);
The “Owner” part in this line must match the parent entity property name in the Pet class. The “Id” part must match the parent entity unique identifier property name in the Owner class. This is how JPA is able to query the underlying data source correctly without us adding any SQL code. Once you’ve made the change, save and compile your code. Note that you don’t have to restart your Spring Boot app. JHipster’s included development tools will automatically apply your changes when using the development profile.
Adding The New End Point To The Resource
So now that we’ve created a way to query for the data we need, we need to actually add the new rest endpoint that I mentioned above. We will do this by adding some code to the PetResource class.

The PetResource class can be found in the src/main/java//web.rest directory. We add the following code to add our new endpoint:
/** * GET /pets/owners/{ownerId} : get all the pets by owner id. * * @return the ResponseEntity with status 200 (OK) and the list of actions in body */ @GetMapping("/pets/owners/{ownerId}") @Timed public List<Pet> getAllPetsForOwner(@PathVariable Long ownerId) { log.debug("REST request to get all pets for owner : {}", ownerId); List<Pet> actions = petRepository.findByOwnerId(ownerId); return actions; }
The important things to note about the addition of code are the fact that we are passing in a PathVariable of ownerId and that we are calling the findByOwnerId method we created in the previous step with that value. So go ahead and save and compile the changes we’ve added here. Let’s go check out the online Swagger documentation again and see what we find!

Check it out! There is our new endpoint. And thanks to Swagger, you can actually check it out from the interface right here. If you’ve already added some data to your application, try it out, you should be able to query for all Pets given an Owner’s id number.
Updating The AngularJS Interface
Believe it or not, we’ve made all the backend changes we need to make. At this point the rest of our changes are related to the frontend.
Just a word of caution, I am not a master AngularJS developer, nor an expert JHipster user. there is likely a “more Angular” and “more JHipster” way to do this, but this method worked for me. If you have a better way, please let me know. I’d be happy to share it.
If you haven’t done so already, go ahead and fire up gulp from the command line in your JHipster project directory. This should launch your application on port 9000. This gives us an interface that updates live as we make changes to the frontend. This is a very helpful tool for making fast changes.
Updating The Master (Owner) View
JHipster generates a very slick looking UI. The AngularJS code is broken up in a manner consistent with John Papa’s Angular 1 Style Guide. Understanding this style guide is a must if you want to understand what is going on with the AngularJS code.

JHipster creates several controller, service, and html files for your entity as well as a state file. At a high level, the controller files should contain logic to render the view. The service file contains logic for communicating with the back-end. The html files contain the code for rendering the display. Finally, the state file contains routing information for the entity.
JHipster also provides the following standard actions for each entity. They are:
- The ability to list all entity items.
- Create a new entity item
- View the details of a given entity item.
- Edit the details of a given entity item.
- Delete an entity item.
For this particular tutorial I am only going to set up the master-detail relationship on the Owner entity details

So we need to add the Pet entity “list” html code to the owner-detail.html file. This is actually as easy as taking the code from the pets.html code and inserting it into the owner-detail.html code at the location you want it to display. Note that if you are using multiple language support you will have to make some additional changes for your translations to be found.
Since you are using gulp, the second you save your changes to owner-detail.html you can check out the changes live. At this point you won’t actually see any Pets displayed in the view. This is because we need to update the owner-detail.controller.js code to actually query for them.
Updating The Detail (Pet) Service Code
So in order to actually query for Pets and restrict to an Owner we need to update the service code, which contains the logic for querying the backend. To do this we will be making some changes to the pet.service.js file.
(function() { 'use strict'; angular .module('masterdetailApp') .factory('Pet', Pet); Pet.$inject = ['$resource']; function Pet ($resource) { var resourceUrl = 'api/pets/:id'; return $resource(resourceUrl, {}, { 'queryByOwner': { url: 'api/pets/owners/:id', method: 'GET', isArray: true }, 'query': { method: 'GET', isArray: true}, 'get': { method: 'GET', transformResponse: function (data) { if (data) { data = angular.fromJson(data); } return data; } }, 'update': { method:'PUT' } }); } })();
The important part of the code above is lines 13-17. We have added a query method that allows us to hit our new REST endpoint we created back in the beginning. By using this method I could reuse the existing Pet service. I imagine that there is a better way and that this method could cause issues with JHipster code generation in the long run, but it works.
In line 14 above, we override the resourceUrl to hit a different endpoint. Note that we will be passing a parameter in the URL. In this case it will be the Owner Id.
Lines 15 and 16 are copied from the “query” method that already existed.
Updating The Master (Owner) Controller
Now that we’ve added code to query for the data we want, we need to call that code from a controller. Since we are modifying the Owner detail view in the UI we want to update the owner-detail.controller.js file.
(function() { 'use strict'; angular .module('masterdetailApp') .controller('OwnerDetailController', OwnerDetailController); OwnerDetailController.$inject = ['$scope', '$rootScope', '$stateParams', 'previousState', 'entity', 'Owner', 'Pet']; function OwnerDetailController($scope, $rootScope, $stateParams, previousState, entity, Owner, Pet) { var vm = this; vm.owner = entity; vm.previousState = previousState.name; vm.pets = []; loadAll(); function loadAll() { Pet.queryByOwner({id: vm.owner.id}, function(result) { vm.pets = result; vm.searchQuery = null; }); } var unsubscribe = $rootScope.$on('masterdetailApp:ownerUpdate', function(event, result) { vm.owner = result; }); $scope.$on('$destroy', unsubscribe); } })();
We’ve added lines 16-25 to the controller and they are responsible for:
- Initializing an empty pets list in the view model.
- Calling a loadAll function that is responsible for querying the backend data source.
- Defining the loadAll function to access the detail (Pet) service and use the queryByOwner logic we added in the previous step.
- Pass the Owner Id as a parameter in the queryByOwner call. Note that this is the current Owner Id that already exists in the view model.
After this change you can check out the UI again to see the changes we’ve made.
The Finished Product
At this point you can create a few Owner entity items. In this example I have defined the owners Andrew and Bob.

I have also defined a Pet item for each owner. Note in listing below the different Owner Ids for each pet that correspond to the master Owner record.

Now it’s time to see the final product. Go ahead and check the details view (click the View button) for each of your Owners. If you’ve followed everything right you should see only the associated Pets in the Owner details!


Now you have a working master-detail UI. Go ahead and try to add some more Pets. You can even do it from the Owner details screen.
A Few Things To Be Aware Of
- As mentioned already if you are using multilanguage support you will need to move some of your translations around.
- We didn’t talk about state or routing at all in this example. You will actually have to make quite a few changes to get routing to behave as you would expect in your application. For example if you create a new Pet from the Owner detail view you will be forward to the Pet listing afterward in the current example.
- We only implemented this from the Owner detail view. If you have other entities, or other places where you want to display this info you will need to make additional changes.
- There is probably a better way to do this with “good AngularJS” and “good JHipster” coding practices. Please reach out if you know what they are.
Give It A Shot
JHipster is a great tool for creating fast apps. I’m just getting in to everything you can do with it but I can already see how it saves a ton of setup and configuration time.
If you have any comments or suggestions on how I went about solving for this problem please let me know and I will be happy to update the blog. I created this entry myself as I was struggling to find the answer in one spot. Hopefully this will help somebody out.
You can find the source for this project on my github:
https://github.com/andrewehale/jhipster-master-detail
Thank you very much , you saved my time and really you did a great job to share it, thank you again
LikeLike
JHipster is definitely awesome, super easy to get up and running!
LikeLike
Great post! Can this be used for implementing entities that are only shown for a specific User? It would be a lot of code for a larger data model.
LikeLike
If you want to use this to tie data to the logged in user this may help but you need to be concerned about security. The way we went about this feature (and there may be a better way) was to add a user id column to each entity that we needed to tie a user. We then pulled the user id from the logged in session using the built in security tools that come with jhipster. We then modified all of our hibernate functions to utilize the user id. I actually have a stackoverlfow post on it somewhere you may be able to dig up…
LikeLike
Thanks for the post. It helped a lot. There is typo in the method specification “List findOwnerById(Long Id);” under the subheading “Make The Data Available From The Repository”.
LikeLike
you could add to the jdl schema :
filter *
This way you even don’t need to modify anything in the backend
LikeLike
great…in march 2019 this is not yet implemented in Jhipster, tried just now…
LikeLike
at october 2019 the issue is till there and I have reported it here: https://github.com/jhipster/generator-jhipster/issues/9639
let’s see what they say 🙂
than ks for your gret work!
LikeLike