Resizing element using angular - html

I wanted to make two stretchable rectangles which can be resized horizontally(both left and right).But the problem with my current code is that once i resize my first rectangle(in red),the second rectangle(in blue) automatically jumps to the initial position of the first rectangle.Please help me to fix this.
I am attaching my code along with the output images.
html file:
<div class="rectangle" [ngStyle]="style" mwlResizable [validateResize]="validate"
[enableGhostResize]="true"
[resizeSnapGrid]="{ left: 1, right: 1 }" (resizeEnd)="onResizeEnd($event)">
<div class="resize-handle-left" mwlResizeHandle [resizeEdges]="{ left: true }"></div>
<div class="resize-handle-right" mwlResizeHandle [resizeEdges]="{ right: true }"></div>
</div>
<div class="rectangle1" [ngStyle]="style1" mwlResizable [validateResize]="validate1"
[enableGhostResize]="true"
[resizeSnapGrid]="{ left: 1, right: 1 }" (resizeEnd)="onResizeEnd1($event)">
<div class="resize-handle-left1" mwlResizeHandle [resizeEdges]="{ left: true }"></div>
<div class="resize-handle-right1" mwlResizeHandle [resizeEdges]="{ right: true }"></div>
</div>
css file:
.rectangle {
position: relative;
width: 50px;
height: 50px;
border-radius: 10px;
background-color: red;
}
.rectangle1 {
position: relative;
width: 50px;
height: 50px;
border-radius: 10px;
background-color: blue;
}
.resize-handle-left,
.resize-handle-right {
position: absolute;
height: 50px;
cursor: col-resize;
width: 5px;
}
.resize-handle-left {
left: 0;
}
.resize-handle-right {
right: 0;
}
.resize-handle-left1,
.resize-handle-right1 {
position: absolute;
height: 50px;
cursor: col-resize;
width: 5px;
}
.resize-handle-left1 {
left: 0;
}
.resize-handle-right1 {
right: 0;
}
.ts file:
import { Component } from '#angular/core';
import { ResizeEvent } from 'angular-resizable-element';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor() { }
public style: object = {};
public style1: object = {};
validate(event: ResizeEvent): boolean {
const MIN_DIMENSIONS_PX: number = 50;
if (
event.rectangle.width &&
event.rectangle.height &&
(event.rectangle.width < MIN_DIMENSIONS_PX ||
event.rectangle.height < MIN_DIMENSIONS_PX)
) {
return false;
}
return true;
}
validate1(event: ResizeEvent): boolean {
const MIN_DIMENSIONS_PX: number = 50;
if (
event.rectangle.width &&
event.rectangle.height &&
(event.rectangle.width < MIN_DIMENSIONS_PX ||
event.rectangle.height < MIN_DIMENSIONS_PX)
) {
return false;
}
return true;
}
onResizeEnd(event: ResizeEvent): void {
this.style = {
position: 'fixed',
left: `${event.rectangle.left}px`,
top: `${event.rectangle.top}px`,
width: `${event.rectangle.width}px`,
height: `${event.rectangle.height}px`
};
}
onResizeEnd1(event: ResizeEvent): void {
this.style1 = {
position: 'fixed',
left: `${event.rectangle.left}px`,
top: `${event.rectangle.top}px`,
width: `${event.rectangle.width}px`,
height: `${event.rectangle.height}px`
};
}
}
Links to the output:
On refreshing my page
problem with resizing

Remove css property top and position
import { Component } from '#angular/core';
import { ResizeEvent } from 'angular-resizable-element';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
public style: object = {};
public style1: object = {};
validate(event: ResizeEvent): boolean {
const MIN_DIMENSIONS_PX: number = 50;
if (
event.rectangle.width &&
event.rectangle.height &&
(event.rectangle.width < MIN_DIMENSIONS_PX ||
event.rectangle.height < MIN_DIMENSIONS_PX)
) {
return false;
}
return true;
}
validate1(event: ResizeEvent): boolean {
const MIN_DIMENSIONS_PX: number = 50;
if (
event.rectangle.width &&
event.rectangle.height &&
(event.rectangle.width < MIN_DIMENSIONS_PX ||
event.rectangle.height < MIN_DIMENSIONS_PX)
) {
return false;
}
return true;
}
onResizeEnd(event: ResizeEvent): void {
this.style = {
left: `${event.rectangle.left-8}px`,
width: `${event.rectangle.width}px`,
height: `${event.rectangle.height}px`
};
}
onResizeEnd1(event: ResizeEvent): void {
this.style1 = {
left: `${event.rectangle.left-8}px`,
width: `${event.rectangle.width}px`,
height: `${event.rectangle.height}px`
};
}
}

resizeEnd is the same event for both the rectangles. So when it fires function attached to this event will be called. In your code
functions:- onResizeEnd and onResizeEnd1 must be called togather
when resizeEnd event occurs and the values of another rectangle
got reset.
try to create separate events for both the rectangles.
let me know if this is helpful.

Related

innerHTML doesn't work with app-component in Angular 12

i want to put dynamically components like div and textArea into a div with innerHTML but it doesn't work
imaginarium is the parent component where i'd like to show the draggable component
imaginarium.html
<div *ngIf="shape == true">
<div>
<table>
<tr>
<td><button (click)='update()'>box</button></td>
</tr>
<tr>
<td><button (click)='update()'>editeur de texte</button></td>
</tr>
</table>
</div>
<div class="box-container" [innerHTML]="trustedURL"></div>
</div>
i give you all the necessary code to test yourself
imaginarium.scss
.box-container {
position: absolute;
width: 80%;
height: 90%;
outline: 1px solid black;
transform: translate3d(-100%, -100%);
}
imaginarium.ts
import { DomSanitizer } from '#angular/platform-browser';
trustedURL : any;
constructor(
public sanitizer: DomSanitizer
) {
}
update(){
this.trustedURL = this.sanitizer.bypassSecurityTrustHtml(`<app-resizable-draggable [width]='100' [height]='150' [left]='100' [top]='100' ></app-resizable-draggable>`);
}
this is this child component
resizable-draggable.html
<ul #menu (mouseleave)="resetMenu()"></ul>
<div #box class="resizable-draggable"
[style.width.px]="width"
[style.height.px]="height"
[style.transform]="'translate3d('+ left + 'px,' + top + 'px,' + '0px)'"
[class.active]="status === 1 || status === 2"
(mousedown)="setStatus($event, 2)"
(window:mouseup)="setStatus($event, 0)"
(contextmenu)="Menu()"
>
<div class="resize-action" (mousedown)="setStatus($event, 1)"></div>
</div>
with CSS
resizable-draggable.scss
.resizable-draggable {
outline: 1px solid black;
&.active {
outline-style: solid;
background-color: #80ff800d;
}
&:hover {
cursor: all-scroll;
}
}
.resize-action {
position: absolute;
left: 100%;
top: 100%;
transform: translate3d(-50%, -50%, 0) rotateZ(45deg);
border-style: solid;
border-width: 8px;
border-color: transparent transparent transparent #1100ff;
&:hover, &:active {
cursor: nwse-resize;
}
}
and the .ts
resizable-draggable.ts
import { Component, OnInit, Input, ViewChild, ElementRef, AfterViewInit, HostListener } from '#angular/core';
const enum Status {
OFF = 0,
RESIZE = 1,
MOVE = 2
}
#Component({
selector: 'app-resizable-draggable',
templateUrl: './resizable-draggable.component.html',
styleUrls: ['./resizable-draggable.component.scss']
})
export class ResizableDraggableComponent implements OnInit, AfterViewInit {
#Input('width') public width!: number;
#Input('height') public height!: number;
#Input('left') public left!: number;
#Input('top') public top!: number;
#ViewChild("box") public box!: ElementRef;
#ViewChild('menu', {static: false}) menuChild! : ElementRef;
private boxPosition!: { left: number, top: number };
private containerPos!: { left: number, top: number, right: number, bottom: number };
public mouse!: {x: number, y: number}
public status: Status = Status.OFF;
private mouseClick!: {x: number, y: number, left: number, top: number}
ngOnInit() {}
ngAfterViewInit(){
this.loadBox();
this.loadContainer();
}
private loadBox(){
const {left, top} = this.box.nativeElement.getBoundingClientRect();
this.boxPosition = {left, top};
}
private loadContainer(){
const left = this.boxPosition.left - this.left;
const top = this.boxPosition.top - this.top;
const right = left + window.screen.width -395;
const bottom = top + window.screen.height- 240;
this.containerPos = { left, top, right, bottom };
}
setStatus(event: MouseEvent, status: number){
if(status === 1) event.stopPropagation();
else if(status === 2) this.mouseClick = { x: event.clientX, y: event.clientY, left: this.left, top: this.top };
else this.loadBox();
this.status = status;
}
#HostListener('window:mousemove', ['$event'])
onMouseMove(event: MouseEvent){
this.mouse = { x: event.clientX, y: event.clientY };
if(this.status === Status.RESIZE) this.resize();
else if(this.status === Status.MOVE) this.move();
}
private resize(){
if(this.resizeCondMeet()){
this.width = Number(this.mouse.x > this.boxPosition.left) ? this.mouse.x - this.boxPosition.left : 0;
this.height = Number(this.mouse.y > this.boxPosition.top) ? this.mouse.y - this.boxPosition.top : 0;
}
}
private resizeCondMeet(){
return (this.mouse.x < this.containerPos.right && this.mouse.y < this.containerPos.bottom);
}
private move(){
if(this.moveCondMeet()){
this.left = this.mouseClick.left + (this.mouse.x - this.mouseClick.x);
this.top = this.mouseClick.top + (this.mouse.y - this.mouseClick.y);
}
}
private moveCondMeet(){
const offsetLeft = this.mouseClick.x - this.boxPosition.left;
const offsetRight = this.width - offsetLeft;
const offsetTop = this.mouseClick.y - this.boxPosition.top;
const offsetBottom = this.height - offsetTop;
return (
this.mouse.x > this.containerPos.left + offsetLeft &&
this.mouse.x < this.containerPos.right - offsetRight &&
this.mouse.y > this.containerPos.top + offsetTop &&
this.mouse.y < this.containerPos.bottom - offsetBottom
);
}
Menu(){
this.menuChild.nativeElement.innerHTML = `
<li><button>Actions</button></li>
<li><button>Actions</button></li>
<li><button>Actions</button></li>
<li><button>Actions</button></li>
<li><button>Actions</button></li>
`;
return false;
}
resetMenu(){
this.menuChild.nativeElement.innerHTML = ``;
}
}
have you got an idea to show the component ?
thanks
I've searched and I found something helpful :
https://dzone.com/articles/how-to-dynamically-create-a-component-in-angular
it's permit to add angular components into html

How to add line break to custom tooltip directive using angular 7

I am new to angular 7 directives. I created a custom tooltip directive using angular 7. Now, I am unable to specify line breaks when i pass the tooltip text from my html. I want a line break after the Tootltip Title text I pass from html. Any idea how to achieve this?
Tried to pass
and 
 code for line break in my input string to the tooltip directive. Didn't work either.
Here is what I tried so far.
My directive : tooltip.directive.ts:
import { Directive, Input, ElementRef, HostListener, Renderer2 } from '#angular/core';
#Directive({
selector: '[tooltip]'
})
export class TooltipDirective {
#Input('tooltip') tooltipTitle: string;
#Input() placement: string;
#Input() delay: number;
tooltip: HTMLElement;
offset = 10;
constructor(private el: ElementRef, private renderer: Renderer2) { }
#HostListener('mouseenter') onMouseEnter() {
if (!this.tooltip) { this.show(); }
}
#HostListener('mouseleave') onMouseLeave() {
if (this.tooltip) { this.hide(); }
}
show() {
this.create();
this.setPosition();
this.renderer.addClass(this.tooltip, 'ng-tooltip-show');
}
hide() {
this.renderer.removeClass(this.tooltip, 'ng-tooltip-show');
window.setTimeout(() => {
this.renderer.removeChild(document.body, this.tooltip);
this.tooltip = null;
}, 0);
}
create() {
this.tooltip = this.renderer.createElement('span');
this.renderer.appendChild(
this.tooltip,
this.renderer.createText(this.tooltipTitle) // textNode
);
this.renderer.appendChild(document.body, this.tooltip);
this.renderer.addClass(this.tooltip, 'ng-tooltip');
this.renderer.addClass(this.tooltip, `ng-tooltip-${this.placement}`);
}
setPosition() {
const hostPos = this.el.nativeElement.getBoundingClientRect();
const tooltipPos = this.tooltip.getBoundingClientRect();
let top, left;
if (this.placement === 'top') {
top = hostPos.top - tooltipPos.height - this.offset;
left = hostPos.left + (hostPos.width - tooltipPos.width) / 2;
}
/* other positions to be added */
this.renderer.setStyle(this.tooltip, 'top', `${top}px`);
this.renderer.setStyle(this.tooltip, 'left', `${left}px`);
}
}
CSS :
/* Styles for tooltip */
.ng-tooltip {
position: absolute;
max-width: 150px;
font-size: 14px;
text-align: center;
color: #f8f8f2;
padding: 3px 8px;
border-radius: 2px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.5);
background-color: #38393b;
z-index: 1000;
opacity: 0;
}
.ng-tooltip:after {
content: "";
position: absolute;
border-style: solid;
}
.ng-tooltip-top:after {
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-color: #38393b transparent transparent transparent;
}
.ng-tooltip-show {
opacity: 1;
}
From the html file, I invoke the tooltip directive by passing the text like this:
<div tooltip="Title: ( want a line break here) 
 {{someVariable}}" placement="top" class="remark">Hover Here</div>
This can be resolved by taking any of the approach,
Either create two different input such as #Input('Title'): string and #Input('Body'): string and pass into two different parameters.
OR
if you want to pass it in a single parameter make a use of interface such as
export interface IToolTip {
title?: string;
body: string;
footer?: string;
}
Assign this interface to your tooltip variable #Input('tooltip') tooltipTitle: ITooltip; Rest of the things can taken care under create() funciton.
Thanks.

Angular reactive form dropdown without select and option tags

I ran into difficulty styling my dropdown : https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select#Styling_with_CSS
The select element is notoriously difficult to style productively with CSS.
in summary none of the hacks you can use on the option and select tags are worth their salt even in combination.
I'd love to continue using reactive forms but I wish to purify my html and css by using only <div> tags to draw up and use my dropdown in a reactive form.
is this possible?
here is my code as it exists today.
// this.statusForm = this.fb.group({
// status: ['Delivered', Validators.required]
// });
<form [formGroup]="statusForm">
<select formControlName="status">
<option value="Delivered">Delivered</option><!---->
<option value="Cancelled">Cancelled</option><!---->
<option value="UndeliveredTechnicalissue">Undelivered/Technical issue</option><!---->
</select>
</form>
the js is just the FormBuilder hydration.
I can gather/ console.log() the value using
this.statusForm.value.status;
You create the "options" part as ul>li or div and then style it accordingly.
The trick is to hide/show this part on mouse click or keyboard interaction, but for this you can use a boolean variable (here expanded).
Here a working Stackblitz.
If you want to look at the code in just one page, have a look to the code below:
template
<div class="select-container">
<input type="text"
[id]="customId"
[placeholder]="placeholder"
[value]= "selectedValue"
[disabled]="disabled"
(click)="showOptions()"/>
<ul class="select-menu box" role="listbox" *ngIf="expanded">
<li role="option"
*ngFor="let option of options; let i = index;"
[id]="customId + '-option-' + i"
[title]="option.label"
class="option-item"
[ngClass]="{ 'selected': activeItemIndex === i }"
(click)="selectItem(option)">
<span> {{option.label}}</span>
</li>
</ul>
</div>
component
#Component({
selector: 'form-select',
templateUrl: './form-select.component.html',
styleUrls: ['./form-select.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => FormSelectComponent)
}
]
})
export class FormSelectComponent implements ControlValueAccessor{
public selectedValue = '';
public disabled = false;
public value: string;
#Input()
label: string;
#Input()
formCtrl: AbstractControl;
#Input()
pipe: { type?: string; params?: any };
#Input()
options: {key: string, label: string}[] = [];
#Input()
customId: string;
#Input()
placeholder: string;
public expanded = false;
public activeItemIndex: number;
public onChange(newVal: T) {}
public onTouched(_?: any) {}
public registerOnChange(fn: any): void {
this.onChange = fn;
}
public registerOnTouched(fn: any): void {
this.onTouched = fn;
writeValue(value: string) {
if (value && this.options) {
const match = this.options.find(
(item: { type?: string; params?: any }, index: number) => {
if (item.key === value) {
this.activeItemIndex = index;
return true;
}
}
);
this.selectedValue = match ? match.label : '';
}
}
showOptions() {
if (!this.disabled) {
this.expanded = true;
}
}
selectItem(item: {key: string, label: string}) {
this.value = item.key;
this.expanded = false;
this.selectedValue = item.label;
this.onChange(item.key);
}
}
scss styles
.select-container {
position: relative;
.input-container {
i {
position: absolute;
top: 1rem;
right: 1rem;
}
input[type='text'] {
overflow-x: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding-right: 2rem;
}
}
.select-menu {
width: 100%;
z-index: 100;
max-height: 17.75rem;
overflow: auto;
position: absolute;
top: -5px;
right: 0;
background-color: white;
border: 1px solid gray;
padding: 1rem;
box-sizing: border-box;
.option-item {
padding-left: 1rem;
line-height: 3rem;
color: gray;
overflow-x: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin: 0 -1rem;
&:last-child {
margin-bottom: 0;
}
&.selected,
&:hover {
background-color: lightgray;
color: black;
}
&:focus {
outline: none;
}
}
}
}
How to use it:
In template:
<form [formGroup]="mainFormGroup">
<form-select formControlName="myControl" [options]="options">
</form-select>
</form>
in component:
const options = [{
key: key1,
label: 'value_1'
}, {
key: key2,
label: 'value_2'
}];
this.mainFormGroup = this.fb.group({
myControl: ['']
});

Implement Three Section header with sticky sections

I have to implement a 3 section sticky header. The first section is a header part, the second section is a hero component, the 3d part is a subheader. When the user starts to scroll the page, 2nd section (hero component) disappears behind the header. The 3d section sticks to the header. If the user continues scrolling and scrolled 30% of the page, the 3d section also starts to hide behind the header.
I have some questions:
1) How to better organize the code (split the header into components)
2) And how to implement the described logic using Angular 8?
Maybe there are some examples with Angular 2+
I have implemented a subheader that hides the hero part on scroll
HTML
<div class="subheader__wrapper">
<div class="subheader subheader__sticky"
[ngClass]="getSubheaderClassTop()"
>
<div class="row align-middle align-justify subheader-row-height u-grid-full-width">
<cc-breadcrumb class="columns"></cc-breadcrumb>
<a *ngIf="!noBackButton" (click)="onBack()"
id="form-dropdown-back-link"
class="columns small-1 mos-u-text-size--md mos-u-color-text--white"
>
<my-icon size="lg" icon="keyboard_arrow_left" theme="disabled-light" alt="Back"></my-icon>
<span class="anchor-icon-text-fix">{{ 'subheader.back' | translate }}</span>
</a>
</div>
</div>
</div>
<hero
*ngIf="isHeroVisible"
class="mos-c-position--relative"
[backgroundUrl]="heroImageURL"
[backgroundImage]="gradient"
blendMode="multiply"
backgroundPosition="top"
flexDirection="row"
minHeight="192">
<div *ngIf="!isBlendMode" class="subheader__gradient"></div>
<div class="subheader__content flex-child-grow">
<div class="row x-large-row c-position--relative">
<div class="column medium-8 small-12 u-color-text--white x-large-6">
<h2 class="o-subheader-2" data-id="subheader">
{{ heroSubtitle }} {{heroSubtitleDate}}
</h2>
<h2 class="mos-o-display-2" data-id="header">
{{ heroTitle }}
</h2>
<hr class="subheader__separator c-hero--separator">
</div>
</div>
</div>
</hero>
CSS
:host {
display: block;
}
.subheader {
width: 100%;
height: 52px;
background-color: color(sapphire, 500);
z-index: 100;
&-row-height {
height: 52px;
}
&__sticky {
position: fixed;
}
&__gradient {
pointer-events: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: linear-gradient(to right, rgba(0,0,0,0.8) 0%, rgba(255, 255, 255, 0.2) 100%);
}
&__content {
z-index: 1;
width: 100%;
justify-self: center;
.subheader__separator {
margin-right: auto;
}
}
}
.circle-step-position {
position: absolute;
top: 0;
left: 0;
z-index: 2;
}
JS
import {
AfterViewInit,
Component,
EventEmitter,
Input,
NgZone,
Output,
ElementRef,
ChangeDetectorRef,
ChangeDetectionStrategy,
} from '#angular/core';
import { TranslateService } from '#ngx-translate/core';
import { pathOr } from 'ramda';
import { AutoUnsubscribe } from '../shared/auto-unsubscribe.decorator';
import { WindowRef } from '../services/api/aot-ref/windowRef';
import { fromEvent } from 'rxjs';
import { takeUntil, distinctUntilChanged, map, throttleTime, startWith } from 'rxjs/operators';
#Component({
selector: 'cc-section-subheader',
templateUrl: './subheader.component.html',
styleUrls: ['./subheader.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
#AutoUnsubscribe
export class SubheaderComponent implements AfterViewInit {
componentDestroy;
#Input() subheader: string;
#Input() header: string;
#Input() country: string;
#Input() noBackButton: boolean;
#Input() heroTitle: string;
#Input() heroSubtitle: string;
#Input() heroSubtitleDate: string;
#Input() heroImageURL: string;
#Input() isHeroVisible = false;
#Input() isSummary = false;
#Input() isFileUpload = false;
#Input() isValidateData = false;
#Input() isJobMatch = false;
#Input() isAggregateValidation = false;
#Output() back = new EventEmitter<void>();
adjustSubheader = true;
lastScroll = 0;
lastHeight: string;
isExpanded = false;
isBlendMode = false;
constructor(
private ngZone: NgZone,
private window: WindowRef,
private subheaderElement: ElementRef,
private translate: TranslateService,
private cdr: ChangeDetectorRef,
) {
this.isBlendMode = !!('backgroundBlendMode' in document.body.style);
}
get areButtonsDisabled() {
return true;
}
get gradient(): string {
return this.isBlendMode
? 'linear-gradient(to right, black 0%, white 100%)'
: null;
}
ngAfterViewInit() {
this.ngZone.runOutsideAngular(() => {
fromEvent(this.window.nativeWindow, 'scroll')
.pipe(
map(() => this.isHeaderVisible()),
distinctUntilChanged(),
takeUntil(this.componentDestroy())
)
.subscribe((isHeaderVisible: boolean) => {
this.ngZone.run(() => {
this.adjustSubheader = isHeaderVisible;
this.cdr.detectChanges();
});
});
});
this.ngZone.runOutsideAngular(() => {
fromEvent(this.window.nativeWindow, 'resize').pipe(
startWith(null),
throttleTime(200),
takeUntil(this.componentDestroy()),
)
.subscribe(() => this.updateWrappersHeight());
});
}
/**
* Since sub-header has position set as fixed it removes the element from the normal document
* flow. To properly align content under it we need to set sub-header's height equal to
* sub-header.
*/
updateWrappersHeight(): void {
const el = this.subheaderElement.nativeElement;
const headerHeight = this.window.nativeWindow.getComputedStyle(el.querySelector('.subheader')).height;
if (headerHeight !== this.lastHeight) {
this.lastHeight = headerHeight;
el.querySelector('.subheader__wrapper').style.height = this.lastHeight;
}
}
onBack() {
this.back.emit();
}
getHtmlElementOffset(element: HTMLElement, offset = 0): number {
if (!element.offsetParent) {
return offset;
}
return this.getHtmlElementOffset(
element.offsetParent as HTMLElement,
offset + pathOr(0, ['offsetParent', 'offsetTop'], element),
);
}
getElementOffset(element: ElementRef): number {
if (!element) {
return 0;
}
return this.getHtmlElementOffset(
element.nativeElement,
element.nativeElement.offsetTop,
);
}
isHeaderVisible() {
const scrollTop = this.window.nativeWindow.pageYOffset;
const down = scrollTop > this.lastScroll;
const top = this.getElementOffset(this.subheaderElement);
this.lastScroll = scrollTop;
return scrollTop <= top;
}
getSubheaderClassTop(): string {
return this.adjustSubheader
? ''
: 'subheader__sticky--fixed-to-top';
}
}

Table Columns Unexpectedly Resizing when Data is Added

So essentially I wrote a table with resizeable columns as an angular component that worked as intended as a standalone component with no external CSS.
However I moved it into a project I was making using the base template from:
https://github.com/akveo/ngx-admin
After I moved in here, the widths which were defined at ngOnInit where unevenly spread, and when I tried to move them they some not move.
As I said the table works perfectly fine on my own vanilla angular app, however when I move it to ngx-admin it messes up once data is added.
Picture (before):
Picture (after):
HTML:
<div class='container'>
<table #table>
<tr>
<th *ngFor="let column of columns; let i = index" [ngClass]="'col-header'" [attr.id]="'col-header-' + i">
<button>
<mat-icon (mousedown)="clicked($event)" class="icon">compare_arrows</mat-icon>
</button>
<div (overflow)="overflow()">
{{ column.title }}
</div>
</th>
<th id="col-header-actions" *ngIf="actionModules.length !== 0" [colSpan]="actionModules.length" class="col-header">
{{ settings.actions.columnTitle }}
</th>
</tr>
<tr *ngFor="let datum of dataShowing">
<td *ngFor="let column of columns; let i = index" [ngClass]="'col-' + i">
<div (overflow)="overflow()" [ngClass]="'col-div-' + i">
{{ datum[column.name] }}
</div>
</td>
<td *ngIf="settings.actions.edit">
<button class="action-icon">
<mat-icon (mousedown)="edit(datum)" class="icon">mode_edit</mat-icon>
</button>
</td>
<td *ngIf="settings.actions.delete">
<button class="action-icon">
<mat-icon (mousedown)="delete(datum)" class="icon">delete</mat-icon>
</button>
</td>
</tr>
<tr *ngIf="!hasData">
<td [colSpan]="columns.length + 1" >
<div id="no-data">
No Data
</div>
</td>
</tr>
</table>
</div>
CSS:
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td, th {
border: 1px solid #e2e2e2a6;
text-align: left;
padding: 10px;
position: relative;
overflow: hidden;
text-overflow: ellipsis;
}
td {
height: 1.25em;
}
tr {
padding: 5px;
&:nth-child(odd) {
background-color: #ffffff;
}
&:nth-child(even) {
background-color: #f5f5f5;
}
&:not(:nth-child(1)):hover {
background-color: #eaf5ff;
}
}
th button{
float: right;
background-color: #ffffff;
}
button{
z-index: 1;
position: relative;
display: block;
background: transparent;
border: none;
&:hover {
border: none;
}
&:action {
border: none;
}
}
th div {
position: absolute;
height: 1em;
overflow: hidden;
word-break: break-all;
float: left;
white-space: nowrap;
text-overflow: ellipsis;
transform: translateY(30%);
}
td div {
position: absolute;
height: 1em;
overflow: hidden;
word-break: break-all;
white-space: nowrap;
text-overflow: ellipsis;
top: 0.5em;
width: auto;
transform: translateY(30%);
}
mat-icon {
color: #b9dfff;
}
.container {
width: 100%;
height: 800px;
}
#no-data {
width: 100%;
}
.action-icon {
float: none;
margin: auto;
}
#col-header-actions {
text-align: center;
}
TS:
import { Component, OnInit, Input, ElementRef, Renderer, ViewChild, HostListener} from '#angular/core';
import { AfterViewInit, DoCheck } from '#angular/core/src/metadata/lifecycle_hooks';
#Component({
selector: 'smart-tabl',
templateUrl: './smart-tabl.component.html',
styleUrls: ['./smart-tabl.component.scss']
})
export class SmartTablComponent implements OnInit, AfterViewInit, DoCheck {
#Input() settings: any;
#Input() data: any;
columns: any;
dataShowing: any;
#ViewChild('table') table: ElementRef;
lastMouseX: number;
lastMouseY: number;
curDragX: number;
curDragY: number;
dragging: boolean;
originalColumnWidth: number;
adjacentColumnWidth: number;
adjacentColumn: any;
column: any;
hasData: boolean;
actionModules: any;
constructor(private el:ElementRef) {
}
ngOnInit() {
this. actionModules = [];
let settings = this.settings;
let columns = [];
Object.keys(settings.columns).forEach((key) => {
settings.columns[key].name = key;
columns.push(settings.columns[key]);
});
this.columns = columns;
this.hasData = (this.columns.length !== 0);
this.dataShowing = this.data;
if (!this.settings.actions.hasOwnProperty('delete')) {
this.settings.actions.delete = true;
this.actionModules.push("delete");
}
if (!this.settings.actions.hasOwnProperty('edit')) {
this.settings.actions.edit = true;
this.actionModules.push("edit");
}
}
ngAfterViewInit() {
let headers: any = document.querySelectorAll(".col-header");
for (let i = 0; i < headers.length; i++) {
if (headers[i].attributes.id.nodeValue === "col-header-actions") {
headers[i].style.width = (this.actionModules.length * 5) + "%";
} else {
headers[i].style.width = ((100 - (this.actionModules.length * 5))/(this.columns.length))+"%";
let divs: any = document.querySelectorAll(".col-div-" + i);
for (let j = 0; j < divs.length; j++) {
divs[j].style.width = ((headers[i].clientWidth)*0.9) + "px";
}
}
}
}
ngDoCheck() {
if (this.data.length === 0){
this.hasData = false;
} else {
this.hasData = true;
}
}
#HostListener('mouseup') released(event) {
this.dragging = false;
}
add(item) {
this.data.push(item);
}
clicked(event) {
this.lastMouseX = event.clientX;
this.lastMouseY = event.clientY;
this.dragging = true;
this.column = event.target.parentNode.parentNode;
this.originalColumnWidth = parseInt(this.column.style.width, 10);
let id = this.column.attributes.id.nodeValue;
let idNum = parseInt(id[id.length - 1]) + 1
let query = "col-header-" + (idNum)
if (idNum === this.columns.length) {
query = "col-header-actions";
}
this.adjacentColumn = document.getElementById(query);
this.adjacentColumnWidth = parseInt(this.adjacentColumn.style.width, 10);
}
#HostListener('mousemove', ['$event']) mouseMoved(event) {
this.curDragX = event.clientX;
this.curDragY = event.clientY;
let percentageChange = ((this.lastMouseX-this.curDragX)/parseInt(this.table.nativeElement.clientWidth, 10))*100;
if (this.dragging &&
this.originalColumnWidth - (percentageChange) > 6 &&
this.originalColumnWidth - (percentageChange) < 100 &&
this.adjacentColumnWidth + (percentageChange) > 6 &&
this.adjacentColumnWidth + (percentageChange) < 100 ) {
this.column.lastElementChild.style.width = ((this.column.clientWidth - this.column.firstElementChild.clientWidth)*0.8) + "px";
this.column.style.width = (this.originalColumnWidth - (percentageChange) + "%");
let id = this.column.attributes.id.nodeValue;
//change adjacent columns
this.adjacentColumn.style.width = (this.adjacentColumnWidth + (percentageChange) + "%");
let adjacentId = this.adjacentColumn.attributes.id.nodeValue;
let adjDivs: any = document.querySelectorAll(".col-div-" + adjacentId[adjacentId.length - 1]);
for (let i = 0; i < adjDivs.length; ++i) {
adjDivs[i].style.width = (this.adjacentColumn.clientWidth) + "px";
}
//change inner div sizing
let divs: any = document.querySelectorAll(".col-div-" + id[id.length - 1]);
for (let i = 0; i < divs.length; ++i) {
divs[i].style.width = (this.column.clientWidth) + "px";
}
}
}
filter() {
this.dataShowing = this.data;
}
edit(item) {
console.log(item);
}
delete(item) {
const index = this.data.indexOf(item);
this.data.splice(index, 1);
console.log(this.data.length);
}
}
turns out the ngx-admin already had css for col-(column number), and it was messing up mind, so i changed my naming and it worked fine.