Search code examples
angularjsangularjs-ng-repeat

How to save value with ng-repeat Dynamic repeater field


Dynamic repeater field with AngularJS

I'm new to AngularJS. I have managed to generate the repeater field dynamically but I don't know how to save its value.

Repeater values come from an array in PHP. It would be something like this:

$repeater = [
    'name' => 'redirections',
    'title' => 'Redirection',
    'add_button_text' => 'New redirection',
    'save_button_text' => 'Save',
    'delete_button_text' => 'Remove redirection',
    'fields' => [
        [
            'name' => 'old_url',
            'type' => 'url',
            'label' => 'Old URL'
        ],
        [
            'name' => 'new_url',
            'type' => 'url',
            'label' => 'New URL'
        ]
    ]
];

Then the info I render in HTML like this:

<div class="field-container repeater">
    <div class="field-wrapper" ng-repeat="(key, item) in repeaterItems">
        <div class="title" ng-click="toggleRepeater($event)">
            <span>{{ field.title }}</span>
            <span ng-repeat="input in field.fields" class="field-block">
            </span>
        </div>
        <div class="inside">
            <div class="inputs" ng-repeat="(index, input) in field.fields">
                <label>
                    {{ input.label }}
                    <input type="{{ input.type }}" name="{{ input.name }}"
                           value="{{ input.value }}" ng-model="input.value"
                           ng-change="update_repeater_item()">
                </label>
            </div>
            <span class="delete"><span ng-click="remove_repeater_item($event)">
              {{ field.delete_button_text }}
            </span></span>
        </div>
        <input id="{{ item.id }}" class="{{ item.id }} repeater-item-value"
               type="hidden" name="{{ field.name }}[{{ item.id }}][]" value="">
    </div>

    <button class="repeater-button add-button" ng-click="add_repeater_item($event)">
      {{ field.add_button_text }}
    </button>
    <button class="repeater-button save-button" ng-click="save_repeater($event)">
      {{ field.save_button_text }}
    </button>
</div>

And finally, what I have about Angular is this:

scope.repeaterItems = [];
scope.toggleRepeater = function($event) {
    const clicked = angular.element($event.currentTarget);
    const inside = angular.element($event.currentTarget.nextElementSibling);
    if (clicked.hasClass('open')) {
        clicked.removeClass('open');
        inside.removeClass('show'); // .inside
    }
    else {
        clicked.addClass('open');
        inside.addClass('show'); // .inside
    }
};

scope.add_repeater_item = function() {
    scope.repeaterItems.push({
        'id': Date.now().toString(36) + Math.random().toString(36).substr(2),
        'value': {}
    });
};

scope.remove_repeater_item = function($event) {
    const clicked = $event.currentTarget;
    const container = clicked.closest('.field-wrapper'); // .field-wrapper
    const inputs = container.getElementsByTagName('input');
    let deletedItemId = '';
    for (let input of inputs) {
        if (input.type === 'hidden') {
            deletedItemId = input.id;
        }
    }
    scope.repeaterItems = $filter('filter')(scope.repeaterItems, {id: '!' + deletedItemId}, true);
};
scope.update_repeater_item = function(value) {
    console.log(value);
};

scope.save_repeater = function($event) {
    console.log($event);    
};

What I get visually with this code is this (in case it helps): Repeater rendering

Any help or improvement of the current code is appreciated. Thanks.


Solution

  • Bind the inputs to properties of the ng-repeat item value object:

    <div ng-repeat="item in repeaterItems" ng-init="itemIndex = $index">
        <div ng-repeat="input in field.fields">
            <label>
                {{ input.label }}
                <input type="{{ input.type }}" name="{{ input.name + itemIndex}}"
                       ng-model="item.value[input.name]"
                       ng-change="update_repeater_item(item, itemIndex, input.name)">
            </label>
        </div>
        <span ng-click="remove_repeater_item(itemIndex)">
          {{ field.delete_button_text }}
        </span>
    </div>
    
    <button class="add-button" ng-click="add_repeater_item()">
      {{ field.add_button_text }}
    </button>
    <button class="save-button" ng-click="save_repeater(repeaterItems)">
      {{ field.save_button_text }}
    </button>
    

    This simplifies the code:

    $scope.repeaterItems = [];
    
    $scope.add_repeater_item = function() {
        $scope.repeaterItems.push({
            'id': Date.now().toString(36) + Math.random().toString(36).substr(2),
            'value': {}
        });
    };
    
    $scope.remove_repeater_item = function(index) {
        console.log("removing", $scope.repeaterItems[index].id);
        $scope.repeaterItems.splice(index,1); 
    };
    
    scope.update_repeater_item = function(item, index, name) {
        console.log(item.id, item.value);
        console.log($scope.repeaterItems[index]);
        console.log(name + ' changed to ' + item.value[name]);
    };
    
    $scope.save_repeater = function(items) {
        console.log(items);
        $http.post(url, items);    
    };