How to transfer an array item to another array and update it using Angular Material Drag n Drop CDK without both items binding to the same paramter

Issue

I’m using Angular Material Drag N Drop CDK to move a set of default items (List 1) to a list of dynamic items (List 2). When I drag a default item (L1) into the dynamic items (L2) and then update the now dynamic item(New L2 Items), it also updates the default item (Old L1 Item) as well.

When you drag a default item (L1) to the dynamic item (L2), the default list (L1) resets back to its original array with a resetList method. My goal is to update the now dynamic item (New L2 Item) and to potentially drag the same default item (Old L1 Item that was reset) into the dynamic list (L2) creating another new dynamic item (Another New L2 Item). The problem Im running into is, when I drag a default item (L1) into the dynamic list (L2), then update the new dynamic item (New L2 Item using ngModel) the default item (Old L1 Item) that was dragged and reset also updates.

Here is my html in form.component.html

<!-- Default Answer List - List 1 -->
 <aside cdkDropList id="defaultAnswerList"
            [cdkDropListConnectedTo]="['dynamicAnswerList']" [cdkDropListData]="defaultAnswers">
            <div class="aside-container">
                <div class="auto-complete-content-area p-10">
                    <div *ngFor="let answer of defaultAnswers">
                        <!-- answer.isInput - Text Input -->
                        <div class="element-wrapper addon-group" *ngIf="answer.isInput" cdkDrag>
                            <div class="label-side">
                                Short Text
                            </div>
                            <div class="element-side">
                                <input type="text" [(ngModel)]="answer.placeholderText" class="input-element"
                                    placeholder="Placeholder" />
                                <label>Drag to add a short text answer</label>
                            </div>
                        </div>
          </div>
       </div>
    </aside>

<!-- Dynamic Answer List - List 2-->
<div class="input-answers" cdkDropList id="dynamicAnswerList"
                    (cdkDropListDropped)="dropIt($event)" [cdkDropListData]="dynamicAnswers">
                        <div class="input-section" cdkDragLockAxis="y" style="cursor: all-scroll" *ngFor="let answer of dynamicAnswers; index as i"
                            cdkDrag>
                            <div class="input-wrapper" *ngIf="answer.isInput || answer.isAddressSearch || answer.isAgeInput || answer.isVehicleVIN">
                                <input type="text" class="input-box normal-input-box" [(ngModel)]="answer.placeholderText"
                                    placeholder="Add Text Placeholder" />
                            </div>
                        </div>
                    </div>

Here is my form.component.ts file

// Here is the original array which is then set to defaultAnswers
defaultAnswersOrigin: Answer[] = [
        {isInput: true, placeholderText: "Enter Placeholder", hasPrefix: false, hasSuffix: false, prefixText: "", suffixText: "", width: "45", position: 0},
        {isDatePicker: true, placeholderText: "Enter Date Placeholder", hasPrefix: false, hasSuffix: false, prefixText: "", suffixText: "", width: "45", position: 1},
        {isSelect: true, placeholderText: "Enter Menu Placeholder", hasPrefix: false, hasSuffix: false, prefixText: "", suffixText: "", width: "45", position: 2},
        {isTextarea: true, secondaryPlaceholderText: "Enter Text Placeholder", hasSecondaryPlaceholder: true, hasPrefix: false, hasSuffix: false, prefixText: "", suffixText: "", width: "45", position: 3},
        {isCheckbox: true, displayValue: "", hasPrefix: false, hasSuffix: false, prefixText: "", suffixText: "", width: "45", position: 4},
        {isButton: true, placeholderText: "", hasPrefix: false, hasSuffix: false, prefixText: "", suffixText: "", displayValue: "Enter Button Text", width: "45", position: 5},
        {isPrevNextButtons: true, placeholderText: "", hasPrefix: false, hasSuffix: false, prefixText: "", suffixText: "", displayValue: "", width: "90", position: 6},
        {isProgressButton: true, placeholderText: "", hasPrefix: false, hasSuffix: false, prefixText: "", suffixText: "", displayValue: "", width: "90", position: 7}
    ];

 defaultAnswers = this.defaultAnswersOrigin;
 answers: Answers = [];

   // Drop it method used in html
    dropIt(event: CdkDragDrop<string[]>) {
        if (event.previousContainer !== event.container) {
            transferArrayItem(this.defaultAnswers,
                                 this.answers,
                                 event.previousIndex,
                                 event.currentIndex);
                this.answers.forEach((answer, i) => {
                    answer.position = i;
                });
            this.resetList();
        } else if (event.previousIndex !== event.currentIndex) {
            if (event.previousContainer === event.container) {
                moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
            }
        }
      }

// Reset list method used
     resetList() {
        this.defaultAnswers = [];
        setTimeout(() => {
          this.defaultAnswers = this.defaultAnswersOrigin.slice();
        }, 0);    
      }

Im expecting to be able to drag an item from L1 to L2 and update it successfully using ngModel. In this specific use-case, I want to change the placeholder which is a parameter in the Answer class.

Whats actually happening is, Item from L1 and New Item from L2 both update as if they’re binding to the same parameter. Therefor I can’t update item from L2 without changing L1. If I also add the same item from L1 into L2 again (which I can do because the list is reset), all three items (L1, L2 New and 2nd L2 New) will update using ngModel.

*******UPDATE – REPRODUCED STACKBLITZ
I was able to reproduce the error in stackblitz. You may have to refresh the page to get the drag n drop to work.

Steps to reproduce: 
1. go to url below
2. Drag a Short Text item from Default List into Dynamic List
3. Start changing the new items placeholder in Dynamic List
4. Notice how placeholder in reset Default list is changing as well
5. Add same item from Default list to Dynamic list again
6. Change placeholder of new item
7. Notice how all three placeholders change now

Solution

You’ll need to create a copy of the original item, then add the copy to the 2nd list. There are a lot of ways to copy an object but basically something like this:

function createCopy(orig){
   return  JSON.parse(JSON.stringify(orig))
}

Answered By – Mike Feltman

Answer Checked By – Robin (AngularFixing Admin)

Leave a Reply

Your email address will not be published.