Using JavaScript and Angular features to remove duplication for common needs in view components (6/9)


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


In the first iteration we have built a menu to navigate in the contact application and we have also implemented the contact creation and contacts display use cases from the client side.
Before implementing the contact modification use case, we will perform our first refactoring task.

In the previous parts we have introduced duplication to implement the handling of notification messages displayed to the user by the parent/view components.
Indeed, the code used to display the error messages are duplicated in each view component.

This is the displayed error message when we try to list contacts from an unreachable  backend (to simulate this situation we just need to not enable the mock):

managecontacts-error-page

And this is the error message when we try to create a contact from an unreachable  backend either (to simulate this situation we also just need to not enable the mock):

createcontact-error-page

Contrary to the contact creation use case, the display of contacts doesn’t need to notify the user with any successful message but the contact deletion/modification that we will soon implement needs to do it. 
So if we don’t remove the duplication now we will go on to increase the level of duplication in the next parts.
The duplication is concentrated on the controller and the template of the view components. The child components have indeed very little duplication.

We cannot say that the code written to perform the message rendering in the view components is complex and big but imagine a professional web application where you may have a dozen of views. In this case, we may easily forget to update all duplicated code.
A single duplicated occurrence not updated and at best you lose the homogeneity of the messages rendering and at worst the messages are not rendered any longer.
It is clearly not professional. So, we will solve this problem now.

Here are the tasks to remove duplication for common needs in view components :



Source code layout

source-code-layout


Using default parameter in the displayErrorMsg() function of the view components

Actually, ContactList and ContactCreation components send explicitly a generic error message to their parent components when an unrecoverable error occurs during backend communication.

ContactList snippet

    function ContactListController($scope, contactService) {
 
        var ctrl = this;
        .  .  .  .  .  .  .
 
        ctrl.fetchAllContacts = function () {
            contactService.fetchAllContacts()
                .then(
                    function (contacts) {
                        ctrl.contacts = contacts;
                    },
                    function (errResponse) {
                        ctrl.onError({
                            msg: "Unexpected error. Thank you to contact us to get a issue solving."
                        })
                    }
                );
        }


ContactCreation snippet 

    function ContactCreationController($scope, contactService) {
        var ctrl = this;
        .  .  .  .  .  .  .
        ctrl.submitAction = function () {
 
            console.log('Saving New Contact', ctrl.contact);
 
            contactService.createContact(ctrl.contact)
                .then(
                    function (newId) {
                        .  .  .  .  .  .  .
                    },
                    function (errResponse) {
                        ctrl.onError({
                            msg: "Unexpected error. Thank you to contact us to get a issue solving."
                        })
                    }
                );
        }

The child components invoke the onError() function with as parameter the same generic message : « Unexpected error. Thank you to contact us to get a issue solving. ».
As remember, the onError() function is bound to the displayErrorMsg()  function of the parent component.

The same message is written twice. It is not desirable. We could hard code this message directly in the displayErrorMsg() function but it would not be a good idea either  because if
 our application grows, it is very likely that for future use cases, some rule checks will be performed  and we could be brought to render specific error messages to the user.

To address this problem, it seems better to specify a default value for the parameter of the displayErrorMsg() function in order to let the caller either sends a specific error message or uses the default value for the error message if he doesn’t pass the parameter.
Here is the updated displayErrorMsg() function :

  this.displayErrorMsg = function (msg = genericErrorMsg) {
      this.hideAllMsg();
      this.currentErrorMsg = msg;
 }

We use the default function parameters feature available since ECMAScript 2015 that allows formal parameters to be initialized with default values if no value or undefined is passed.

Now the child components that want to use the default error message may invoke the onError() callback function in this way :

function (errResponse) {
	ctrl.onError();
}

If later child components need to pass a specific error message, it is still possible.

Extracting a common template from the templates of view components

Here are the actual html templates of the view components.

contactCreationView.html

<div class="row">
    <div class="col-md-6">
        <div ng-show="$ctrl.currentSuccessMsg" class="alert alert-success">
            <strong>Success! </strong>{{$ctrl.currentSuccessMsg}}
        </div>
 
        <div ng-show="$ctrl.currentErrorMsg" class="alert alert-danger">
            <strong>Warn! </strong>{{$ctrl.currentErrorMsg}}
        </div>
    </div>
</div>
 
<div class="row">
    <div class="col-md-6">
        <contact-creation on-contact-added="$ctrl.contactAdded(createdContact)" on-error="$ctrl.displayErrorMsg(msg)"> </contact-creation>
    </div>
</div>

contactsManagementView.html

<div class="row">
    <div class="col-md-10">
        <div ng-show="$ctrl.currentSuccessMsg" class="alert alert-success">
            <strong>Success! </strong>{{$ctrl.currentSuccessMsg}}
        </div>
 
        <div ng-show="$ctrl.currentErrorMsg" class="alert alert-danger">
            <strong>Warn! </strong>{{$ctrl.currentErrorMsg}}
        </div>
 
    </div>
</div>
 
<div class="row">
    <contact-list class="col-md-10" on-error="$ctrl.displayErrorMsg(msg)"> </contact-list>
</div>

The duplication in the message rendering is clear.  We will move it in a new template file : baseParentHeaderTemplate.html.

baseParentHeaderTemplate.html

<div ng-show="$ctrl.currentSuccessMsg" class="alert alert-success">
  <strong>Success! </strong>{{$ctrl.currentSuccessMsg}}
</div>
 
<div ng-show="$ctrl.currentErrorMsg" class="alert alert-danger">
  <strong>Warn! </strong>{{$ctrl.currentErrorMsg}}
</div>

Now we include this template in the templates of view components by using the ngInclude that fetches, compiles and includes an external HTML fragment.

Here is the updated version of contactCreationView.html :

<div class="row">
    <div class="col-md-6">
        <ng-include src="'components/common/baseParentHeaderTemplate.html'"></ng-include>
    </div>
</div>
 
<div class="row">
    <div class="col-md-6">
        <contact-creation on-contact-added="$ctrl.contactAdded(createdContact)" on-error="$ctrl.displayErrorMsg(msg)"> </contact-creation>
    </div>
</div>

Here is the updated version of contactsManagementView.html :

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


Extracting a base controller from the controllers of view components

On the internet, you could find multiple blogs, forums where are explained how you can avoid JavaScript duplication in the AngularJS code.
You have to keep in mind that for many concerns managed by AngularJS, you cannot use plain JavaScript features because AngularJS performs the task for you.
For example, Angular objects such as services or controllers are instantiated by the framework itself. This has important consequences : we cannot use the JavaScript prototype feature exactly as we do with plain JavaScript.

For example to address our need that is having a base controller prototype which each controller of view components inherits properties, here is a simple way to get it with plain JavaScript :
– we declare a constructor for the base controller for view components
– we declare a distinct constructor for each controller of view components
– we assign a distinct instance of the base controller in the prototype of the constructor of these controllers.

The code could look like that :

// child objects
var ContactCreationViewController = function (){
  // specific properties...
return this;
};
 
var ListContactsViewController = function (){
  // specific properties...	
return this;
};
 
// parent object
var BaseViewController = function() {
	  const genericErrorMsg = "Unexpected error. Thank you to contact us to get a issue solving.";
 
this.currentErrorMsg = "";
this.currentSuccessMsg = "";
 
this.hideAllMsg = function () {
    this.currentSuccessMsg = "";
    this.currentErrorMsg = "";
}
 
this.displaySuccessMsg = function (msg) {
    this.hideAllMsg();
    this.currentSuccessMsg = msg;
}
 
this.displayErrorMsg = function (msg = genericErrorMsg) {
    this.hideAllMsg();
    this.currentErrorMsg = msg;
}
 
return this;
};
 
// instantiation of the concrete controllers
ContactCreationViewController.prototype = new BaseViewController();
ListContactsViewController.prototype = new BaseViewController();
 
var contactCreationViewController = new ContactCreationViewController();
var listContactsViewController = new ListContactsViewController();

Unfortunately with Angular objects, you cannot do it in this way since you have not the hand on the instantiation of them.

To get controllers inheritance, a possible way is performing injection of a base controller in another controller by using the $controller service.
This works but personally I find this way defeats the purpose of JavaScript prototype as it relies only on a wrapper pattern where the base controller is instantiated with a parameter that is the concrete controller and performs tasks for it.
By loosing the prototype feature, in concrete controllers we could not have functions implementation  mixing the properties of the base controller prototype with new definition of these properties in. This mechanism allows in a some way to define behavior overriding.

In general, I strive to favor the use of plain and natural JavaScript features when they are compatible with Angular constraints.
A very close way to set the prototype to an Angular object constructor function  as we could do it with plain JavaScript would be to copy all properties of the object we want to use as prototype to the prototype property of the constructor function.
Angular provides an angular.extend(dst, src) function that can do this task. This function indeed extends the destination object dst by copying own enumerable properties from the src object(s) to dst.

The solution we will use relies on the creation of an Angular service that contains common properties of the controllers of view components.
We will inject this service in these controllers and use the angular.extend(dst, src) function to populate the prototype of the functions of concrete controllers.

baseParentComponentController.js

'use strict';
 
angular.module('myContactApp')
    .service('baseParentComponentController', function () {
 
        const genericErrorMsg = "Unexpected error. Thank you to contact us to get a issue solving."
 
        this.currentErrorMsg = "";
        this.currentSuccessMsg = "";
 
        this.hideAllMsg = function () {
            this.currentSuccessMsg = "";
            this.currentErrorMsg = "";
        }
 
        this.displaySuccessMsg = function (msg) {
            this.hideAllMsg();
            this.currentSuccessMsg = msg;
        }
 
        this.displayErrorMsg = function (msg = genericErrorMsg) {
            this.hideAllMsg();
            this.currentErrorMsg = msg;
        }
 
    });


contactsManagementView.js

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


contactCreationView.js

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

You can see that we have performed angular.extend(ContactCreationController.prototype, baseParentComponentController); and not angular.extend(ContactCreationController, baseParentComponentController); because we don’t want to populate directly the child controller but the prototype property to enable the JavaScript prototype feature.

Updating index.html with the new created resources

Here is the updated index.html

<!DOCTYPE html>
<html ng-app="myContactApp">
 
<head>
    <title>Contacts and Groups Management (Angular JS)</title>
    <style>
 
    </style>
 
    <!-- JQUERY -->
    <script src="lib-js/jquery-2.2.3.js"></script>
 
    <!-- bootstrap JS v3.3.6 -->
    <script src="lib-js/bootstrap.js"></script>
 
    <!-- bootstrap CSS v3.3.6 > -->
    <link rel="stylesheet" type="text/css" href="css/bootstrap.css">
 
    <!-- angular lib -->
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-messages.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-mocks.js"></script>
 
 
    <!-- angular extension lib -->
    <script src="lib-js/angular-ui-router.js"></script>
 
    <!-- app  -->
    <link rel="stylesheet" type="text/css" href="app.css">
    <script src="app.js"></script>
 
    <!--  native types enhancing and utility functions -->
    <script src="util/my-util.js"></script>
 
 
    <!--common components-services-->
    <script src="components/common/baseParentComponentController.js"></script>
 
 
    <!-- services -->
    <script src="services/contactService.js"></script>
 
    <!-- mock services -->
    @mockServices@
 
 
    <!-- contact management components -->
    <script src="components/manageContacts/contactsManagementView.js"></script>
    <script src="components/manageContacts/contactList.js"></script>
    <script src="components/manageContacts/myAddress.js"></script>
 
    <!-- contact creation components -->
    <script src="components/createContact/contactCreationView.js"></script>
    <script src="components/createContact/contactCreation.js"></script>
 
 
</head>
 
<body>
 
    <div class="container-fluid">
 
        <div class="row">
            <div class="col-md-2 border1PxWithNoPadding">
                <ul class="nav nav-pills nav-stacked">
                    <li ui-sref-active="active"><a ui-sref="manageContacts">Manage existing contacts</a></li>
                    <li ui-sref-active="active"><a ui-sref="createContacts">Create a new contact</a></li>
                </ul>
            </div>
 
            <div class="col-md-10">
                <ui-view></ui-view>
            </div>
        </div>
    </div>
</body>
 
</html>

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/#/createContacts?mock

Downloading the source code

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

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

Ce contenu a été publié dans Angular, AngularJS, Bootstrap, JavaScript, rest, 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 *