Implementing client-side the contact deletion (7/9)


Go back to the table of contents for
Developing a Contact Management Application with Angular 1.5X and Java


Here are the tasks to implement the contact deletion use case from the client side :



Preview of the content that we want to get

preview-deletion-contact

Use case presentation

The contact deletion should be a provided option in the contactList component.

Source code layout

source-code-layout

Updating the contactList component

We will add a new column in the displayed html table.
The column header name is « Actions ». We stay general in the terminology as in the next part we will have to provide another action : the contact modification. The column data will contain a « delete » button that triggered deletes the contact displayed on the row associated to. Here is the updated contactList.html template :

<div class="panel panel-primary">
 
    <!-- heading -->
    <div class="panel-heading">
        <span class="lead">Contacts</span>
    </div>
 
    <!-- body -->
    <div>
 
        <table class="table table-hover table-bordered">
            <!--    <table>-->
 
            <thead class="main-background-color">
                <tr>
                    <th>Id</th>
                    <th>First Name</th>
                    <th>Last Name</th>
                    <th>Birthday</th>
                    <th style="word-wrap: break-word; ">Email</th>
                    <th>Phone</th>
                    <th>Sex</th>
                    <th>
                        <span>Address </span>
                        <span ng-show="$ctrl.isShortAddressMode() ">
                            <a style="font-size: 10px " ng-click="$ctrl.toggleAddressMode() " value="Full ">Full</a>
                        </span>
                        <span ng-show="$ctrl.isFullAddressMode() ">                            
                            <a style="font-size: 10px " ng-click="$ctrl.toggleAddressMode() " value="Short ">Short</a>
                        </span>
                    </th>
                    <th class="col-md-2">Actions</th>
                </tr>
            </thead>
            <tbody> 
                <tr ng-repeat="c in $ctrl.contacts ">
                    <td>{{c.id}}</td>
                    <td>{{c.firstName}}</td>
                    <td>{{c.lastName}}</td>
                    <td>{{c.birthday | date:'MM/dd/yyyy'}}</td>
                    <td>{{c.email}}</td>
                    <td>{{c.phone}}</td>
                    <td>{{c.sex}}</td>
                    <td>
                        <my-address address="c.address " display-mode="$ctrl.addressMode ">
                        </my-address>
                    </td>
                    <td>
                        <button type="button" ng-click="$ctrl.deleteAction(c)" class="btn btn-danger btn-xs width-80px">Delete</button>
                    </td>
                </tr>
            </tbody>
        </table>
        <!-- </div>-->
    </div>
</div>

According to this declaration :

<button type="button" ng-click="$ctrl.deleteAction(c)" class="btn btn-danger btn-xs width-80px">Delete</button>

the deleteAction() function of the controller is called when the delete button is clicked on.
This function doesn’t take as parameter the contact id but directly the contact.
Both are possible. The advantage of passing the object as argument is we could use the Array indexOf() function that accepts as argument an object to retrieve the index of the contact in the array.
If we had passed a contact id in the deleteAction() function, we would have written code to iterate over the array to find the a contact with an id matching with the passed parameter.

Here is the deleteAction() function :

ctrl.deleteAction = function (contact) {
	var index = ctrl.contacts.indexOf(contact);
 
	if (index == -1) {
		return;
	}
 
	contactService.deleteContact(contact.id)
		.then(
			function () {
				ctrl.contacts.splice(index, 1);
				ctrl.onContactDeleted({
					"contactId": contact.id
				});
			},
			function (errResponse) {
				ctrl.onError();
			}
		);
 
}

As for the previous use cases, we invoke a function of the contactService instance to communicate with the backend.

When the backend returns an error, as usually, the failure callback notifies the parent/view component about an error is occurred.

When the deletion is successful, the success callback performs two things :
– removing the deleted contact from the contacts array property of the controller.
– notifying the parent view component about the deletion operation.

The contact removal from the contacts array is performed with the Array splice() method that allows to change the content of an array by removing existing elements and/or adding new elements.
As the method may do many things, it may be a little bit clumsy to use.

Here are three possible usages  :

– array.splice(start)  // delete elements from start index to the last index

– array.splice(start, nbElementToDelete)  
// delete nbElementToDelete elements from start index 

– array.splice(start, nbElementToDelete , item1, item2, …)
// delete nbElementToDelete  elements from start  index and add item1, item2,… from this index

Formal declaration :
array.splice(start, deleteCount  item1, item2, …)

Parameters description:
start  : index at which to start changing the array.
deleteCount (optional) : number element to delete. If it is omitted, it will be equal to (array.length – start).

– item1, item2, ect… (optional) :  The elements to add to the array, beginning at the start index.

In the component declaration, we add onContactDeleted, a new binding property to communicate the deletion successful information to the parent component.
As previous properties used as callbacks, we use the '&' binding type.

Here is the contactList.js file updated :

(function () {
    'use strict';
    //IIFE
 
 
    function ContactListController($scope, contactService) {
 
        var ctrl = this;
        ctrl.contacts = [];
        ctrl.addressMode = 'LONG';
 
        this.$onInit = function () {
            ctrl.fetchAllContacts();
        };
 
        ctrl.fetchAllContacts = function () {
            contactService.fetchAllContacts()
                .then(
                    function (contacts) {
                        ctrl.contacts = contacts;
                    },
                    function (errResponse) {
                        ctrl.onError();
                    }
                );
        }
 
 
        ctrl.isShortAddressMode = function () {
            if (ctrl.addressMode == 'SHORT') {
                return true;
            }
            return false;
        }
 
        ctrl.isFullAddressMode = function () {
            return !ctrl.isShortAddressMode();
        }
 
 
        /**
              user action
          */
        ctrl.toggleAddressMode = function () {
            if (ctrl.addressMode == 'LONG') {
                ctrl.addressMode = 'SHORT';
            } else {
                ctrl.addressMode = 'LONG';
            }
        }
 
 
        ctrl.deleteAction = function (contact) {
            var index = ctrl.contacts.indexOf(contact);
 
            if (index == -1) {
                return;
            }
 
            contactService.deleteContact(contact.id)
                .then(
                    function () {
                        ctrl.contacts.splice(index, 1);
                        ctrl.onContactDeleted({
                            "contactId": contact.id
                        });
                    },
                    function (errResponse) {
                        ctrl.onError();
                    }
                );
 
        }
 
 
    }
 
 
 
    angular.module('myContactApp').component('contactList', {
 
        templateUrl: 'components/manageContacts/contactList.html?' + new Date(),
        controller: ContactListController,
        bindings: {
            onError: '&',
            onContactDeleted: '&'
        }
    });
 
 
 
    //END IIFE
})();

Updating the contactService service to provide a method deleting a contact

Here is the method added to delete a contact :

deleteContact: function(id){
	return $http.delete(url + '/'+id)
			.then(
					function(response){
						return;
					}, 
					function (errResponse) {
						var errorReason = "the contact deletion has encountered an error from the server side";
						console.error(errorReason);
						return $q.reject(errorReason);
					}
			);
}

We follow still the same way that we used in the previously implemented methods of contactService but this time we call the delete() method of the $http service.

Two things to notice:
– the id of the contact to delete is passed in the url of the request sent to the backend.
– in case of successful deletion, we don’t need to return anything to the caller of the service.

Here is the updated contactService.js file :

(function () {
    'use strict';
    //IIFE
 
 
    angular.module('myContactApp')
        .factory('contactService', ContactService);
 
    function ContactService($http, $q) {
 
        var url = 'api/contacts';
 
        return {
            createContact: function (contact) {
                return $http.post(url, contact)
                    .then(
                        function (response) {
                            var newId = MyNgUtil.getIdentifierFromHeaderLocation(response);
                            return newId;
                        },
                        function (errResponse) {
                            var errorReason = "the contact creation has encountered an error from the server side";
                            console.error(errorReason);
                            return $q.reject(errorReason);
                        }
                    );
            },
 
            fetchAllContacts: function () {
                return $http.get(url)
                    .then(
                        function (response) {
                            var contacts = response.data;
 
                            if (contacts == null || contacts.length == 0) {
                                console.info('No contact found');
                            }
                            // convert date in string type to a date type
                            for (var i = 0; i < contacts.length; i++) {
                                if (contacts[i].birthday) {
                                    contacts[i].birthday = new Date(contacts[i].birthday);
                                }
                            }
                            return contacts;
                        },
                        function (errResponse) {
                            var errorReason = "the contacts fetching has encountered an error from the server side";
                            console.error(errorReason);
                            return $q.reject(errorReason);
                        }
                    );
            },
 
            deleteContact: function (id) {
                return $http.delete(url + '/' + id)
                    .then(
                        function (response) {
                            return;
                        },
                        function (errResponse) {
                            var errorReason = "the contact deletion has encountered an error from the server side";
                            console.error(errorReason);
                            return $q.reject(errorReason);
                        }
                    );
            }
 
        }
    }
 
 
    //END IIFE
})();

Updating the view component (contactsManagementView) to take into consideration the new API of the child component (contactList)

The onContactDeleted property added in the contactList component declaration must now be specified when the contactsManagementView template declares a contactList component instance.

Here is the updated version of contactsManagementView.html :

<div class="row">
    <div class="col-md-6">
        <ng-include src="'components/common/baseParentComponentHeaderTemplate.html'"></ng-include>
    </div>
</div>
 
 
<div class="row">
    <contact-list class="col-md-10" on-error="$ctrl.displayErrorMsg(msg)" on-contact-deleted="$ctrl.contactDeleted(contactId)">
    </contact-list>
</div>

contactDeleted() is the contactsManagementView controller method declared as the callback function invoked by contactList when the deletion is successful.

As for contactAdded(), contactDeleted() invokes displaySuccessMsg() to print a notification message to the user. Here is the updated version of contactsManagementView.js :

(function () {
    'use strict';
    //IIFE
 
 
    function ContactsManagementController(baseParentComponentController) {
        var ctrl = this;
        angular.extend(ContactsManagementController.prototype, baseParentComponentController);
 
        ctrl.contactAdded = function (createdContact) {
            ctrl.displaySuccessMsg("Contact with id " + createdContact.id + " added.");
        }
 
        ctrl.contactDeleted = function (contactId) {
            ctrl.displaySuccessMsg("Contact with id " + contactId + " deleted.");
        }
 
    }
 
 
    angular.module('myContactApp').component('contactsManagementView', {
        templateUrl: 'components/manageContacts/contactsManagementView.html?' + new Date(),
        controller: ContactsManagementController
    });
 
 
    //END IIFE
})();

Mocking the DELETE HTTP request deleting a contact

We will return a mocked response when we request the backend to delete a contact.
Here is the added code :

// DELETE CONTACT												  
$httpBackend.whenDELETE(/api\/contacts\/\d*/)
	.respond(function (method, url, data, headers) {
		console.log("mock delete contact with url " + url);
		var idContact = parseInt(MyNgUtil.getIdentifierFromUrl(url));
		var index = MyNgUtil.getIndexWithId(idContact, contacts);
		if (index != -1) {
			contacts.splice(index, 1);
			return [200, {}, undefined];
		}
		return [500, {}, undefined];
	});

In order to display updated data, when we delete a contact, the mocked back end should remove the deleted contact from the contacts array that simulates back end data.
To do it we need to identify the contact id of the deleted contact.
In contactService, we have seen that when we send a contact deleting request, we don’t provide any data to transmit the contact id. The information is contained in the submitted  url :  return $http.delete(url + '/' + id).  
So, a simple way of doing it is parsing and retrieving this information from the url parameter available in the respond() function invoked as callback.
To achieve it, we will reuse the MyNgUtil.getIdentifierFromUrl(url) function that we have indirectly used in the contact creation use case to retrieve from the Location header, the contact id of the created contact.
After retrieving the contact id, we iterate over the contacts array to retrieve the index of the object to remove.
This processing may be common enough in an Angular application. So as getIdentifierFromUrl() and getIdentifierFromHeaderLocation(), we move it in a function of MyNgUtil : getIndexWithId(id, objs).

Here is the full updated version of mockServices.js :

(function () {
    'use strict'; //IIFE
 
 
    var regexMock = new RegExp("/\?.+mock");
    if (!document.URL.match(regexMock)) {
        return;
    }
 
    var myAppDev = angular.module('myContactApp')
 
    .config(function ($provide) {
        $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
    })
 
    .run(function ($httpBackend, $http) {
 
        console.info("mocked backend enabled");
 
        // state	   	    	
        var contacts = [
            {
                id: 1,
                firstName: 'John',
                lastName: 'Doe',
                birthday: '',
                email: 'johndoe@secure.com',
                phone: '0134345454',
                sex: 'MALE',
                address: {
                    addressLine: '154 Unknown Street',
                    city: 'Ideal City',
                    zip: '10101',
                    country: 'Wanted Country'
                }
 
            },
 
 
            {
                id: 2,
                firstName: 'Jane',
                lastName: 'Calamity',
                birthday: new Date("1852-05-01"),
                email: 'janecalimity@secure.com',
                phone: '0134345454',
                sex: 'MALE',
                address: {
                    addressLine: '123 Billy The Kid Street',
                    city: 'Western City',
                    zip: '99999',
                    country: 'World Wide West'
                }
 
            }
 
	    			   ];
 
        var nextContactId = contacts.length + 1;
 
        // behavior
        $httpBackend.whenGET(/\.html.*$/).passThrough();
 
        // GET CONTACTS
        $httpBackend.whenGET("api/contacts").respond(contacts);
 
        // POST CONTACT
        $httpBackend.whenPOST("api/contacts")
            .respond(function (method, url, data, headers) {
                console.log("mocked response for POST contact with data " + data);
                var contact = angular.fromJson(data);
                contact.id = nextContactId;
                contacts.push(contact);
                var responseMockHeaders = {
                    Location: 'api/contacts/' + contact.id
                };
                nextContactId++;
                return [201, {}, responseMockHeaders];
            });
 
 
        // DELETE CONTACT												  
        $httpBackend.whenDELETE(/api\/contacts\/\d*/)
            .respond(function (method, url, data, headers) {
                console.log("mock delete contact with url " + url);
                var idContact = parseInt(MyNgUtil.getIdentifierFromUrl(url));
                var index = MyNgUtil.getIndexWithId(idContact, contacts);
                if (index != -1) {
                    contacts.splice(index, 1);
                    return [200, {}, undefined];
                }
                return [500, {}, undefined];
            });
 
    }); // run end		
 
 
 
})(); //END IIFE

Running the application

As for the previous part, from the command line, under the contact-webapp directory, execute the mvn -Pdev command to include the mock facility to the application.

When the server is started, visit the web page with the mocking backend enabled by browsing  : http://localhost:8095/contact-webapp/#/manageContacts?mock

Downloading the source code

[sdm_download id= »2509″ fancy= »1″]

Next part : Implementing client-side the contact modification (8/9)

Ce contenu a été publié dans Angular, AngularJS, Bootstrap, java, JavaScript, Spring Boot. Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *