Mori no Ando
Mori no Ando

Reputation: 12

Angular directive nested in table body renders above table

Okay so, I'm using Angular 1.5.7, and I'm trying to do some table rendering with ng-repeat and stuff. This is what my table markup looks like:

    <table class="table table-hover">
        <thead>
            <tr>
                <td>Property name</td>
                <td>Property value</td>
            </tr>
        </thead>
        <tbody>
            <adm-preset-property
             ng-repeat="(propertyName, definition) in 
             componentDefinition.component_properties"
             property-name="propertyName"
             property-value="component.component_properties"
             property-definition="definition"></adm-preset-property>
        </tbody>
    </table>

The <adm-preset-property> directive has a replace: true property and is rendered from a root <tr></tr> tag.

Now the loop works fine, BUT, instead of the table rows being rendered inside the table body, where they are nested, they are rendered ABOVE the table. I end up with

<tr>
    {{ content }}
</tr>
<tr>
    {{ content }}
</tr>
<tr>
    {{ content }}
</tr>
<table>...</table>

What's worse, I can't seem to reproduce this on JSFiddle. What am I doing wrong?

EDIT: As requested, here's the template for the <adm-preset-property>

Template:

<tr>
    <td>
        <span data-toggle="tooltip" ng-attr-title="{{ ::propertyDefinition.description }}">{{ ::propertyDefinition.name }}</span>
    </td>
    <td ng-switch="propertyDefinition.editor_type">
        <div ng-switch-when="select">
            <ui-select append-to-body="true" ng-model="propertyValue[propertyName]" theme="bootstrap">
                <ui-select-match placeholder="Select option...">{{ $select.selected.value }}</ui-select-match>
                <ui-select-choices repeat="option.key as (key, option) in propertyDefinition.editor_properties.options | filter:{'value':$select.search} track by $index"
                    ng-value="key">
                    {{ option.value | highlight: $select.search }}</ui-select-choices>
            </ui-select>
        </div>
        <div ng-switch-when="boolean">
            <input type="checkbox" ng-model="propertyValue[propertyName]">
        </div>
        <div ng-switch-when="float">
            <input type="range" step="0.01" ng-model="propertyValue[propertyName]" min="{{propertyDefinition.editor_properties.min}}"
                max="{{propertyDefinition.editor_properties.max}}">&nbsp;{{ propertyValue[propertyName] }}
        </div>
        <div ng-switch-when="color">
            <input type="color" style="width: 75%;" ng-model="propertyValue[propertyName]">
        </div>
        <div ng-switch-when="int">
            <input type="number" style="width: 75%;" ng-model="propertyValue[propertyName]" min="{{propertyDefinition.editor_properties.min}}"
                max="{{propertyDefinition.editor_properties.max}}"> <br/>
            <small>{{::propertyDefinition.editor_properties.min}} - {{::propertyDefinition.editor_properties.max}}</small>
        </div>
        <div ng-switch-default>
            <input type="text" style="width: 75%;" ng-bind="propertyValue[propertyName]" />
        </div>
    </td>
</tr>

Directive:

(function() {
    "use strict";

    angular
        .module('adomee.admin')
        .directive('admPresetProperty', admPresetProperty);

    /* @ngInject */
    function admPresetProperty($log)
    {
        return {
            restrict: 'E',
            replace: true,
            scope: {
                propertyName: '=',
                propertyValue: '=',
                propertyDefinition: '='
            },
            templateUrl: 'app/preset/components/preset-property.tmpl.html',
            link: function($scope, $element, $attributes) {
                if ($scope.propertyDefinition.editor_type === 'select' && typeof($scope.propertyDefinition.defaultValue) === 'number') {
                    $scope.propertyValue[$scope.propertyName] = String($scope.propertyDefinition.defaultValue);
                }
            }
        }
    }
})();

Upvotes: 1

Views: 666

Answers (1)

Mori no Ando
Mori no Ando

Reputation: 12

Okay, after some research and consulting with my boss, we have concluded that Angular, naturally, compiles the directive's template into the DOM after the primary markup has been loaded.

Since the table expects a <tr> tag followed by <td>, but it runs into my custom tag instead, it removes the custom tag and places it outside of the table, which then compiles to my template afterwards, resulting in table rows on top of the actual table.

What I did was:

  1. Change restrict: 'E' to restrict: 'A' in the directive.
  2. Remove the replace property from it.
  3. Remove the root <tr> tag and leave two <td> tags.
  4. Place the directive into the table onto a <tr> and ng-repeat it.

Here's what it looks like now.

<tr adm-preset-property 
    ng-repeat="(propertyName, definition) in componentDefinition.component_properties"
    property-name="propertyName"
    property-value="component.component_properties"
    property-definition="definition"></tr>

Upvotes: 1

Related Questions