ngx-sortablejs
ngx-sortablejs copied to clipboard
DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
Hello,
I am running into an issue with drag and dropping across containers.
- I have reviewed the example source code
I have created a reusable sortable list that I am implementing multiple times on one page and I want to drag an item from one group to another. Here is an example of the implementation where app-sortable-list-form is the reused component
<ng-container formArrayName="agendaBlocks">
<!-- Iterate over schedule blocks -->
<div class="schedule-block" *ngFor="let block of activeFormGroup.controls.agendaBlocks.controls; let i = index">
<!-- Schedule block header -->
<div class="block-header">
<app-label color="midnight-blue" medium> {{ block.controls.name.value }} </app-label>
<app-label color="dark" xsmall> {{ getTimePipeInputs(block) | timeRange }} </app-label>
</div>
<!-- Session list -->
<ng-container [formGroupName]="i">
<app-sortable-list-form
[showThumbnail]="true" thumbnailType="session"
formControlName="sessions" dragGroup="agendaBlocks">
</app-sortable-list-form>
<!-- Add Session button -->
</ng-container>
</div>
</ng-container>
Here is the source code of the reusable sortable list form. It implements ControlValueAccessor so it can be added to a reactive form (not necessary info but wanted to make sure it doesn't cause confusion).
You'll see the options called dragDropOptions and an input which allows the use of a group.
sortable-list-form.component.ts
import { Component, ElementRef, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { SortablejsOptions } from 'angular-sortablejs';
import { ThumbnailType } from '../../../thumbnail';
import { UniqueUtils } from '../../../utils';
import { SortableListItem } from '../../models';
export const SORTABLE_LIST_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SortableListComponent),
multi: true,
};
@Component({
selector: 'app-sortable-list-form',
templateUrl: 'sortable-list-form.component.html',
styleUrls: ['sortable-list-form.component.scss'],
providers: [SORTABLE_LIST_VALUE_ACCESSOR]
})
export class SortableListComponent implements ControlValueAccessor {
@Input() showThumbnail = false;
@Input() thumbnailType: ThumbnailType = 'content';
@Input()
set dragGroup(value: string) {
this.dragDropOptions.group = value;
}
private _dragging = false;
get dragging(): boolean {
return this._dragging;
}
set dragging(value: boolean) {
this._dragging = value;
const op = this._dragging === true ? 'add' : 'remove';
(this.elementRef.nativeElement as HTMLElement).classList[op]('dragging');
}
dragDropOptions: SortablejsOptions= {
ghostClass: 'drop-placeholder',
filter: 'input, textarea, app-button, app-icon-hover-button',
preventOnFilter: false,
onStart: (event) => this.onDragStart(event),
onEnd: (event) => this.onDragEnd(event),
onSort: () => this.onSort()
};
uniqueContainerId: string = UniqueUtils.getRandomNumberString();
listItems: SortableListItem[];
// ControlValueAccessor properties
onChange: Function;
onTouched: Function;
isDisabled = false; // NOT IMPLEMENTED
constructor(protected elementRef: ElementRef) {
(this.elementRef.nativeElement as HTMLElement).classList.add('sortable-list');
}
// Handlers
onRemoveClicked(index: number) {
this.listItems = [...this.listItems || []];
this.listItems.splice(index, 1);
this.notifyChanged();
}
onListItemFormSave(index: number, listItemData: SortableListItem) {
if (index == null) {
this.listItems = [...this.listItems, listItemData];
}
else {
this.listItems.splice(index, 1, listItemData);
}
this.notifyChanged();
}
// This is here because Session's title property is names "Name". Everything else is "Title"
getNameOrTitleText(value: SortableListItem) {
if (value == null) {
return null;
}
return value.title || value.name || null;
}
// ControlValueAccessor implementation
writeValue(value: SortableListItem[]): void {
this.listItems = value != null
? value
: [];
}
registerOnChange(onChangeFunction: Function): void {
this.onChange = onChangeFunction;
}
registerOnTouched(onTouchedFunction: Function): void {
this.onTouched = onTouchedFunction;
}
setDisabledState?(isDisabled: boolean): void {
this.isDisabled = isDisabled;
}
protected notifyChanged() {
if (this.onChange) {
// Allows the use of the standard required validator
const notifyValue = (this.listItems || []).length === 0 ? null : this.listItems;
this.onChange(notifyValue);
}
}
// Drag drop functions
protected onDragStart(event: Event) {
console.log(event);
this.listItems = [...this.listItems || []];
this.dragging = true;
}
protected onDragEnd(event?: Event) {
console.log(event);
this.dragging = false;
}
protected onSort() {
this.notifyChanged();
}
}
sortable-list-form.component.ts
<div class="drag-list-container" [ngClass]="uniqueContainerId" [sortablejs]="listItems" [sortablejsOptions]="dragDropOptions">
<ng-container *ngFor="let item of listItems; let i = index">
<app-panel-list-item class="list-item stripe"
[hideThumbnail]="showThumbnail !== true" [thumbnailUrl]="item.thumbnailUrl"
[thumbnailType]="thumbnailType"
[title]="getNameOrTitleText(item)" [description]="item.description" mode="remove"
(removeClicked)="onRemoveClicked(i)">
</app-panel-list-item>
</ng-container>
</div>
Here is the start event:

Here is the end event:

Here is the error text:
at EmulatedEncapsulationDomRenderer2.push.../../node_modules/@angular/platform-browser/fesm5/platform-browser.js.DefaultDomRenderer2.removeChild (http://localhost:4200/vendor.js:69796:20)
at DebugRenderer2.push.../../node_modules/@angular/core/fesm5/core.js.DebugRenderer2.removeChild (http://localhost:4200/vendor.js:61146:23)
at execRenderNodeAction (http://localhost:4200/vendor.js:57614:22)
at visitRenderNode (http://localhost:4200/vendor.js:57591:13)
at visitSiblingRenderNodes (http://localhost:4200/vendor.js:57540:13)
at visitRootRenderNodes (http://localhost:4200/vendor.js:57534:5)
at renderDetachView (http://localhost:4200/vendor.js:58279:5)
at detachEmbeddedView (http://localhost:4200/vendor.js:58237:5)
at ViewContainerRef_.push.../../node_modules/@angular/core/fesm5/core.js.ViewContainerRef_.remove (http://localhost:4200/vendor.js:58502:24)
at http://localhost:4200/vendor.js:4509:38
at DefaultIterableDiffer.push.../../node_modules/@angular/core/fesm5/core.js.DefaultIterableDiffer.forEachOperation (http://localhost:4200/vendor.js:55958:17)
at NgForOf.push.../../node_modules/@angular/common/fesm5/common.js.NgForOf._applyChanges (http://localhost:4200/vendor.js:4502:17)
at NgForOf.push.../../node_modules/@angular/common/fesm5/common.js.NgForOf.ngDoCheck (http://localhost:4200/vendor.js:4496:22)
at checkAndUpdateDirectiveInline (http://localhost:4200/vendor.js:58959:19)
at checkAndUpdateNodeInline (http://localhost:4200/vendor.js:60220:20)
at checkAndUpdateNode (http://localhost:4200/vendor.js:60182:16)
at debugCheckAndUpdateNode (http://localhost:4200/vendor.js:60816:38)
at debugCheckDirectivesFn (http://localhost:4200/vendor.js:60776:13)
at Object.eval [as updateDirectives] (ng:///SharedModule/SortableListComponent.ngfactory.js:57:5)
at Object.debugUpdateDirectives [as updateDirectives] (http://localhost:4200/vendor.js:60768:21)
at checkAndUpdateView (http://localhost:4200/vendor.js:60164:14)
at callViewAction (http://localhost:4200/vendor.js:60405:21)
at execComponentViewsAction (http://localhost:4200/vendor.js:60347:13)
at checkAndUpdateView (http://localhost:4200/vendor.js:60170:5)
at callViewAction (http://localhost:4200/vendor.js:60405:21)
at execEmbeddedViewsAction (http://localhost:4200/vendor.js:60368:17)
at checkAndUpdateView (http://localhost:4200/vendor.js:60165:5)
at callViewAction (http://localhost:4200/vendor.js:60405:21)
at execComponentViewsAction (http://localhost:4200/vendor.js:60347:13)
at checkAndUpdateView (http://localhost:4200/vendor.js:60170:5)
at callViewAction (http://localhost:4200/vendor.js:60405:21)
at execEmbeddedViewsAction (http://localhost:4200/vendor.js:60368:17)
at checkAndUpdateView (http://localhost:4200/vendor.js:60165:5)
at callViewAction (http://localhost:4200/vendor.js:60405:21)
at execComponentViewsAction (http://localhost:4200/vendor.js:60347:13)
at checkAndUpdateView (http://localhost:4200/vendor.js:60170:5)
at callViewAction (http://localhost:4200/vendor.js:60405:21)
at execComponentViewsAction (http://localhost:4200/vendor.js:60347:13)
at checkAndUpdateView (http://localhost:4200/vendor.js:60170:5)
at callViewAction (http://localhost:4200/vendor.js:60405:21)
at execEmbeddedViewsAction (http://localhost:4200/vendor.js:60368:17)
at checkAndUpdateView (http://localhost:4200/vendor.js:60165:5)
at callViewAction (http://localhost:4200/vendor.js:60405:21)
at execEmbeddedViewsAction (http://localhost:4200/vendor.js:60368:17)
at checkAndUpdateView (http://localhost:4200/vendor.js:60165:5)
at callViewAction (http://localhost:4200/vendor.js:60405:21)
at execComponentViewsAction (http://localhost:4200/vendor.js:60347:13)
at checkAndUpdateView (http://localhost:4200/vendor.js:60170:5)
at callViewAction (http://localhost:4200/vendor.js:60405:21)
at execEmbeddedViewsAction (http://localhost:4200/vendor.js:60368:17)
please let me know if you need more information.
Also, I love this library!
I added unique id classes to the containers to help show the container differences between from and to
Hi @ScottMGerstl
thank you for a very detailed description.
Which version of sortable library do you use? If 1.8+ then could you try whether it is reproducible with v1.7.0?
@smnbbrv
sortablejs was on 1.8.0. I downgraded and tried it on 1.7.0 but I am getting the same error.
For angular-sortablejs I am on 2.6.0.
Doing a bit more digging, It looks like maybe the wrong parent is being passed to the removeChild function.
// For reference
DefaultDomRenderer2.prototype.removeChild = function (parent, oldChild) {
if (parent) {
parent.removeChild(oldChild);
}
};
When I drag and drop inside a single list, oldChild.parentElement is the same as parent. When I drag and drop across lists, oldChild.parentElement is the container the item originated from and parent is the list I dropped it in. Not sure if that helps. It is failing on the first removeChild attempt when I drop across lists.
Here are visual before and after states of the lists that have been dragged across (It's mid development so, please forgive the unrefined UI).
Initial State

After State

In the After State, the item "Purpose, Values, Vision, Mission" is sort of duplicated but I believe it is a result of the error on drop. The top item is the real one. the bottom item when dragged and dropped creates empty items as seen below. It happens as many times as I drag and drop a "fake" item

hi @ScottMGerstl
thank you for investigation. This is a weird behaviour... Is there a public repository the issue can be reproduced? It's hard to say anything without trying and failing :)