Angular State/Country Directive

John Holland AngularJS, JavaScript, Single-Page Application Leave a Comment

A Lesson In ROI: Scratch vs. Adapt Existing Angular Code

On a recent project, I needed to develop an Angular front end for a form that included address information. As part of the address information, there were State and Country fields. I knew I needed to make these select lists, because from a UX perspective, it would be crazy to leave them as regular old text inputs.

I knew I wanted to create directives to implement these select lists. I also knew I probably wasn’t the first developer to ever want to do this.

Incidentally, the longer I develop software the more I find there really isn’t much that is “new” out there. There are different spins on functionality, but rarely do I run across a situation that has never been encountered by myself or someone else. Making the right decisions for the problem at hand involves the right choice for ROI (Return on Investment); choosing to write custom code or adapting existing code to work for our purpose. Each decision has repercussions.

In this blog, I talk about a decision that all developers face every day: how to solve programming problems in the best way with the best use of time. The scenario we’ll talk about shows the use of Angular directives for creating select lists for country and state. In doing so, it provides a good understanding of Angular directives as one possible way they can help make Angular development great.

The Project

I was developing an Angular front end for a form that included address information, including State and Country fields. Given that this is likely a common need, I did what most developers do when given this scenario: I went to Google and searched for it.

I found some results right out of the gate. The directives I found were really big and much more robust than what I had in mind. I was looking for a Toyota Corolla and what I was finding were Cadillac Escalades.

So I again did what I believe most developers would do (I never claimed to be a trend setter).
I found a piece here and another piece there and put them together to do what I wanted it to do. This was to create a common directive that provides a State and a Country select list, without being overloaded with functionality I did not need (which would most likely just get in the way or cause unexpected consequences down the road).

That is not to say that the extra functionality that I am speaking of would not be helpful in some situations, just not in mine.

What I wanted was a simple directive whose purpose in life is solely to produce a select list of States and one for Countries. That’s all I need it to do. Anything else I want to do concerning these select lists, I will do outside of the directive, as I want them to be as light as possible.

Customization

I started by creating the State select list directive first. I named everything state and made it very “state” specific. Then I did some refactoring and changed things to use option so it would be more generic. This way when I copied it to use in creating my Country select list directive, I wouldn’t have to go through and change “state” to “country”. All I had to do was provide a different collection for the options, and it worked as I wanted it to.

I mentioned the refactoring, and as part of that I started down the road of combining the two directives and making a single State/Country select list directive. I would pass in which set of options I wanted it to populate, and then I would only have a single directive to be concerned about. Anyone familiar with S.O.L.I.D. is probably screaming at me right about now.

Then I thought about that first principle of S.O.L.I.D., “Single-responsibility,” which from what I have found is the most commonly known and implemented. So I rolled back to having each be its own directive. This allows for better testability and each is very clear in its intended purpose.

The country options I needed were limited to just a few, so I have included a few more in the directive below. I have coded the entire list of countries before which is fairly lengthy, but thankfully I didn’t need that this time.

If you are looking for all of the Countries, here is what seems to be a pretty good source: https://countrycode.org.

Here is what the HTML for the State and Country select lists look like within the Angular view.

	<state-selectlist ng-model="state" name="state" ng-required="country === 'USA'" emptyoption="Select State"></state-selectlist>
	<country-selectlist ng-model="country" name="country" ng-required="true" emptyoption="Select Country"></country-selectlist>

I will assume you have a common or utility module or whatever you might call it in your situation, so I will just provide the actual directives themselves below.

.directive('stateSelectlist', function() {
	return {
		restrict: 'E',
		template: '<select ng-options="option.key as option.value for option in options"><option value="">{{emptyoption}}</option></select>',
		replace: true,
		scope: true,
		link: function ($scope, element, attributes) {
			$scope.emptyoption = attributes.emptyoption || 'Select Option';
			$scope.selectedOption = '';
			
			$scope.options = [
				{'key': 'AL', 'value': 'Alabama'},
				{'key': 'AK', 'value': 'Alaska'},
				{'key': 'AZ', 'value': 'Arizona'},
				{'key': 'AR', 'value': 'Arkansas'},
				{'key': 'CA', 'value': 'California'},
				{'key': 'CO', 'value': 'Colorado'},
				{'key': 'CT', 'value': 'Connecticut'},
				{'key': 'DE', 'value': 'Delaware'},
				{'key': 'DC', 'value': 'District of Columbia'},
				{'key': 'FL', 'value': 'Florida'},
				{'key': 'GA', 'value': 'Georgia'},
				{'key': 'HI', 'value': 'Hawaii'},
				{'key': 'ID', 'value': 'Idaho'},
				{'key': 'IL', 'value': 'Illinois'},
				{'key': 'IN', 'value': 'Indiana'},
				{'key': 'IA', 'value': 'Iowa'},
				{'key': 'KS', 'value': 'Kansas'},
				{'key': 'KY', 'value': 'Kentucky'},
				{'key': 'LA', 'value': 'Louisiana'},
				{'key': 'ME', 'value': 'Maine'},
				{'key': 'MD', 'value': 'Maryland'},
				{'key': 'MA', 'value': 'Massachusetts'},
				{'key': 'MI', 'value': 'Michigan'},
				{'key': 'MN', 'value': 'Minnesota'},
				{'key': 'MS', 'value': 'Mississippi'},
				{'key': 'MO', 'value': 'Missouri'},
				{'key': 'MT', 'value': 'Montana'},
				{'key': 'NE', 'value': 'Nebraska'},
				{'key': 'NV', 'value': 'Nevada'},
				{'key': 'NH', 'value': 'New Hampshire'},
				{'key': 'NJ', 'value': 'New Jersey'},
				{'key': 'NM', 'value': 'New Mexico'},
				{'key': 'NY', 'value': 'New York'},
				{'key': 'NC', 'value': 'North Carolina'},
				{'key': 'ND', 'value': 'North Dakota'},
				{'key': 'OH', 'value': 'Ohio'},
				{'key': 'OK', 'value': 'Oklahoma'},
				{'key': 'OR', 'value': 'Oregon'},
				{'key': 'PA', 'value': 'Pennsylvania'},
				{'key': 'RI', 'value': 'Rhode Island'},
				{'key': 'SC', 'value': 'South Carolina'},
				{'key': 'SD', 'value': 'South Dakota'},
				{'key': 'TN', 'value': 'Tennessee'},
				{'key': 'TX', 'value': 'Texas'},
				{'key': 'UT', 'value': 'Utah'},
				{'key': 'VT', 'value': 'Vermont'},
				{'key': 'VA', 'value': 'Virginia'},
				{'key': 'WA', 'value': 'Washington'},
				{'key': 'WV', 'value': 'West Virginia'},
				{'key': 'WI', 'value': 'Wisconsin'},
				{'key': 'WY', 'value': 'Wyoming'}
			];
		}
	};
})
	

.directive('countrySelectlist', function() {
	return {
		restrict: 'E',
		template: '<select ng-options="option.key as option.value for option in options"><option value="">{{emptyoption}}</option></select>',
		replace: true,
		scope: true,
		link: function ($scope, element, attributes) {
			$scope.emptyoption = attributes.emptyoption || 'Select Option';
			$scope.selectedOption = '';
			
			$scope.options = [
				{'key': 'USA', 'value': 'United States of America'},
				{'key': 'AFG', 'value': 'Afghanistan'},
				{'key': 'ALB', 'value': 'Albania'},
				{'key': 'DZA', 'value': 'Algeria'},
				{'key': 'ASM', 'value': 'American Samoa'},
				{'key': 'AND', 'value': 'Andorra'},
				{'key': 'AGO', 'value': 'Angola'},
				{'key': 'AUS', 'value': 'Australia'},
				{'key': 'HKG', 'value': 'Hong Kong'},
				{'key': 'IRL', 'value': 'Ireland'},
				{'key': 'ISR', 'value': 'Israel'},
				{'key': 'ITA', 'value': 'Italy'},
				{'key': 'JAM', 'value': 'Jamaica'},
				{'key': 'JPN', 'value': 'Japan'},
				{'key': 'LUX', 'value': 'Luxembourg'},
				{'key': 'MDG', 'value': 'Madagascar'},
				{'key': 'NIC', 'value': 'Nicaragua'},
				{'key': 'PRT', 'value': 'Portugal'},
				{'key': 'SGP', 'value': 'Singapore'},
				{'key': 'GBR', 'value': 'United Kingdom'}
			];
		}
	};
})

Final Thoughts

My blog posts are usually derived from encountering a problem that needs a practical solution, and this is no different.

As developers, we have to make decisions about ROI (Return on Investment) every day as we build applications. We have to determine quickly whether someone else has most likely already done something like what we are wanting to do, or whether this is something we need to build from scratch.

In this instance, I adapt existing code which saved time and, in the end, money. It made more sense to find an example of what I was looking for, take out what I didn’t need, and add things I did need, as opposed to starting from scratch.

Compare this to another Keyhole blog in which we chose to create a from-scratch application due to available solutions making things more complicated than necessary. I believe every decision is unique and should be evaluated on a case-by case-basis.


About the Author
John Holland

John Holland

John Holland is an experienced developer and architect who has been designing and developing database applications since 1987 and Internet development since 1995. Expertise surrounding application design and development focuses on technologies C#, ASP.NET, and PHP.


Share this Post

Leave a Reply