AngularJS: From Prototyping to Functional Code
Table of Contents
- 1 Introduction
- 2 The prototype: An enhanced CRUD
- 3 Choosing our tools
- 4 Setting up the application
- 5 Building the prototype
- 6 Deleting clients
- 7 Creating clients
- 8 Refactoring for functionality
- 9 Closing comments

##1 Introduction
Prototyping is capturing an unpolished idea into code so someone else, maybe our boss, client, or mother, can love or hate it. It's always necessary to get some feedback from users (and the sooner, the better).
It's just a prototype, there's a good chance it will never come to life as a real product. It may only serve as research for launching a new product or service, a pitch for a potential investor, or simply a project we are developing during our spare time. So that begs the question, ‘How much code is worth putting into our prototype?'
As programmers, one of our main goals is to avoid rework as much as possible. The ideal scenario would be that our prototype is functional enough that our client has a mental image of how the new product will behave, and at the same time, the code is minimal and flexible enough to let us make the necessary adjustments so our prototype evolves until it transforms into the final project we are going to deploy for our end users.
In this article, we will approach a practical case with AngularJS; starting with a simple prototype, we will apply some strategies with the aim of (once our prototype has been approved) scaling it gradually in order to achieve a shippable and maintainable product.
##2 The prototype: An enhanced CRUD
We will work through a simple CRUD implementation, adding all the necessary interaction to let our user perform client management tasks within a single window. This includes adding functionality like form validations, text formatting, search filters, client removal and inline editing, all of these will make use of the tools AngularJS provides us. Later, assuming the client has accepted our prototype, we will apply a series of refactors to our code. The prototype will evolve from using hardcoded data and lacking structure, into a project capable of actually performing the tasks simulated in its early prototype stage and with a code structure that will let us easily add new functionality. To achieve this goal, we will use filters, services, directives and other AngularJS tools.
##3 Choosing our tools
We only need our trusty old text editor (syntax highlight is always a plus) and the latest version of AngularJS.
Giving the stakeholders something other than a white background with black text gives the prototype more weight and visual appeal. This increases the chance of adoption. Bootstrap is great for this because it is easy to integrate and customize.
We recommend using tools like yeoman to build our scaffolds and generator-angular to generate our controllers, factories and other AngularJS resources. Using frontend pre-processors like HAML, SASS and Coffeescript are optional, the choice is left to the reader. In this article we are using HTML and Javascript.
##4 Setting up the application
For our prototype, we can borrow any of the starter templates from the bootstrap's examples section. We just have to make sure we are including Bootstrap and AngularJS to our index.html and we are ready to go.
<head>
<title>AngularJS Prototype</title>
<!-- Bootstrap main css file -->
<link href="/styles/bootstrap.css rel="stylesheet" />
<!-- Bootstrap template css file -->
<link href="/styles/sticky-footer.css rel="stylesheet" />
<!-- Angular js file -->
<script src="/scripts/angular.js"></script>
</head>
Note: As we create new Javascript resources, we need to add them to the head of our document, just below <script src="/scripts/angular.js"></script>.
##5 Building the prototype
The initial HTML content of our application is pretty simple:
<!-- index.html -->
<div class="container">
<div class="page-header">
<h1>Clients</h1>
</div>
</div>
As we can see, div.container looks like the perfect place for our AngularJS application to live. First we initialize our Angular application under the name of clientsApp in the file scripts/app.js as follows:
angular.module('clientsApp', []);
We need to put all the client handling logic somewhere in our code. We can define a controller named UsersCtrl inside the file `scripts/controllers/clientsCtrl.js:
angular.module('clientsApp').controller('ClientsCtrl', function($scope) {
});
Then, in our index.html we include the ng-app and ng-controller directives, so Angular knows where to take control from and over which scope it should operate:
<div class="container" ng-app="clientsApp" ng-controller="ClientsCtrl">
...
</div>
We are ready to start our prototype! Let's take a deep breath and get down to work.
###5.1 Listing users
The first task we want to accomplish is pretty straight-forward - list available clients - but, where is this client information coming from? We are barely starting our prototype, so we don't have an API (indeed, not even a backend) to interact with; our experience and web tendencies suggest we might deal with a JSON API, and it might be REST compliant with the usual idioms for reading/writing information (GET, POST, PUT/PATCH, DELETE), but taking these assumptions as true in early prototype stages could result in wasting time and work. We should wait until we get more information, and more importantly, we should wait until our project is actually approved.
How can we overcome this uncertainty? The only thing we know for sure is that we need to manipulate data inside our application in order to execute (or simulate?) tasks.
####5.1.1 Faking our data for the prototype
Thanks to the fact that AngularJS works with Javascript objects, better known as Plain Old Javascript Objects (POJOs), for data models, we have the ease of handling test data inserted directly inside our controllers, no matter the source of the data, or the implemented standard to communicate with the backend. So the one thing we know for sure is that this data will end up stored inside a basic Javascript data structure, for example, an array somewhere inside our scope, and our AngularJS views/directives will be able to manipulate this data without any problems. Let's use this affirmation to our advantage and load the test data inside our controller's scope.
// scripts/controllers/usersCtrl.js
angular.module('clientsApp').controller('ClientsCtrl', function($scope) {
$scope.clients = [
{ id: 1, name: 'John', age: 25, percentage: 0.3 },
{ id: 2, name: 'Jane', age: 39, percentage: 0.18 },
{ id: 3, name: 'Jude', age: 51, percentage: 0.54 },
{ id: 4, name: 'James', age: 18, percentage: 0.32 },
{ id: 5, name: 'Javier', age: 47, percentage: 0.14 }
];
});
And inside our view, we can use the trusty ng-repeat directive to show the client data inside a table, just as we regularly do in a fully-featured backend-powered AngularJS application.
<!-- index.html -->
<div class="container" ng-app="usersApp" ng-controller="UsersCtrl">
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Age</th>
<th>Percentage</th>
</tr>
</thead>
<tbody>
<tr ng-repeat='client in clients'>
<td>{{ client.id }}</td>
<td>{{ client.name }}</td>
<td>{{ client.age }}</td>
<td>{{ client.percentage }}</td>
</tr>
</tbody>
</table>
</div>

This approach has been very useful in my experience when the main goal is not to show how we interact with a backend, but how our application will behave while interacting with the user. If we are lucky enough, our project will be accepted and, just after that, we will worry about handling actual data, but for now, our client array works just fine.
###5.2 Formatting information
Oftentimes, when we are showing a prototype, the client misses the primary goal of the demonstration because of minimal (or even insignificant) interface details. Does "Hey, why aren't those numbers formatted as percentage values?" sound familiar to you? These kinds of details can result in the client ignoring or overlooking key details and losing focus of the whole picture.
Really minor details such as that unformatted number being automatically calculated may only require a couple of minutes to be solved, but no one warned us about this. The small details are as important as the whole picture - don't let these details devalue our prototype.
Inside our test data we have some numerical (percentage) values, and because of the data context, our user would expect these values to be displayed properly formatted; let's implement a quick solution:
$scope.percentageOf = function(percentage) {
return percentage * 100 + ' %';
};
A simple controller method which formats the output can do the trick just for now. At this moment, range and decimal validations and other stuff are unnecessary because we have control over the test data, and we are cautious enough to not include erroneous data in our demo.
We just need to call this method inside our view, as simple as including it right inside the client.percentage interpolation:
<table class="table">
<thead>
<tr>
...
<td>{{ percentageOf(client.percentage) }}</td>
</tr>
</thead>
</table>

Done! We have formatted the data in a really easy way. Now the data will show up in a basic, but functional way, and if this makes our client happy, we can (hopefully, in the near future) implement a more robust, complete and reusable solution.
##6 Deleting clients
Once we are done with Retrieve related tasks, it's time to start manipulating the client data. Deleting clients is the easiest task to accomplish and thanks to the nature of the data we are using, it's accomplished simply by removing a desired element from our array.
Let's add a delete method inside our ClientsCtrl controller, which will receive the object to be deleted:
// scripts/controllers/usersCtrl.js
$scope.delete = function(client) {
var index = $scope.clients.indexOf(client);
$scope.clients.splice(index, 1);
};
Inside our view, we should incorporate a Delete button inside the ng-repeat directive, so that every client can be individually deleted.
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Age</th>
<th>Percentage</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr ng-repeat='client in clients'>
<td>{{ client.id }}</td>
<td>{{ client.name }}</td>
<td>{{ client.age }}</td>
<td>{{ percentageOf(client.percentage) }}</td>
<td>
<a class='btn btn-danger' href='' ng-click='delete(client)'>
Delete
</a>
</td>
</tr>
</tbody>
Let's test. Refresh the browser and click on random Delete buttons and...

It works! We have implemented limited delete functionality - just enough to allow our client to visualize the behavior. It's worth mentioning that if we refresh the browser, the "deleted" clients will respawn, because our data is preloaded inside our controller.
##7 Creating clients
OK, we are displaying clients inside a table and deleting clients - we are halfway through our CRUD implementation!
Next, let’s add the ability to add new clients to the table. We’ll create a form to collect all the information related to the new client. It will verify the validity of the provided information and, when valid, will add this information to the existing client list. This validation provides information to our user through error messages when they enter invalid data.
###7.1 New client form
First things first, we should provide our user with the tools he/she needs to create new records in our "database." The main idea is to provide a form which lets our user input information. We create a regular HTML form tag and add some Angular directives to accomplish this goal.
Let’s create a form with three text fields, name, age and percentage. The ng-model directive will help us store this information inside an object which will later be submitted to the controller.
<form role='form'>
<div class='form-group'>
<label for='name'>Name:</label>
<input class='form-control' ng-model='newClient.name' type='text'>
</div>
<div class='form-group'>
<label for='age'>Age</label>
<input class='form-control' ng-model='newClient.age' type='number'>
</div>
<div class='form-group'>
<label for='percentage'>Percentage</label>
<input class='form-control' ng-model='newClient.percentage' type='number'>
</div>
</form>
Once the form is built and its inputs are associated with the newClient object, we need to add a submit input. This input, besides submitting the information to our Angular controller, should prevent the submission of the HTTP request. Otherwise, the loaded page will refresh and we will lose all the information stored during this prototype execution. We can accomplish this task in two different ways. We can, add a ng-submit to the form or include a ng-click directive to the submit button. Let’s implement the latter; when the user clicks the submit button, our ng-click directive will call the create method from ClientsCtrl controller (we will create this later).
<form role='form'>
...
<button ng-click='create()'>
Save
</button>
</form>
When this information is submitted, we expect our new client to be displayed in the clients list; in other words, it should be added to the existing client collection inside our scope. Does this sound like adding a new element to an array? Sure it does, so that is precisely what we should do.
Let’s add the create method inside our ClientsCtrl controller. It will receive the new client object to be added and we will append (or push) it into our array, something like this:
$scope.create = function() {
$scope.newClient.id = $scope.clients.length + 1;
$scope.clients.push($scope.newClient);
$scope.newClient = null;
};
Notice how we simulated the incremental client id by calculating the clients array length. Refresh the browser and create a new client through the form.

Click on the submit button and see the new client added to the list. It works!

It isn't as spectacular as we might have expected. Our form accepts invalid and even blank data. We can improve this flow by including some client-side validations.
####7.1.1 Validations
We can easily add client-side validations to our form through Angular directives. We insert these directives directly into the HTML input tags. Depending on the desired validation, we use different directives. We will add a couple of simple validations: required validation for all the inputs and min max validatons for numeric inputs.
<form role='form'>
<div class='form-group'>
<label for='name'>Name:</label>
<input class='form-control' ng-model='newClient.name' type='text' required>
</div>
<div class='form-group'>
<label for='age'>Age</label>
<input class='form-control' ng-model='newClient.age' type='number' required max='100' min='1'>
</div>
<div class='form-group'>
<label for='percentage'>Percentage</label>
<input class='form-control' ng-model='newClient.percentage' type='number' required max='1' min='0'>
</div>
<button class='btn btn-primary' ng-click='create()'>
Save
</button>
</form>
It is also necessary to add the novalidate attribute to our form. Otherwise, recent web browsers will try to apply their own validation rules based on the data types and prevent our Angular-powered validations from being executed.
<form novalidate role='form'>
</form>
It’s good practice to deactivate the submit button when form data is invalid. We will use the ng-disabled directive to toggle the submit button. To accomplish this, we just have to give a name to our form. Angular will create an object associated with this name. This allows us to check the state of the form as a whole or to check individual attributes. For our prototype, we will just verify the state of the form's $valid attribute.
<form name='clientForm' novalidate role='form'>
...
<button class='btn btn-primary' ng-click='create()' ng-disabled='clientForm.$invalid'>
Save
</button>
</form>
And finally, to improve our user's experience, let’s add error messages for each attribute inside our form. Angular lets us identify validation when our data is missing. By giving each input a name, using attribute.$error.validation and the ng-show directive inside each form-group, we can explain to our user what is wrong with the data he/she has provided:
<form name='clientForm' novalidate role='form'>
<div class='form-group'>
<label for='name'>Name:</label>
<input class='form-control' ng-model='newClient.name' name='name' type='text' required>
<span class='help-block' ng-show='clientForm.name.$error.required'>Name is required</span>
</div>
<div class='form-group'>
<label for='age'>Age</label>
<input class='form-control' ng-model='newClient.age' name='age' type='number' required max='100' min='1'>
<span class='help-block' ng-show='clientForm.age.$error.required'>Age is required</span>
<span class='help-block' ng-show='clientForm.age.$error.min'>Age should be greater than 1</span>
<span class='help-block' ng-show='clientForm.age.$error.max'>Age should be lesser than 100</span>
</div>
<div class='form-group'>
<label for='percentage'>Percentage</label>
<input class='form-control' ng-model='newClient.percentage' name='percentage' type='number' required max='1' min='0'>
<span class='help-block' ng-show='clientForm.percentage.$error.required'>Percentage is required</span>
<span class='help-block' ng-show='clientForm.percentage.$error.min'>Percentage should be greater than 0</span>
<span class='help-block' ng-show='clientForm.percentage.$error.max'>Percentage should be lesser than 1</span>
</div>
<button class='btn btn-primary' ng-click='create()' ng-disabled='clientForm.$invalid'>
Save
</button>
</form>

We just turned our form into a robust component and considerably increased the user’s experience. This shows our client how are we going to handle erroneous input provided by the user. Of course there are other custom validations that might be convenient to handle through our form, but right now, for the scope of our prototype, this should be enough.
###7.2 Editing users
Our prototype is almost ready, we just need one more CRUD action. Our edit process will work directly on the client object to be modified. In this case, the same rules and validations we just implemented on the above section would apply here and obviate the need for more validations.
####7.2.1 Inline edit
For this use case, our client has requested that the process of updating clients should be done through an inline-edit component. This is when a table's row toggles from read-only to an editable form and toggles back to read-only after finishing the update process, (displaying the updated information).
The first thing we need to do is to build the alternative representation of our client row. We add a new tr to our HTML structure just after each existing tr representation. Each of the contained td’s will have a text input associated to each one of the object's attributes through the ng-model directive, just as we did before when we created new clients.
<tbody ng-repeat='client in clients'>
<tr>
<!-- read-only cells -->
</tr>
<tr>
<td>{{ client.id }}</td>
<td>
<input class='form-control' ng-model='client.name' type='text'>
</td>
<td>
<input class='form-control' ng-model='client.age' type='text'>
</td>
<td>
<input class='form-control' ng-model='client.percentage' type='text'>
</td>
<td></td>
</tr>
</tbody>
Notice we moved the ng-repeat directive to the table's tbody so we can include two tr tags on each iteration. Note: It is possible to achieve this behavior in other ways.

We are getting close, but we need to display the table rows in a conditional way. ng-if to the rescue! With this directive, it is possible to show/hide (more precisely, to insert or not) HTML tags depending on a certain condition. What condition should we evaluate?
We can use an active object approach where only the active object gets to be displayed on edit mode and the rest of the objects will be displayed on read-only mode. Transforming this explanation into code results in the following HTML:
<tbody ng-repeat='client in clients'>
<tr ng-if='client != activeClient'>
<!-- read-only cells -->
</tr>
<tr ng-if='client == activeClient'>
<!-- editable cells -->
</tr>
</tbody>
Now, we need to add a couple of buttons to edit/update the desired client object, but the only thing these buttons will actually do is toggle the activeClient object. We execute these methods through the ng-click directive:
<tbody ng-repeat='client in clients'>
<tr ng-if='client != activeClient'>
<!-- read-only cells -->
<td>
<a class='btn btn-primary' href='' ng-click='edit(client)'>
Edit
</a>
<a class='btn btn-danger' href='' ng-click='delete(client)'>
Delete
</a>
</td>
</tr>
<tr ng-if='client == activeClient'>
<!-- editable cells -->
<td>
<a class='btn btn-primary' href='' ng-click='update(client)'>
Update
</a>
</td>
</tr>
</tbody>
And last, but not least, we have to implement both methods in the controller. We initialize activeClient with a null value in order to be more explicit through our code. We create the edit method and point activeClient to a desired client. update will return activeClient to its initial null state, deactivating the inline edition in the view. The implementation looks like this:
$scope.edit = function(client) {
$scope.activeClient = client;
};
$scope.update = function(client) {
$scope.activeClient = null;
};

Our implementation has a placebo effect (even the update method makes no use of the received parameter), but it shows our client in great detail how are we going to update information through the application, and it only took a few lines of code.
Everything seems to be in place, we have finished our prototype. Now the moment of truth: Demo to our clients. Hopefully they will like it, but in case they ask for modifications, we should have no fear, we can easily adapt our prototype as we have a small amount of code to deal with. Let's cross our fingers...
##8 Refactoring for functionality
Your client is happy with the demo: he/she gave us the green light. Let's get down to work!
We will apply changes to our small prototype so that it can actually perform the production tasks. We need to refactor and introduce some new services and directives for a more robust implementation that is suitable for production.
###8.1 Plugging in a JSON backend
Luckily while we were celebrating the success of our prototype, the backend guys worked quickly to build a JSON API for our application to interact with. The days where our application had to use fake data are over! Let's do the corresponding adaptations so our code can consume the API. If we are careful enough, these modifications won't have a huge impact on our existing code.
###8.2 Refactor: Factory to handle clients
The most appropriate thing to do is to centralize all the code that interacts with the backend. This protects against API endpoint changes. We could address all the required changes to a single spot and our controller won't even notice the changes.
We are going to use a factory, which encapsulates all the backend interactions. Internally, the factory will use the $http service provided by AngularJS out of the box. Our factory implementation will use the $http.get, $http.post, $http.put, and $http.delete methods to interact with the remote data as required.
// scripts/factories/clients.coffee
angular.module('clientsApp').factory('Clients', function($http) {
var BASE_URL = '/clients';
return {
all: function() {
return $http.get(BASE_URL);
},
create: function(client) {
return $http.post(BASE_URL, client);
},
update: function(client) {
return $http.put(BASE_URL + '/' + client.id, client);
},
delete: function(id) {
return $http.delete(BASE_URL + '/' + id);
}
};
});
In order to use this factory, we need to inject it into our ClientsCtrl, so the controller's methods can interact with it:
angular.module('clientsApp').controller('ClientsCtrl', function($scope, Clients) {
...
});
Our controller is ready to consume remote data from the backend. The first thing we need to do is to stop using hardcoded data and get it from the backend instead. We are using the method all we implemented earlier in our Clients factory to achieve this task, as follows:
Clients.all().success(function(data) {
$scope.clients = data;
});
This way, the object clients located inside our $scope will still be handling an array, the difference is that this array contains real data, and the rest of our application didn't even notice the change.
Now, we also need the methods create, update and delete to use the Clients service, but at the same time, they still have to maintain the clients collection stored inside $scope.clients (this is our "local" data copy). Thanks to the way we implemented our Clients factory, this task won't be too hard to achieve:
$scope.create = function() {
Clients.create($scope.newClient).success(function(data) {
$scope.clients.push(data);
$scope.newClient = null;
});
};
$scope.delete = function(client) {
return Clients.delete(client.id).success(function(data) {
var index = $scope.clients.indexOf(client);
$scope.clients.splice(index, 1);
});
};
$scope.update = function(client) {
Clients.update(client).success(function(data) {
$scope.activeClient = null;
});
};
Excellent, our application is now handling remote data. The adaptations were simple, yet effective, and our views were not affected by these changes.
###8.3 Refactor: Directive to validate integer values
Previously we mentioned that our form could implement more specific validations. For example, the age field could limit the input value to only integers (with no decimals). We can create a new data validation directive. The main advantage of this solution is that it will not be dependent on the form and we will be able to use this validation in other places.
For simplicity (and because it suits our needs), we will borrow the directive implementation right from Angular's documentation, specifically from the custom validation section inside Forms.
// scripts/directives/integer.coffee
var INTEGER_REGEXP = /^\-?\d+$/;
angular.module('clientsApp').directive('integer', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$validators.integer = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) return true;
if (INTEGER_REGEXP.test(viewValue)) return true;
return false;
};
}
};
});
We just need to add the integer directive to the age input field inside the form and an error message associated to this new validation and our form will be finished.
<form name='clientForm' novalidate role='form'>
...
<input class='form-control' ng-model='newClient.age' name='age' type='number' required max='100' min='1' integer>
<span class='help-block' ng-show='clientForm.age.$error.integer'>Age should be an integer</span>
...
</form>

###8.4 Refactor: Filter to show percentage correctly
Now that we know our project will (eventually) reach production, it's worth it to invest a little more time in our code. From experience, we know that aiming for reusability is the way to go.
Let's take the percentage formatting logic out of the controller and put it somewhere else for further use, for example, a filter. This filter will behave just like Angular filters do, but will satisfy a specific requirement of our application, and will be available everywhere.
First, we need to get rid of the percentageOf method from our controller and implement the same logic inside a filter:
// scripts/filters/percentage.coffee
angular.module('clientsApp').filter('percentage', function() {
return function(value) {
return value * 100 + ' %';
};
});
And last, but not least, we have to adjust our view; instead of calling the percentageOf method, we are processing the content with the percentage filter.
<!-- before -->
<td>{{ percentageOf(client.percentage) }}</td>
<!-- after -->
<td>{{ client.percentage | percentage }}</td>

It works! This solution did not require too much extra code, and we have the benefit of reusing this filter in any view, controller or even other filters.
###8.5 Refactor: Directive for inline editing
We left the most "entertaining" part for the end; the inline edit feature we have right now works, but we can improve it. For our prototype, the active approach worked pretty well, but we can switch to an isEditing approach for our final version, this approach will help our component to become more flexible. Also, if we manage to remove this logic from the controller and centralize it elsewhere, we can easily include the cancel feature to stop editing and restore the initial data. We are building a directive to solve both issues.
First, we need to move the view code someplace where the directive can reach it - a script block with a type of text/ng-template can store this template:
<script id='client.html' type='text/ng-template'>
<td ng-if-start='client != activeClient'>{{ client.id }}</td>
<td>{{ client.name }}</td>
<td>{{ client.age }}</td>
<td>{{ client.percentage | percentage }}</td>
<td ng-if-end>
<a class='btn btn-primary' href='' ng-click='edit(client)'>
Edit
</a>
<a class='btn btn-danger' href='' ng-click='delete(client)'>
Delete
</a>
</td>
<td ng-if-start='client == activeClient'>{{ client.id }}</td>
<td>
<input class='form-control' ng-model='client.name' type='text'>
</td>
<td>
<input class='form-control' ng-model='client.age' type='text'>
</td>
<td>
<input class='form-control' ng-model='client.percentage' type='text'>
</td>
<td ng-if-end>
<a class='btn btn-primary' href='' ng-click='update(client)'>
Update
</a>
</td>
</script>
This way, our directive can reference the template by id (url). If our backend friends want to, they can dispatch this template server-side and our directive will still work.
We used a variant of the ng-if directive, ng-if-start and ng-if-end; with this variant, we can display content conditionally without the need of a parent tag just by indicating where the conditional block starts and where it ends.
The time to implement our directive has come; in the same way our controller implemented create, update and delete methods and the activeClient object, our directive can implement some methods to toggle a boolean value. We can call them editClient, updateClient, deleteClient and isEditing, as follows:
// scripts/directives/clientRow.coffee
angular.module('clientsApp').directive('clientRow', function() {
return {
restrict: 'A',
templateUrl: 'client.html',
link: function(scope, element, attrs) {
scope.isEditing = false;
scope.editClient = function() {
scope.isEditing = true;
};
scope.updateClient = function() {
scope.isEditing = false;
};
scope.deleteClient = function() {};
}
};
});
Also, as we didn't specify a scope for our directive, updateClient can also invoke the update method from the parent scope:
angular.module('clientsApp').directive('clientRow', function() {
return {
...
link: function(scope, element, attrs) {
...
scope.updateClient = function() {
scope.update(scope.client);
scope.isEditing = false;
};
}
};
});
The same way, deleteClient invokes the delete method:
angular.module('clientsApp').directive('clientRow', function() {
return {
...
link: function(scope, element, attrs) {
...
scope.deleteClient = function() {
scope.delete(scope.client);
};
}
};
});
And now our template, instead of using the edit, update and delete methods, will use its own editClient, updateClient and deleteClient methods, and also its own isEditing variable to conditionally display the rows:
<script id='client.html' type='text/ng-template'>
<td ng-if-start='!isEditing'>{{ client.id }}</td>
...
<td ng-if-end>
<a class='btn btn-primary' href='' ng-click='editClient()'>
Edit
</a>
<a class='btn btn-danger' href='' ng-click='deleteClient()'>
Delete
</a>
</td>
<td ng-if-start='isEditing'>{{ client.id }}</td>
...
<td ng-if-end>
<a class='btn btn-primary' href='' ng-click='updateClient()'>
Update
</a>
</td>
</script>
We can also add the cancel feature and restore the initial value of the object. First, our editClient method will store a copy of the initial object for future restoration (in case we need it):
angular.module('clientsApp').directive('clientRow', function() {
return {
...
link: function(scope, element, attrs) {
...
scope.editClient = function() {
scope.original = angular.copy(scope.client);
scope.isEditing = true;
};
}
};
});
Second, we add a new cancelEdit method to our directive; this method will restore the object to its initial state and will also deactivate the edit mode by toggling isEditing.
angular.module('clientsApp').directive('clientRow', function() {
return {
...
link: function(scope, element, attrs) {
...
scope.cancelEdit = function() {
scope.isEditing = false;
angular.copy(scope.original, scope.client);
};
}
};
});
And finally, we add a Cancel button to each client row, which will execute our newly created cancelEdit method.
<td ng-if-start='isEditing'>{{ client.id }}</td>
...
<td ng-if-end>
<a class='btn btn-primary' href='' ng-click='updateClient()'>
Update
</a>
<a class='btn btn-danger' href='' ng-click='cancelEdit()'>
Cancel
</a>
</td>
The Angular template ends up as follows:
<script id='client.html' type='text/ng-template'>
<td ng-if-start='!isEditing'>{{ client.id }}</td>
<td>{{ client.name }}</td>
<td>{{ client.age }}</td>
<td>{{ client.percentage | percentage }}</td>
<td ng-if-end>
<a class='btn btn-primary' href='' ng-click='editClient()'>
Edit
</a>
<a class='btn btn-danger' href='' ng-click='deleteClient()'>
Delete
</a>
</td>
<td ng-if-start='isEditing'>{{ client.id }}</td>
<td>
<input class='form-control' ng-model='client.name' type='text'>
</td>
<td>
<input class='form-control' ng-model='client.age' type='text'>
</td>
<td>
<input class='form-control' ng-model='client.percentage' type='text'>
</td>
<td ng-if-end>
<a class='btn btn-primary' href='' ng-click='updateClient()'>
Update
</a>
<a class='btn btn-danger' href='' ng-click='cancelEdit()'>
Cancel
</a>
</td>
</script>
And the clients table will make use of the directive like this:
| Id | Name | Age | Percentage | Actions |
|---|---|---|---|---|

Note: We could achieve this same result by creating a new controller, say ClientCtrl, and adding a ng-controller directive to the same tag that holds the ng-repeat directive, but based on all the interaction we included into the edit feature, I feel using a directive gives us more flexibility for future changes, besides, it won't create additional n scopes.
##9 Closing comments
We have discussed some use cases focusing on the prototyping process; but we can run into other ones, the main idea is to write the minimum necessary code to achieve the desired data simulations and interactions, always knowing in advance that changes will be required. In the early prototyping stages it is almost impossible to avoid the rework due to the volatile nature of the process, but it can be considerably reduced by not writing unnecessary code, if you feel adventurous enough to write more code/functionality than required, most of that code will have a really short lifespan.
If we talk about the stage after prototyping, it's worth mentioning that the refactor stage is pretty important to ensure ease of maintenance for our code; don't let the joy of an accepted prototype or the urgency of writing functional code distract you from refactoring.
This workflow is pretty flexible, but we need to set boundaries; because of our ability to implement changes in the prototype, our client might keep bringing changes and changes, and we will be prototyping for days and days. Don't get me wrong, I'm not saying be inflexible, it's just important to remember that feedback involves negotiation, and without it, we might end up only building prototypes and not writing a single line of functional code.