1

I'm trying to use the angular material drag and drop component to reorder a list of basic radio inputs.

When the page loads, the correct radio input is checked. My problem is that, when I reorder the checked item in the list using the [Grab] handle, the radio selection appears to reset and I can't figure out how to persist the selection.

The really odd thing to me, is that if this was a checkbox, it'd work fine. So I'm assuming it's something to do with the way I've set up the radio input.

Appreciate any help you're able to offer.

Here's my stackblitz: https://stackblitz.com/edit/angular-2q94xh

app.component.html

<table cdkDropList (cdkDropListDropped)="drop($event)">
  <tr *ngFor="let selection of content" cdkDrag>
    <td>
      <div class="grab" cdkDragHandle>[Grab]</div>
    </td>
    <td>
      <input 
        [id]="selection.id" 
        type="radio" 
        name="radio"
        [checked]="selection.selected"
      >
    </td>
    <td>{{ selection.selected }}</td>
  </tr>
</table>

app.component.ts

import { Component } from '@angular/core';
import {CdkDragDrop, moveItemInArray, transferArrayItem, CdkDrag} from '@angular/cdk/drag-drop';
@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.content, event.previousIndex, event.currentIndex);
  }
content = [
  {
    "id": "1",
    "selected": false
  },
  {
    "id": "2",
    "selected": false
  },
  {
    "id": "3",
    "selected": true
  }
]
}
Aaron
  • 271
  • 2
  • 11
  • 1
    One problem is your view and model are not synchronized. When you change the model, you send it to the value with `[checked]="selection.selected"`, but you also have to update your model when the view changes, subscribing to `(change)="selection.selected = $event.target.checked"`. But even when fixing this, the view still loses the checked attribute (whereas the model keeps it), and that's strange – Random Aug 06 '20 at 12:57
  • Thanks for replying, and totally agree with you. In my haste I incorrectly stripped out the (change) from my main code because it was doing a bunch of other things and, as you say, doesn't appear to be the root cause of the problem anyway. – Aaron Aug 06 '20 at 13:54
  • 1
    Note for others that switching to angular materials seems to eliminate the issue, although it feels like a workaround at best. – Aaron Aug 06 '20 at 17:42

1 Answers1

1

I think that's an issue with cdk/drag-drop. It doesn't handle dragging radio groups properly.

It creates clones which have the same names for radio inputs. And browser moves selection to that cloned elements.

I created a github issue for that bug as well as the dedicated PR in order to fix that.

As for now, I can suggest you the following workaround:

import { DragRef } from '@angular/cdk/drag-drop';

function patchCloneNode(method) {
  const originalMethod = DragRef.prototype[method];

  DragRef.prototype[method] = function() {
    const sourceNode = this._rootElement;
    const originalRadioInputs = sourceNode.querySelectorAll('input[type="radio"]');
    
    const clonedNode = originalMethod.apply(this, arguments) as HTMLElement; 
    const clonedRadioInputs = clonedNode.querySelectorAll<HTMLInputElement>('input[type="radio"]');

    Array.from(clonedRadioInputs).forEach((input, i) => {
      input.name = originalRadioInputs[i].name + method;
    });
    return clonedNode;
  }   
} 

patchCloneNode('_createPlaceholderElement');
patchCloneNode('_createPreviewElement');

Forked Stackblitz

yurzui
  • 205,937
  • 32
  • 433
  • 399