Now that we have our entity domain model created and working, let's make it more usable. The Product listing screen has a table view generated by JHipster; it is sufficient for simple CRUD operations but isn't the best-suited user experience for end users who want to browse our product listing. Let's see how we can easily change to something more appealing. We will also add a nice client-side filter option to filter the listing. We will be using both Angular and Bootstrap features for this.
First, let's find the source code that we would need to edit. In your favorite editor/IDE navigate to src/main/webapp/app/entities/product:

Let's start by customizing the product.component.html file to update the UI view of the product listing. The HTML code currently renders a table view and uses some Angular directives to enhance the view with sorting and pagination. Let's first change the view from a table into a list, but first open the development web server from BrowserSync, if it's not already open, by navigating to http://localhost:9000. Log in and navigate to Entities | Product Category and create a category, then navigate to Entities | Product and create few new products so that we have something to list:

We can use the Bootstrap List group (https://getbootstrap.com/docs/4.0/components/list-group/) component for this purpose. Let's use the following snippet and change the view. Replace the div with class="table-responsive" with the following code:
<div *ngIf="products">
<div class="list-group">
<a [routerLink]="['../product', product.id ]"
class="list-group-item list-group-item-action flex-column
align-items-start"
*ngFor="let product of products; trackBy: trackId">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{product.name}}</h5>
<small *ngIf="product.productCategory">
<a [routerLink]="['../product-category',
product.productCategory?.id ]" >
{{product.productCategory?.id}}
</a>
</small>
</div>
<small class="mb-1">{{product.description}}</small>
<p class="mb-1">Price: {{product.price}}</p>
<small>
Size:
<span jhiTranslate="{{'storeApp.Size.' +
product.size}}">
{{product.size}}
</span>
</small>
</a>
</div>
</div>
As you can see, we are iterating the products using the Angular directive *ngFor="let product of products; trackBy: trackId" on the anchor element so that the element is created for each product in the list. We wrap this in a *ngIf="products" directive so that the view is rendered only when the product's object is defined. The [routerLink]="['../product', product.id ]" directive will create a href for the anchor using the Angular router so that we can navigate to the particular product route. We then use properties from the product in template strings to be rendered using {{product.name}} syntax. As you save the code, you might notice that the view refreshes automatically, thanks to BrowserSync.
This will produce the following:

While it's a good start, it's not enough. So, let's go in and make it better. Let's add the image to the listing first. Modify the code to add Bootstrap rows and columns, as shown in the following code, the original code rendering the content is moved into the second column and remains unchanged:
<div *ngIf="products">
<div class="list-group">
<a [routerLink]="['../product', product.id ]" class="list-group-item list-group-item-action flex-column align-items-start"
*ngFor="let product of products; trackBy: trackId">
<div class="row">
<div class="col-2 col-xs-12 justify-content-center">
<img [src]="'data:' + product.imageContentType +
';base64,' + product.image"
style="max-height:150px;" alt="product image"/>
</div>
<div class="col col-xs-12">
<div class="d-flex w-100 justify-content-between">
...
</div>
<small class="mb-1">{{product.description}}</small>
<p class="mb-1">Price: {{product.price}}</p>
<small>
...
</small>
</div>
</div>
</a>
</div>
</div>
Take a look at the code highlighted in bold. We added a Bootstrap row (https://getbootstrap.com/docs/4.0/layout/grid/) with two column divs, the first div takes up two columns in a 12 column grid specified by col-2, while we also say that when the display is xs (extra small) the div tag should take 12-columns using col-xs-12. The second div is kept responsive by specifying just col so it takes the remaining available columns after the first div, and when the display is extra small it takes up 12 columns as well. The image inside the first column div uses a data URL as src to render the image. Now we have an even better view:

We can polish it further. We can use the Angular currency pipe (https://angular.io/api/common/CurrencyPipe) for the price and remove the redundant label for it by changing to {{product.price | currency:'USD'}}. We can add a label for the category shown on the right-hand side of the list as well.
Finally, we can add the Edit and Delete buttons back, but we need to show them only for users who have the role ADMIN so that normal users will only be able to view the product listing. We can copy the HTML code for edit and delete buttons from the original table. The final code will be as follows:
<div *ngIf="products">
<div class="list-group">
<a [routerLink]="['../product', product.id ]"
class="list-group-item list-group-item-action flex-column
align-items-start"
*ngFor="let product of products; trackBy: trackId">
<div class="row">
<div class="col-2 col-xs-12 justify-content-center">
<img [src]="'data:' + product.imageContentType +
';base64,' + product.image"
style="max-height:150px;" alt="product image"/>
</div>
<div class="col col-xs-12">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{product.name}}</h5>
<small *ngIf="product.productCategory">
<a [routerLink]="['../product-category',
product.productCategory?.id ]" >
Category: {{product.productCategory?.id}}
</a>
</small>
</div>
<small class="mb-1">{{product.description}}</small>
<p class="mb-1">{{product.price | currency:'USD'}}</p>
<small>
Size:
<span jhiTranslate="{{'storeApp.Size.' +
product.size}}">
{{product.size}}
</span>
</small>
<div *jhiHasAnyAuthority="'ROLE_ADMIN'">
<button type="submit"
[routerLink]="['/',
{ outlets: { popup: 'product/'+
product.id + '/edit'} }]"
replaceUrl="true"
queryParamsHandling="merge"
class="btn btn-primary btn-sm">
<span class="fa fa-pencil"></span>
<span class="d-none d-md-inline"
jhiTranslate="entity.action.edit">Edit</span>
</button>
<button type="submit"
[routerLink]="['/',
{ outlets: { popup: 'product/'+
product.id + '/delete'} }]"
replaceUrl="true"
queryParamsHandling="merge"
class="btn btn-danger btn-sm">
<span class="fa fa-remove"></span>
<span class="d-none d-md-inline"
jhiTranslate="entity.action.delete">Delete</span>
</button>
</div>
</div>
</div>
</a>
</div>
</div>
The *jhiHasAnyAuthority="'ROLE_ADMIN'" directive is provided by JHipster and can be used to control presentation based on user roles. By default, JHipster provides ROLE_ADMIN and ROLE_USER, but controlling this only on the client side is not secure as it can be easily bypassed, so we should secure this on the server side as well. We will look at this later in the chapter. Log out and log in again using the user account to see the directive in action:

Now, let's also add the *jhiHasAnyAuthority="'ROLE_ADMIN'" directive to the create button element.
Now that our view is much better, let's bring back the sorting functionality we originally had. Since we do not have table headers anymore we can use some buttons to sort based on certain fields that are important.
Let's use Bootstrap button group (https://getbootstrap.com/docs/4.0/components/button-group/) for this. Place the following snippet above the <div class="list-group"> element we created earlier:
<div class="mb-2 d-flex justify-content-end align-items-center">
<span class="mx-2 col-1">Sort by</span>
<div class="btn-group" role="group"
jhiSort [(predicate)]="predicate" [(ascending)]="reverse"
[callback]="transition.bind(this)">
<button type="button" class="btn btn-light" jhiSortBy="name">
<span jhiTranslate="storeApp.product.name">Name</span>
<span class="fa fa-sort"></span>
</button>
<button type="button" class="btn btn-light" jhiSortBy="price">
<span jhiTranslate="storeApp.product.price">Price</span>
<span class="fa fa-sort"></span>
</button>
<button type="button" class="btn btn-light" jhiSortBy="size">
<span jhiTranslate="storeApp.product.size">Size</span>
<span class="fa fa-sort"></span>
</button>
<button type="button" class="btn btn-light"
jhiSortBy="productCategory.id">
<span
jhiTranslate="storeApp.product.productCategory">Product Category</span>
<span class="fa fa-sort"></span>
</button>
</div>
</div>
We can use Bootstrap margin and flexbox utility classes such as mb-2 d-flex justify-content-end align-items-center to position and align the item properly. We use the btn-group class on a div element to group our button elements together on which we have placed the jhiSort directive and its bound properties such as predicate, ascending, and callback. On the buttons themselves, we use the jhiSortBy directive to specify which field it would use to sort. Now our page looks as follows, where products are sorted by price:

Finally, let's add some good old client-side filtering for the page.
First, let's add a new instance variable called filter of type string to the ProductComponent class in the product.component.ts file:
export class ProductComponent implements OnInit, OnDestroy {
...
filter: string;
constructor(
...
) {
...
}
...
}
Now, let's use this variable in the product.component.html file. Add the highlighted snippet from the following code to the div we created for the sort-by buttons:
<div class="mb-2 d-flex justify-content-end align-items-center">
<span class="mr-2 col-2">Filter by name</span>
<input type="search" class="form-control" [(ngModel)]="filter">
<span class="mx-2 col-1">Sort by</span>
<div class="btn-group" role="group"
...
</div>
</div>
We bound the filter variable to an input element using the ngModel directive, and using [()] ensures two-way binding on the variable.
Finally, update the ngFor directive on our list-group-item element as follows. We use a pipe provided by JHipster to filter the list using the name field of the product:
*ngFor="let product of (products | pureFilter:filter:'name'); trackBy: trackId"
That's it, and we get a shiny filter option on our screen:

The UX is much better than before but for a real-world use case you could build a much better UI for the client-facing website, with features to add items to a cart, pay for items online, and so on, and leave this part for the back office use. Let's commit this to git: this is very important for managing changes to the project later. Run the following commands:
> git add --all
> git commit -am "update product listing page UI"