Grails 3 Angular 5 Profile CRUD Web Application Example

by Didin J. on Nov 18, 2017 Grails 3 Angular 5 Profile CRUD Web Application Example

Step by step tutorial on how to build CRUD web application using Grails 3 Angular 5 profiles with the working example

Step by step tutorial on how to build create-read-update-delete (CRUD) web application using Grails 3 Angular 5 profiles with the working example. We already show you how to integrate Angular 5 as the front end with Node.js Express.js backend in this tutorial. Now, we have to show you how easy to integrate Angular 5 front end with Grails 3 the robust and enterprise-grade web framework. There is Angular profile comes with Grails 3, even there still using Angular 4, it's easy to upgrade to Angular 5.

Table of Contents:

This time we are using Angular 5 as the separate application that will access or integrate with Grails 3 application by accessing the RESTful web service or REST API. So, there will be 2 application run at the same time on the same machine using the different port.

As usual, we provide the tutorial from scratch. We have to prepare the following environment, frameworks, and modules:

  1. Grails 3.3.1
  2. Node.js (recommended stable version)
  3. Angular CLI

For the database, we will be using the default generated Grails 3 in-memory database using HSQLDB or H2. Now, let start the journey.


Install and Create Grails 3 Application

After you download the latest stable Grails 3.3.1. On Windows you can extract to your local disk then add it in environment variables so, it will easy to call using the command line. On Mac or Linux, you can install it using SDKMAN. Open the terminal then type this command to install the latest Grails 3.3.1.

sdk install grails 3.3.1

If grails installed successfully, now you can go to your Grails projects folder. Type this command on the terminal or command line to create a new Grails 3 application with Angular profiles.

grails create-app grails-angular --profile angular

That command will create a new Grails 3 application and Angular 4 profiles with the application name `grails-angular`. Now, go to the newly generated `grails-angular` folder.

cd ./grails-angular

To run the Grails 3 and Angular 4 application separately run these commands.

./gradlew server:bootRun
./gradlew client:bootRun

Grails 3 run on the server and Angular 4 run as the client. To run both of them parallel, use this command.

./gradlew bootRun --parallel

Next, check the running Grails 3 and Angular application on the browser by going to this address `http://localhost:4200` for Angular 4 application and `http://localhost:8080` for Grails 3 application. Now, you can see this page in the browser.

Grails 3 Angular 5 Profile CRUD Web Application Example - Grails 3 Angular Profiles Page

Grails 3 Angular 5 Profile CRUD Web Application Example - Server Page

As you can see, now Grails 3 page use `json-view` instead of the HTML page. So, the result will be a JSON response.


Upgrade Angular 4 to Angular 5

Because the latest Grails 3.3.1 Angular profile still using version 4, we need to upgrade to Angular 5 manually. Fortunately, the official update guides are described here. But we just copy and paste the generated package dependencies from latest Angular-CLI. Open and edit `client/package.json` then replace all dependencies with this.

"dependencies": {
  "@angular/animations": "^5.0.2",
  "@angular/common": "^5.0.2",
  "@angular/compiler": "^5.0.2",
  "@angular/core": "^5.0.2",
  "@angular/forms": "^5.0.2",
  "@angular/http": "^5.0.2",
  "@angular/platform-browser": "^5.0.2",
  "@angular/platform-browser-dynamic": "^5.0.2",
  "@angular/router": "^5.0.2",
  "@ng-bootstrap/ng-bootstrap": "^1.0.0-beta.5",
  "bootstrap": "4.0.0-alpha.6",
  "core-js": "^2.4.1",
  "rxjs": "^5.5.2",
  "zone.js": "^0.8.14"
},
"devDependencies": {
  "@angular/cli": "1.5.0",
  "@angular/compiler-cli": "^5.0.2",
  "@angular/language-service": "^5.0.2",
  "@types/jasmine": "~2.5.53",
  "@types/jasminewd2": "~2.0.2",
  "@types/node": "~6.0.60",
  "codelyzer": "~3.2.0",
  "jasmine-core": "~2.6.2",
  "jasmine-spec-reporter": "~4.1.0",
  "karma": "~1.7.0",
  "karma-chrome-launcher": "~2.1.1",
  "karma-cli": "~1.0.1",
  "karma-coverage-istanbul-reporter": "^1.2.1",
  "karma-jasmine": "~1.1.0",
  "karma-jasmine-html-reporter": "^0.2.2",
  "protractor": "~5.1.2",
  "ts-node": "~3.2.0",
  "tslint": "~5.7.0",
  "typescript": "~2.4.2"
}

Now, test again the application by running both client and server after back to the parent folder.

cd ..
./gradlew bootRun --parallel


Create a Grails 3 Domain Class

For the data, we need to create a domain class or model on the server-side. Type this command to create it after you go to the server folder.

cd server
grails create-domain-class com.djamware.Customer

That command will create a domain class with package name `com.djamware` and domain class name `Customer`. We are creating a simple Customer data, open and edit `server/grails-app/domain/com/djamware/Customer.groovy` then replace all codes with this.

package com.djamware

import grails.rest.*

@Resource(uri='/customer')
class Customer {

  String name
  String address
  String city
  String postalCode
  String phone

  static constraints = {
      name blank:false
      address blank:false
      city blank:false
      postalCode blank:false
      phone blank:false
  }

  String toString() {
    name
  }
}

Actually, there is `json-view` that comes with this Grails 3 and Angular Profile, but we make it fast using Grails 3 REST to make domain class RESTful. Now, just open another terminal tabs or command line then type this command to test the RESTful API.

curl -i -H "Accept: application/json" localhost:8080/customer

You will see this response on the terminal.

HTTP/1.1 200
X-Application-Context: application:development
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 18 Nov 2017 13:04:11 GMT

Next, populate with this single customer data.

curl -i -X POST -H "Content-Type: application/json" -d '{"name":"John Doe","address":"accross the river behind the mountain","city":"the hight mount","postalCode":"11111","phone":"123123123"}' localhost:8080/customer

That command will respond like this if successful.

HTTP/1.1 201
X-Application-Context: application:development
Location: http://localhost:8080/customer/1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 18 Nov 2017 13:09:34 GMT

{"id":1,"phone":"123123123","address":"accross the river behind the mountain","postalCode":"11111","name":"John Doe","city":"the hight mount"}

Simply as that, now you have your RESTful API ready to access from the Angular 5 application. Don't worry about the CORS for using the different port, because it already enabled by Grails 3 default configuration.


Create Angular 5 Component for Displaying Customer List

We can create an Angular 5 component while the Angular 5 application running. Open another terminal tab then goes to the `client` folder then type this command to create an Angular 5 component.

ng g component customer

That command will generate all required files to build customer component and also automatically added a customer component to app.module.ts.

create src/app/customer/customer.component.css (0 bytes)
create src/app/customer/customer.component.html (27 bytes)
create src/app/customer/customer.component.spec.ts (642 bytes)
create src/app/customer/customer.component.ts (277 bytes)
update src/app/app.module.ts (1027 bytes)

Before adding any functionality to the component, we need to add `HttpClientModule` to `app.module.ts`. Open and edit `src/app/app.module.ts` then replace current `HttpModule` with this.

import { HttpClientModule } from '@angular/common/http';

Also, replace `HttpModule` in `@NgModule` imports.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule,
  AppRoutingModule,
  NgbModule.forRoot()
],

Now, we will making a request to Customer REST API using this Angular `HttpClient` module. Open and edit `src/app/customer/customer.component.ts` then add this import.

import { HttpClient } from '@angular/common/http';

Inject `HttpClient` to the constructor.

constructor(private http: HttpClient) { }

Add array variable for holding customers data before the constructor.

customers: any;

Add a few lines of codes for getting a list of customer data from REST API inside `ngOnInit` function.

ngOnInit() {
  this.http.get('http://localhost:8080/customer').subscribe(data => {
    this.customers = data;
  });
}

Now, we can display the customer list on the page. Open and edit `src/app/customer/customer.component.html` then replace all tags with these lines of HTML tags that contain Customer list iteration inside the HTML table.

<div class="container">
  <h1>Customer List</h1>
  <table class="table">
    <thead>
      <tr>
        <th>Name</th>
        <th>Address</th>
        <th>Action</th>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="let cust of customers">
        <td>{{ cust.name }}</td>
        <td>{{ cust.address }}</td>
        <td><a href="/show/{{cust.id}}">Show Detail</a></td>
      </tr>
    </tbody>
  </table>
</div>


Create Angular 5 Routes to Customer Component

To use customer component as the default landing page, open and edit `src/app/app-routing.module.ts` then replace the current routes constantly.

const routes: Routes = [
  {
    path: 'customers',
    component: CustomerComponent,
    data: { title: 'Customer List' }
  },
  { path: '',
    redirectTo: '/customers',
    pathMatch: 'full'
  }
];

Don't forget to add the import for `CustomerComponent`.

import { CustomerComponent } from "./customer/customer.component";

Before run and test the Angular 5 application, we need to clean up the generated navigation structure and replace with the simple one. First, open and edit `client/src/app/app.module.ts` then comment on this imports.

// import { NavComponent } from './nav/nav.component';
// import { NavService } from './nav/nav.service';

Also, this `@NgModule` declarations.

// NavComponent,

Remove `NavService` from providers.

Next, open and edit `client/src/app/app.component.html` then replace all HTML tags with this Angular <router-outlet>.

<router-outlet></router-outlet>

Now, you can run again the application. This time we run the separate server and client application to make Angular 5 or client-side development smoother.

./gradlew server:bootRun

On the other terminal or command line.

./gradlew client:bootRun

Open your browser then point to `localhost:4200`, you will see this page.

Grails 3 Angular 5 Profile CRUD Web Application Example - Customer List


Create an Angular 5 Component for Add Customer

The customer list is empty, so we need to create a component to add the customer. Type this command in the client folder to generate it.

ng g component customer-create

Open and edit `client/src/app/app-routing.module.ts` then add this import.

import { CustomerCreateComponent } from "./customer-create/customer-create.component";

Add this route to the routes after customer list route.

{
  path: 'customer-create',
  component: CustomerCreateComponent,
  data: { title: 'Add Customer' }
},

Add 'customer-create' link on `client/src/app/customer/customer.component.html`.

<h1>Customer List
  <a [routerLink]="['/customer-create']" class="btn btn-default btn-lg">
    <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
  </a>
</h1>

Now, open and edit `client/src/app/customer/customer-create.component.ts` then replace all with this codes to implement POST to REST API.

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-customer-create',
  templateUrl: './customer-create.component.html',
  styleUrls: ['./customer-create.component.css']
})
export class CustomerCreateComponent implements OnInit {

  customer = {};

  constructor(private http: HttpClient, private router: Router) { }

  ngOnInit() {
  }

  saveCustomer() {
    this.http.post('http://localhost:8080/customer', this.customer)
      .subscribe(res => {
          let id = res['id'];
          this.router.navigate(['/customer-details/', id]);
        }, (err) => {
          console.log(err);
        }
      );
  }

}

Modify `client/src/app/customer-create/customer-create.component.html`, replace all with this HTML tags.

<div class="container">
  <h1>Add New Customer</h1>
  <div class="row">
    <div class="col-md-6">
      <form (ngSubmit)="saveCustomer()" #customerForm="ngForm">
        <div class="form-group">
          <label for="name">Name</label>
          <input type="text" class="form-control" [(ngModel)]="customer.name" name="name" required>
        </div>
        <div class="form-group">
          <label for="name">Address</label>
          <input type="text" class="form-control" [(ngModel)]="customer.address" name="address" required>
        </div>
        <div class="form-group">
          <label for="name">City</label>
          <input type="text" class="form-control" [(ngModel)]="customer.city" name="city" required>
        </div>
        <div class="form-group">
          <label for="name">Postal Code</label>
          <input type="number" class="form-control" [(ngModel)]="customer.postalCode" name="postalCode" required>
        </div>
        <div class="form-group">
          <label for="name">Phone Number</label>
          <input type="text" class="form-control" [(ngModel)]="customer.phone" name="phone" required>
        </div>
        <div class="form-group">
          <button type="submit" class="btn btn-success" [disabled]="!customerForm.form.valid">Save</button>
        </div>
      </form>
    </div>
  </div>
</div>


Create Angular 5 Component for Displaying Customer Detail

The same as the previous section, type this command to generate a new component.

ng g component customer-detail

Open and edit `client/src/app/app-routing.module.ts` then add this import.

import { CustomerDetailComponent } from "./customer-detail/customer-detail.component";

Then add a router to the routes constant before redirect route.

{
  path: 'customer-details/:id',
  component: CustomerDetailComponent,
  data: { title: 'Customer Details' }
},

Open and edit `client/src/app/customer-detail/customer-detail.component.ts` then replace all codes with this.

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-customer-detail',
  templateUrl: './customer-detail.component.html',
  styleUrls: ['./customer-detail.component.css']
})
export class CustomerDetailComponent implements OnInit {

  customer = {};

  constructor(private route: ActivatedRoute, private http: HttpClient) { }

  ngOnInit() {
    this.getCustomerDetail(this.route.snapshot.params['id']);
  }

  getCustomerDetail(id) {
    this.http.get('http://localhost:8080/customer/'+id).subscribe(data => {
      this.customer = data;
    });
  }

}

Open and edit `client/src/app/customer-detail/customer-detail.component.html`. Replace all codes with this.

<div class="container">
  <h1>{{ customer.name }}</h1>
  <dl class="list">
    <dt>Address</dt>
    <dd>{{ customer.address }}</dd>
    <dt>City</dt>
    <dd>{{ customer.city }}</dd>
    <dt>Postal Code</dt>
    <dd>{{ customer.postalCode }}</dd>
    <dt>Phone Number</dt>
    <dd>{{ customer.phone }}</dd>
  </dl>
</div>


Create Angular 5 Component for Edit Customer

As usual, we will generate component for edit customer. Type this command for doing that.

ng g component customer-edit

Open and edit `client/src/app/app-routing.module.ts` then add this import.

import { CustomerEditComponent } from "./customer-edit/customer-edit.component";

Then add a router to the routes constant before redirect route.

{
  path: 'customer-edit/:id',
  component: CustomerEditComponent,
  data: { title: 'Edit Customer' }
},

Open and edit again `src/app/customer-details/customer-details.component.html` and add edit the routeLink after `<dl>` tag.

<div class="row">
  <div class="col-md-12">
    <a [routerLink]="['/customer-edit', customer.id]" class="btn btn-success">EDIT</a>
  </div>
</div>

Now, open and edit `src/app/customer-edit/customer-edit.component.ts` then replace all codes with this.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-customer-edit',
  templateUrl: './customer-edit.component.html',
  styleUrls: ['./customer-edit.component.css']
})
export class CustomerEditComponent implements OnInit {

  customer = {};

  constructor(private http: HttpClient, private router: Router, private route: ActivatedRoute) { }

  ngOnInit() {
    this.getCustomer(this.route.snapshot.params['id']);
  }

  getCustomer(id) {
    this.http.get('http://localhost:8080/customer/'+id).subscribe(data => {
      this.customer = data;
    });
  }

  updateCustomer(id) {
    this.http.put('http://localhost:8080/customer/'+id, this.customer)
      .subscribe(res => {
          let id = res['id'];
          this.router.navigate(['/customer-details', id]);
        }, (err) => {
          console.log(err);
        }
      );
  }

}

Open and edit `src/app/customer-edit/customer-edit.component.html` then replace all codes with this.

<div class="container">
  <h1>Edit Customer</h1>
  <div class="row">
    <div class="col-md-6">
      <form (ngSubmit)="updateCustomer(customer.id)" #customerForm="ngForm">
        <div class="form-group">
          <label for="name">Name</label>
          <input type="text" class="form-control" [(ngModel)]="customer.name" name="name" required>
        </div>
        <div class="form-group">
          <label for="name">Address</label>
          <input type="text" class="form-control" [(ngModel)]="customer.address" name="address" required>
        </div>
        <div class="form-group">
          <label for="name">City</label>
          <input type="text" class="form-control" [(ngModel)]="customer.city" name="city" required>
        </div>
        <div class="form-group">
          <label for="name">Postal Code</label>
          <input type="number" class="form-control" [(ngModel)]="customer.postalCode" name="postalCode" required>
        </div>
        <div class="form-group">
          <label for="name">Phone Number</label>
          <input type="text" class="form-control" [(ngModel)]="customer.phone" name="phone" required>
        </div>
        <div class="form-group">
          <button type="submit" class="btn btn-success" [disabled]="!customerForm.form.valid">Update</button>
        </div>
      </form>
    </div>
  </div>
</div>


Create Delete Function on Customer-Detail Component

Open and edit `client/src/app/customer-detail/customer-detail.component.ts` then add `Router` module to `@angular/router`.

import { ActivatedRoute, Router } from '@angular/router';

Inject `Router` in the constructor params.

constructor(private router: Router, private route: ActivatedRoute, private http: HttpClient) { }

Add this function for delete book.

deleteCustomer(id) {
  this.http.delete('http://localhost:8080/customer/'+id)
    .subscribe(res => {
        this.router.navigate(['/customers']);
      }, (err) => {
        console.log(err);
      }
    );
}

Add delete button in `client/src/app/customer-detail/customer-detail.component.html` on the right of Edit routerLink.

<div class="row">
  <div class="col-md-12">
    <a [routerLink]="['/customer-edit', customer.id]" class="btn btn-success">EDIT</a>
    <button class="btn btn-danger" type="button" (click)="deleteCustomer(customer.id)">DELETE</button>
  </div>
</div>


Run and Test Grails 3 and Angular 5 Profile CRUD Web Application

Now, it's time to test both client and server (Grails Angular) together.

./gradlew bootRun --parallel

You see the data will be reset because we are using the H2 in-memory database. And here the flow of the full Grails 3 Angular 5 Profiles CRUD web application.

Grails 3 Angular 5 Profile CRUD Web Application Example - Full CRUD Workflow

That it's, if you need the full working source code, you can find it on our GitHub.

That just the basic. If you need more deep learning about Groovy, Grails, and Angular you can take the following cheap course:

Thanks!

Loading…