Expandable list component for AngularJS (1.5)

Richard

For several of my projects I required a list where input items could be dynamically added and removed.
Because i saw uses for this over and over i created a component which i’m sharing with you here.
The component ended up like the code below.
Where i used a template generation function in order to create the ul and li elements.
I’m using the angular.element function to create nodes as a method of preference.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
function mpExpandableListController(){
var vm = this;

vm.add = add;
vm.manipulatable = true;
vm.remove = remove;

// If items was not declared, create new array.
if(!vm.items) {
vm.items = [];
}

// If the items array is empty, create first item.
if(vm.items.length === 0) {
add();
}

/**
* Add an item to the item list.
*/
function add(){
vm.items.push({});
setManipulatable();
}

/**
* Remove an item from the item list.
* @param item The item to remove
*/
function remove(item){
vm.items.splice(vm.items.indexOf(item), 1);
setManipulatable();
}

/**
* Set the flag which designates that the list may be manipulated.
*/
function setManipulatable() {
vm.manipulatable = vm.items.length > 1;
}
}

/**
*
* @param $element The element as declared in the HTML
* @param $attrs The attributes for said element
* @returns {string} A template for this component.
*/
function template($element, $attrs){
var container = angular.element('<ul>'),
item = angular.element('<li ng-repeat="item in list.items">'),
footer = angular.element('<li>');

container.addClass($attrs.universalClass);
item.addClass($attrs.itemClass);
footer.addClass($attrs.footerClass);

item.html($element.find('item').html());
footer.html($element.find('footer').html());

container.append(item);
container.append(footer);

return container[0].outerHTML;
}

var mpExpandableList = {
bindings: {
footerClass: '@',
itemClass: '@',
items: '=',
universalClass: '@'
},
controller: mpExpandableListController,
controllerAs: 'list',
template: template
};

angular
.module('my.project')
.component('mpExpandableList', mpExpandableList);

As you can see the template function adds information such as class names from the attributes and also creates the item li tag with the ng-repeat needed to populate our list.
And after this it appends the footer li tag which should ideally contain the add item link/button.
So now we have our list, which we can use as such:

1
2
3
4
5
6
7
8
9
10
11
12
<mp-expandable-list items="main.model" universal-class="list-main" item-class="list-item" footer-class="list-footer">
<item>
<span>{ {item.key}}</span>
<input type="text" ng-model="item.value">
<a href="#" ng-click="list.remove(item)" ng-if="list.manipulatable"></a>
</item>
<footer>
<a href="#" class="add-line" ng-click="list.add()">
Add an item
</a>
</footer>
</mp-expandable-list>

As you can see there are bindings for both list and item.

  • The list binding is for the controller for the expandable list.
    • has two functions (add & remove) for adding and removing lines.
    • has a flag called manipulatable which is true as long as the list may add/remove items.
  • The item binding is for the individual items in the list.

Note that this is not a best practice, nor is it necessarily the best solution for you or your project.
It is just a component that may help you, or be the basis for something else.
You can play with this component on plunker.

Experiences at Google IO 2016 "this" in javascript