Expandable list component for AngularJS (1.5)

by Richard — 3 minutes

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.

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:

<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.

meerdivotion

Cases

Blogs

Event