TLDR;
I've made the two following working examples which does what you ask. In both cases, the top
and left
positions are computed using the following regular expression by capturing groups 1 and 2 transform:\s*translate3d\((.+?),(.+?),.+?\)
.
Cloned DOM, this is my recommended answer, it clones the Angular generated HTML and removes unwanted attributes manually.
Stored data, as you requested, it stores DOM position, width, height, etc. in a variable and then renders it.
The following code clones the Angular generated HTML and removes unwanted attributes manually. There's more details in the comments in code.
In case that you're simply going to re-render what was dragged somewhere else, this approach would be my personal recommendation as it removes the complexity layer of managing attributes.
TS:
/**
* 1. Added a flag called `isPreviewShown` which toggles which HTML is visible, either the preview or the original
* 2. Added a method called `showPreview` which clones the draggable contents and then manually removes unwanted attributes (you'll have to maintain this logic, so you may remove other attributes you don't need)
* 3. Replaced `cssText` style attributes from `translate3d` to `top/left` values. I used the following [regular expression][2] for it `transform:\s*translate3d\((.+?),(.+?),.+?\)` and the replaced it to `left:$1; top:$2`
**/
isPreviewShown = false;
showPreview() {
// clone the contents
this.previewContainer.nativeElement.innerHTML = this.exampleBoundary.nativeElement.innerHTML;
// clear unwanted attributes
document.querySelectorAll('.preview-container *').forEach((content: HTMLElement) => {
const attrs = content.getAttributeNames();
for (var attr in attrs) {
if (
attrs[attr].indexOf('cdk') !== -1 // <-- remove cdk related attributes
|| attrs[attr].indexOf('_ng') === 0 // <-- remove angular relates attributes
|| attrs[attr].indexOf('ng-') === 0 // <-- remove more angular relates attributes
) {
console.log('->> removed: ', attrs[attr])
content.removeAttribute(attrs[attr]);
}
}
// transform/translate3d --> top/left
content.style.cssText = content.style.cssText.replace(/transform:\s*translate3d\((.+?),(.+?),.+?\)/i, 'left:$1; top:$2')
// remove cdk-drag class
content.classList.remove('cdk-drag')
});
console.log('>> Result: ', this.previewContainer.nativeElement.innerHTML)
// show the preview
this.isPreviewShown = true;
this.cdr.detectChanges();
}
HTML:
<!--
1. Added a `preview-container` which is where the preview is going to be shown
2. Added a `main-container` which is meant to make the `preview` to overlap the original content
3. Added an extra button called "Hide preview element" which hides the preview so you can drag again
-->
<div class="main-container">
<div class="example-boundary" #exampleBoundary>
<div class="example-box" cdkDragBoundary=".example-boundary" cdkDrag>
I can only be dragged within the dotted container
</div>
</div>
<div class="preview-container example-box" #previewContainer [hidden]="!isPreviewShown">
</div>
</div>
<br>
<button (click)="hidePreview()" [hidden]="!isPreviewShown"> Hide preview element </button>
<button (click)="showPreview()" [hidden]="isPreviewShown"> Preview the dragged element </button>
CSS:
/**
* 1. Add `position: relative` to the `main-container` so the `preview-container` size and position are shown relative to the main container.
* 2. Had to add `:host ::ng-deep` to class `.example-box` so the CSS can be shared by both preview and original content (an alternative to this, is to set all style attributes inline by using [`ngStyle`][4] so you'll don't need a css class)
*/
:host ::ng-deep .example-box {
/* no changes here */
}
.main-container {
position: relative;
width: 400px;
height: 400px;
}
.preview-container {
position: absolute;
background: white;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
z-index: 1;
}
This approach does exactly as you requested in comments, it stores DOM position, width, height, etc. in a variable and then renders it by iterating a variable.
In case that you're going to manipulate individual items later (like adding configurable stuff that is not present while dragging) then this approach would be best than #1.
TS:
/**
* 1. Added an interface called `IDragAndDropItem` with all the attributes that are going to be stored.
* 2. Added a flag called `isPreviewShown` which toggles which HTML is visible, either the preview or the original
* 3. Added a method called `showPreview` which iterates the first DOM level and stores the desired attributes in the array `previewItems`
* 4. Computed the `top` and `left` values from the `cssText` style attribute, matched the following [regular expression][2] captured group 1 and 2: `transform:\s*translate3d\((.+?),(.+?),.+?\)`
*/
export interface IDragAndDropItem {
width: string;
height: string;
left: string | null;
top: string | null;
text: string;
}
isPreviewShown = false;
previewItems: Array<IDragAndDropItem> = [];
showPreview() {
this.previewItems = [];
// save the contents
document
.querySelectorAll('.example-boundary > *') // <-- purposely doesn't support nested DOM
.forEach((content: HTMLElement) => {
const position = content.style.cssText.match(
/transform:\s*translate3d\((.+?),(.+?),.+?\)/i
);
this.previewItems.push({
width: content.offsetWidth + 'px',
height: content.offsetHeight + 'px',
left: position ? position[1] || null : null,
top: position ? position[2] || null : null,
text: content.innerText
})
});
// show the preview
this.isPreviewShown = true;
this.cdr.detectChanges();
// show html
this.resultHtml.nativeElement.innerText =
this.previewContainer.nativeElement.innerHTML;
}
HTML:
<!--
1. Added a `preview-container` which is where the `previewItems` is iterated and rendered.
2. Added a `main-container` which is meant to make the `preview` to overlap the original content
3. Added an extra button called "Hide preview element" which hides the preview so you can drag again
-->
<div class="main-container">
<div class="example-boundary" #exampleBoundary>
<div class="example-box" cdkDragBoundary=".example-boundary" cdkDrag>
I can only be dragged within the dotted container
</div>
</div>
<div class="preview-container example-boundary" #previewContainer [hidden]="!isPreviewShown">
<div class="preview-box" *ngFor="let item of previewItems" [ngStyle]="{left: item.left, top: item.top, width: item.width, height: item.height}">
{{ item.text }}
</div>
</div>
</div>
<br>
<button (click)="hidePreview()" [hidden]="!isPreviewShown"> Hide preview element </button>
<button (click)="showPreview()" [hidden]="isPreviewShown"> Preview the dragged element </button>
CSS:
/**
* 1. Add `position: relative` to the `main-container` so the `preview-container` size and position are shown relative to the main container.
* 2. Had to share styles from `example-box` with `preview-box`, otherwise every `style` attribute should be also stored.
*/
.example-box, .preview-box {
/* no changes here */
}
.main-container {
position: relative;
width: 400px;
height: 400px;
}
.preview-container {
position: absolute;
background: white;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
z-index: 1;
}