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
Related
I am writing, because I have a problem with my resizeable component.
I want have only cursor 'resize' instead of 'not-allowed' on drag.
Could you please for your help?
Component
resizeable.component.ts
import { Component, ContentChild, ElementRef, OnDestroy, ViewChild } from '#angular/core';
import { Subject } from 'rxjs';
#Component({
selector: 'app-resizeable',
templateUrl: './resizeable.component.html',
styleUrls: ['./resizeable.component.scss']
})
export class ResizeableComponent implements OnDestroy {
#ViewChild('line') line: ElementRef;
#ContentChild('content') content: ElementRef;
initHeight: number;
initPositionY: number;
private unsubscribe$: Subject<void> = new Subject<void>();
ngOnDestroy(): void {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
onDrag(event: DragEvent): void {
const height = this.initHeight + (event.clientY - this.initPositionY);
this.content.nativeElement.style.height = `${height}px`;
event.preventDefault();
}
onDragEnd(event: DragEvent): void {
event.preventDefault();
}
onDragStart(event: DragEvent): void {
event.dataTransfer.effectAllowed = 'move';
this.initHeight = this.content.nativeElement.getBoundingClientRect().height;
this.initPositionY = event.clientY;
}
}
resizeable.component.html
<ng-content></ng-content>
<div
#line
*ngIf="content"
(dragend)="onDragEnd($event)"
(dragstart)="onDragStart($event)"
(drag)="onDrag($event)"
class="break-line"
draggable="true"
>
<hr />
</div>
resizeable.component.scss
.break-line {
cursor: row-resize;
height: 0.5rem;
margin-top: 3rem;
width: 100%;
-webkit-user-drag: element;
-webkit-user-select: none;
}
* Project Description
I am creating a responsive tournament Brackets tree web Page.
* Problem
My Problem is that I want to connect each Bracket "each bracket is a div of its own" to the next one with a decorated line, in general I want two connect two divs with a line that I can decorate.
- Example
Example1
Example2
* what I tried
I tried using the CanvasRenderingContext2D but I didn't have much luck in it, or at least didn't know how to fully utilize it
* What I want/ What's expected
I want to be able to connect the two brackets/divs with a line that can be bent and moved "by the developer not the user" to design it.
p.s: This only needs to be shown on screens larger than "xs" so mobile devices is not a concern.
* My Code
Stackblitz Project
- Used Libraries and versions
Angular V:5.1
Flex-Layout "angular" V: 2.00 beta 12
Angular Material V: 5.01
<div style="position:absolute; left:0; top:0; width: 100px; height: 50px; background:red;"></div>
<div style="position:absolute; left:250px; top:150px; width: 100px; height: 50px; background: blue;"></div>
<svg style="position:absolute; left:0; top:0;" width="300" height="300">
<line x1="100" y1="50" x2="250" y2="150" stroke="black"/>
</svg>
Example:
In app.component.html file:
<div class="cont">
<div *ngFor="let item of arr_item; index as i" class="block d-flex align-items-center justify-content-between">
<div class="d-flex flex-column" #left>
<div *ngFor="let val of item.left" class="box box-left">
{{val}}
</div>
</div>
<div class="d-flex flex-column align-items-end" #right>
<div *ngFor="let val of item.right" class="box box-right">
{{val}}
</div>
</div>
</div>
<svg style="position: absolute; z-index: -1; width:100%; height:100%; top: 0; left: 0;">
<polyline *ngFor="let item of polylines"
[attr.points]="item.points"
[attr.fill]="item.fill"
[attr.stroke]="item.stroke"
[attr.stroke-width]="item.strokeWidth"
/>
</svg>
</div>
In app.component.css file:
.cont {
position: relative;
}
.box {
padding: 10px;
border: 1px solid #292929;
width: fit-content;
margin: 10px;
max-width: 200px;
}
.block {
margin: 10px;
}
In app.component.ts file:
import {
AfterViewInit,
Component,
ElementRef,
OnInit,
QueryList,
ViewChildren
} from "#angular/core";
export class Polyline {
points: string;
fill: string;
stroke: string;
strokeWidth: number;
constructor(
points?: string,
fill?: string,
stroke?: string,
strokeWidth?: number
) {
this.points = points || "";
this.fill = fill || "none";
this.stroke = stroke || "blue";
this.strokeWidth = strokeWidth || 3;
}
}
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit, AfterViewInit {
arr_item = [
{
left: ["Lorem Ipsum", "ABC"],
right: ["Hello World", "Hello"]
},
{
left: [
"Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
"Lorem Ipsum is simply dummy text"
],
right: [""]
},
{
left: [
"",
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s"
],
right: ["Rainbow"]
}
];
#ViewChildren("left", { read: ElementRef }) left: QueryList<ElementRef>;
#ViewChildren("right", { read: ElementRef }) right: QueryList<ElementRef>;
polylines: Polyline[] = [];
ngAfterViewInit() {
this.drawLines();
}
ngOnInit(): void {
window.addEventListener("resize", () => {
this.drawLines();
});
}
getPos_line(ele) {
var className = ele.getAttribute("class");
if (className.indexOf("left") !== -1) {
return {
x: Math.round(ele.offsetLeft + ele.offsetWidth),
y: Math.round(ele.offsetTop + ele.offsetHeight / 2)
};
} else {
return {
x: Math.round(ele.offsetLeft),
y: Math.round(ele.offsetTop + ele.offsetHeight / 2)
};
}
}
getMaxX_Left(arr: any) {
if (arr.length >= 1) {
var maxX = this.getPos_line(arr[0]).x;
for (var i = 0; i < arr.length; i++) {
var posLine = this.getPos_line(arr[i]);
if (posLine.x > maxX) {
maxX = posLine.x;
}
}
return maxX;
} else {
return null;
}
}
getMinX_Right(arr: any) {
if (arr.length >= 1) {
var minX = this.getPos_line(arr[0]).x;
for (var i = 0; i < arr.length; i++) {
var posLine = this.getPos_line(arr[i]);
if (posLine.x < minX) {
minX = posLine.x;
}
}
return minX;
} else {
return null;
}
}
getCenY(left, right) {
if (left.offsetHeight >= right.offsetHeight) {
return Math.round(left.offsetTop + left.offsetHeight / 2);
} else {
return Math.round(right.offsetTop + right.offsetHeight / 2);
}
}
drawLines() {
this.polylines = [];
for (var i = 0; i < this.arr_item.length; i++) {
var polylines_temp: Polyline[] = [];
var left_children = this.left.get(i).nativeElement.children;
var right_children = this.right.get(i).nativeElement.children;
var left_childrenLen = left_children.length;
var right_childrenLen = right_children.length;
var maxX_left = this.getMaxX_Left(left_children);
var minX_right = this.getMinX_Right(right_children);
var cenY = this.getCenY(
this.left.get(i).nativeElement,
this.right.get(i).nativeElement
);
var cenX = Math.round((maxX_left + minX_right) / 2);
var space = 0;
if (left_childrenLen > 1 && right_childrenLen > 1) {
space = 10;
}
for (var j = 0; j < left_childrenLen; j++) {
var posLine = this.getPos_line(left_children[j]);
polylines_temp.push(
new Polyline(`
${posLine.x},${posLine.y}
${cenX - space},${posLine.y}
${cenX - space},${cenY}
${cenX},${cenY}
`)
);
}
for (var j = 0; j < right_childrenLen; j++) {
var posLine = this.getPos_line(right_children[j]);
polylines_temp.push(
new Polyline(`
${cenX},${cenY}
${cenX + space},${cenY}
${cenX + space},${posLine.y}
${posLine.x},${posLine.y}
`)
);
}
this.polylines = this.polylines.concat(polylines_temp);
}
}
}
Link to Stackblitz
clearcanv() {
while (this.svg1.lastChild) {
this.svg1.removeChild(this.svg1.lastChild);
}
this.lines = [];
console.log(this.lines.length);}
drawcanv() {
var body = document.body,
html = document.documentElement;
var pheight = Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight
);
this.svg1.setAttribute("id", "svg");
var j2 = 0;
for (let i = 0; i < this.stages.length; i++) {
j2 = 0;
for (let j = 0; j < this.stages[i].Hist.length; j += 2) {
//Get Position of elements
var element1A = document.getElementById(i.toString() + j.toString());
var element1B = document.getElementById(
i.toString() + (j + 1).toString()
);
var element2 = document.getElementById(
(i + 1).toString() + j2.toString()
);
const x1A = element1A.offsetLeft + element1A.clientWidth;
const x1B = element1B.offsetLeft + element1B.clientWidth;
const x2 = element2.offsetLeft;
const y1A =
element1A.offsetTop + element1A.getBoundingClientRect().height / 2;
const y1B =
element1B.offsetTop + element1B.getBoundingClientRect().height / 2;
const y2 =
element2.offsetTop + element2.getBoundingClientRect().height / 2;
const halfwayx = Math.abs(x1A + x2) / 2;
const halfwayy = Math.abs(y1A + y1B) / 2;
//=======================================================================================
this.DrawLine(x1A, halfwayx, y1A, y1A, pheight);
this.DrawLine(x1B, halfwayx, y1B, y1B, pheight);
this.DrawLine(halfwayx, halfwayx, y1A, halfwayy, pheight);
this.DrawLine(halfwayx, halfwayx, y1B, halfwayy, pheight);
j2++;
}
}}
DrawLine(xs: number, xd: number, ys: number, yd: number, PageHeight: Number) {
//creating a line and adding design
this.lines.push(
document.createElementNS("http://www.w3.org/2000/svg", "line")
);
this.lines[this.lines.length - 1].setAttribute("stroke-width", "1px");
this.lines[this.lines.length - 1].setAttribute("stroke", "#000000");
this.lines[this.lines.length - 1].setAttribute(
"marker-end",
"url(#triangle)"
);
//=======================================================================================
//appending to and styling the svg
this.svg1.appendChild(this.lines[this.lines.length - 1]);
document.body.appendChild(this.svg1);
this.svg1.style.position = "absolute";
this.svg1.style.top = "0";
this.svg1.style.left = "0";
this.svg1.style.width = "100%";
this.svg1.style.height = PageHeight.toString();
this.svg1.style.zIndex = "-2";
//==================================================================s=====================
//Drawing the line.
this.lines[this.lines.length - 1].setAttribute("x1", `${xs}`);
this.lines[this.lines.length - 1].setAttribute("x2", `${xd}`);
this.lines[this.lines.length - 1].setAttribute("y1", `${ys}`);
this.lines[this.lines.length - 1].setAttribute("y2", `${yd}`);
//=======================================================================================}
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.
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';
}
}
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.