Skip to content

Instantly share code, notes, and snippets.

@camshaft
Last active January 2, 2016 15:59
Show Gist options
  • Save camshaft/8327041 to your computer and use it in GitHub Desktop.
Save camshaft/8327041 to your computer and use it in GitHub Desktop.
Angular input directive problem

Purpose

I'm wanting to create a directive that renders an input based on the response from an api:

<input data-hyper-input="input.birthday" />

with the api response:

{
  "input": {
    "birthday": {
      "type": "date",
      "required": true,
      "placeholder": "Enter your birthday"
    }
  }
}

should render:

<input type="date" name="birthday" data-ng-model="$values.birthday" placeholder="Enter your birthday" />

Problem

This works nice for simple input types. It starts falling down when you use type="select" or type="textarea" because html doesn't have a consistent way to write different input types:

<input type="text" />
<select>
  <option value="item1">Item 1</option>
  <option value="item2">Item 2</option>
  <option value="item3">Item 3</option>
</select>
<textarea>this is my content</textarea>

instead of

<input type="text" />
<input type="select">
  <option value="item1">Item 1</option>
  <option value="item2">Item 2</option>
  <option value="item3">Item 3</option>
</input>
<input type="textarea" value="this is my content" />

Attempt #1

I thought this would be pretty easy to fix with angular... I started out with a simple solution:

<div data-ng-switch="input.type">
  <select data-ng-switch-when="select" data-ng-model="input.$model" name="{{input.name}}" data-ng-required="input.required" data-hyper="input.options" data-ng-options="option.value as (option.name || option.text) for option in options"></select>
  <textarea data-ng-switch-when="textarea" data-ng-model="input.$model" name="{{input.name}}" placeholder="{{input.placeholder}}" data-ng-required="input.required"></textarea>
  <input data-ng-switch-default data-ng-model="input.$model" name="{{input.name}}" type="{{input.type}}" placeholder="{{input.placeholder}}" data-ng-required="input.required" />
</div>

This works except you have a wrapper div, which actually ends up being problematic for doing something like:

<input type="select" data-hyper-input="input.birthday" data-ng-change="inputChanged()" />

which actually ends up putting the ng-change onto the div instead of the input element so inputChanged() never gets called.

Attempt #2

I then tried the following:

<select data-ng-if="input.type == 'select'" data-ng-model="input.$model" name="{{input.name}}" data-ng-required="input.required" data-hyper="input.options" data-ng-options="option.value as (option.name || option.text) for option in options"></select>
<textarea data-ng-if="input.type == 'texarea'" data-ng-model="input.$model" name="{{input.name}}" placeholder="{{input.placeholder}}" data-ng-required="input.required"></textarea>
<input data-ng-if="input.type != 'select' && input.type != 'texarea'" data-ng-model="input.$model" name="{{input.name}}" type="{{input.type}}" placeholder="{{input.placeholder}}" data-ng-required="input.required" />

but angular only allows one root element in a directive.

Attempt #3

I then tried using $elem.replaceWith in the link function. This worked ok until you use it with a ng-repeat

<input data-ng-repeat="input in inputs" data-hyper-input="input">

with the api:

{
  "inputs": [
    {
      "name": "name",
      "type": "text",
      "required": true,
      "placeholder": "Enter your name"
    },
    {
      "name": "birthday",
      "type": "date",
      "required": true,
      "placeholder": "Enter your birthday"
    },
    {
      "name": "favorite-color",
      "type": "select",
      "options": [
        {"value": "red"},
        {"value": "blue"},
        {"value": "yellow"}
      ]
    }
  ]
}

The way I wrote it unfortunately made the ng-repeat re-add all of the input elements any time the list changed.

So if we add an item:

$scope.inputs.push({
  type: 'text',
  name: 'address',
  placeholder: 'Enter your address (optional)'
});

we now end up with duplicated inputs:

<input type="text" name="name" data-ng-model="$values.name" placeholder="Enter your name" />
<input type="date" name="birthday" data-ng-model="$values.birthday" placeholder="Enter your birthday" />
<select name="favorite-color" data-ng-model="$values.favoriteColor">
  <option value="red">red</option>
  <option value="blue">blue</option>
  <option value="yellow">yellow</option>
</select>
<input type="text" name="name" data-ng-model="$values.name" placeholder="Enter your name" />
<input type="date" name="birthday" data-ng-model="$values.birthday" placeholder="Enter your birthday" />
<select name="favorite-color" data-ng-model="$values.favoriteColor">
  <option value="red">red</option>
  <option value="blue">blue</option>
  <option value="yellow">yellow</option>
</select>
<input type="text" name="address" data-ng-model="$values.address" placeholder="Enter your address (optional)" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment