How to dynamically generate DOM with Angular.js?

angularjs, javascript

Solution

Within the angular world, DOM manipulation is accomplished using angularjs directives. Here is the angular documentation on directives: https://docs.angularjs.org/guide/directive, you would do well to read through this.

Here is some sample code that will accomplish the idea of your psuedo code:

var myApp = angular.module('myApp', []);

myApp.controller('MyController', function ($scope){
  $scope.items = [
    42, "hello, world!", 3.14, "i'm alive!"
    ]
});

myApp.directive('myInputDirective', function () {
  return {
    restrict: 'E',
    replace: true,
    template: '<div></div>',
    link: function (scope, element, attrs) {
      if (typeof scope.current === "string") {
        element.append('<input type="text">');
      } else {
        element.append('<input type="password">');
      }
    }
  }
});

and here's how the html would look:

<div ng-controller="MyController">
  <ul ng-repeat="item in items" ng-init="current = item">
    <my-input-directive></my-input-directive>
  </ul>
</div>

Here is a plnkr with the working example: http://plnkr.co/edit/iiS4G2Bsfwjsl6ThNrnS?p=preview

Directives are how the DOM is manipulated in angular. First thing to notice is that angular has a set of directives that come out of the box, we're using a few above (`ng-repeat`, `ng-init`, `ng-controller`). Above we've created a custom directive that will analyze the data type of each `item` in the `items` array of our `MyController` controller, and append the correct html element.

I imagine that you already understand the `ng-repeat` directive, so I'll skip that. I'll explain what I'm doing with the `ng-init` directive though. The `ng-init` directive allows you to evaluate an expression in the current scope. What this means is that we can write an expression that is evaluated in our current controllers scope, in this case the `MyController` scope. I am using this directive to create an alias for our current item named `current`. We can use this inside our directive to check what type the current item in the array iteration is.

Our directive `myInputDirective`, is returning an object with a few different properties. I won't explain them all here (I'll let you read the documentation), but I will explain what the `link` function is and what I am doing with it. A `link` function is typically how we modify the DOM. The link function takes in the current `scope` (in this case the scope of `MyController`), a jqLite wrapped `element` that is associated with the directive, and the `attrs` which is a hash object with key-value pairs of normalized attribute names and values. In our case, the important parameters are the `scope`, which contains our `current` variable, and the `element`, which we will append the correct input onto. In our `link` function, we're checking the `typeof` our `current` item from our items array, then we are appending an element onto our root element based on what the type of the current item is.

For this particular problem, what I'm doing above is overkill. But based off of your question I figured you were looking for a starting point for more advanced uses of angular apart from the built in directives that angular provides. These are somewhat advanced topics in angular, so I hope that what I've said make some sense. Check out the plunker and play around with it a bit, and go through some of the tutorials on https://docs.angularjs.org/guide. Hope this helps!

Problem

I'm getting started with Angular.js and I'm wondering how to do something along the lines of this (pseudocode): ``` <li ng-repeat="item in items"> <# if(item.dataType == "string") { #> <input type="text" /> <# } else if(...) { #> <input type="password" /> <# } #> </li> ``` I know the above code is not angularish, and I know that for simple processing I could use a conditional `ng-hide` or `ng-show` or something similar. But for complex behavior, if I had to perform various data checks and business logic, how could I dynamically generate DOM elements with Angular.js?

Original source