I have referred to other questions on SO related to expand/collapse, but the solutions are mostly based on setting height CSS property. I have tried, but those solutions are not applicable to my use case.
I have long string, if its length exceeds a limit I want to clip it by default and expand it by clicking on "Show more". On clicking "Show less", it should collapse the long string. I have implemented the functionality but the transition from collapsed to expanded does not have animation and looks jarring.
I would like to know how the collapsed/expanded transitions could made nicer.
Here's stackblitz reproduction of the same:
https://stackblitz.com/edit/angular-ivy-exalqr?file=src/app/app-expand/app-expand.component.ts
Just use the below css, where you set the width to the number of characters you want to show.
html
.cropper-wrapper {
width: 95px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
transition: all 1s ease-in-out;
}
.isCollapsed {
width: 100% !important;
display: inline-block;
}
css
<span class="cropper-wrapper" [ngClass]="{ isCollapsed: !isCollapsed }"
>{{ str }}
</span>
<span (click)="toggle()">{{ isCollapsed ? 'Show More' : 'Show less' }}</span>
ts
import {
ChangeDetectionStrategy,
Component,
Input,
OnInit,
} from '#angular/core';
#Component({
selector: 'app-expand',
templateUrl: './app-expand.component.html',
styleUrls: ['./app-expand.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppExpandComponent implements OnInit {
isCollapsed = false;
#Input() str;
private _input: string = '';
private _originalInput: string = '';
constructor() {}
ngOnInit() {
this.toggle();
}
toggle() {
if (this.isCollapsed) {
this._input = this._originalInput;
} else {
if (this._originalInput.length > 10) {
this._input = this._originalInput.substring(0, 10) + '...';
}
}
this.isCollapsed = !this.isCollapsed;
}
}
forked stackblitz
Related
I have implemented click and hover functionaly over an element , from presvious answer I had tackled the popup position relative to mousepointer.
But now I want to fix the popup modal position when hovered on say vertically middle by the side wrt the hovered element.
Issue when I hover on the element the popup modal starts flickering and when I click in some other place within the element somehow then the modal opens up finally.
Onclick
To show the popup modal in full screen when clicked on(this thing works but not in the correct way,flickering thing) + when popup modal being hovered it should be kept open.
Once the popup modal is opened the popup modal can only be closed once click happens anywhere outside of the modal
OnHover
To show the popup modal fixed vertically centre by the side wrt the hovered element(this thing as well works but positioning is not correct) +
when popup modal being hovered it should be kept opened.
In this case as soon as the mouse leaves from the element or from the popup modal , the popup modal should close.
Minimum Reproducible Example
https://stackblitz.com/edit/angular-yybbgm?file=src/app/app.component.html
app.component.html
<div class="box"
(mouseenter)="addClickEvent($event)"
(mouseleave)="addClickEvent($event)"
(mousemove)="addClickEvent($event)"
(click)="addClickEvent($event)">
</div>
<fs-modal *ngIf="modalShow"
[ngStyle]="{'top.px': (zoneIsClicked ? 0 : modaltop) ,
'left.px':(zoneIsClicked ? 0 : modalleft)}"
[ngClass]="zoneIsClicked ? 'zoneClicked' : 'zoneNotClicked'">
</fs-modal>
app.component
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
name = 'Angular';
modalShow = false;
modalleft;
modaltop;
zoneIsClicked;
addClickEvent(e) {
if(e.type === 'click'){
this.modalShow = true;
this.zoneIsClicked = true;
}
/*else if (e.type === 'mousemove') {
this.modalleft = e.clientX
this.modaltop = e.clientY
}*/
else if (e.type === 'mouseenter') {
this.modalShow = true;
this.zoneIsClicked = false;
this.modalleft = e.clientX
this.modaltop = e.clientY
}
else if (e.type === 'mouseleave') {
this.modalShow = false;
}
}
}
app.component.css
.box{
width: 100px;
height: 100px;
background: rgba(254, 249, 247, 1);
border: 1.5px solid #e24301;
margin: 50px;
font-size: 0.8rem;
position: absolute;
}
.zoneClicked{
display: block;
position: absolute;
width: 900px;
}
.zoneNotClicked{
position: absolute;
width: 50%;
}
fsmodal.component
import { Component, Input } from '#angular/core';
#Component({
selector: 'fs-modal',
template: `<div [ngStyle]="{'border':'1px solid black'}">ok</div>`,
styles: [`h1 { font-family: Lato; }`]
})
export class fsModalComponent {
}
Make some changes in your ts file. Remove the mousemove event for the box and handle the mouseleave event of box div
https://stackblitz.com/edit/angular-rkve7b?file=src/app/app.component.ts
You can use the getBoundingClientRect() to find exact position of the hovered element and set those values to your popup. Please find the demo below.
StackBlitz Demo
I am trying to create a dynamic button with an onclick event. The showname() defined the on same Component.ts. But there is no response on clicking the button
Component.ts
createtooltip() {
this.tooltip = document.createElement('div');
this.tooltip.style.cssText =
'position:absolute; background:black; color:white; padding:4px;z-index:10000;' +
'border-radius:2px; font-size:12px;box-shadow:3px 3px 3px rgba(0,0,0,.4);' +
'opacity:0;transition:opacity 0.3s';
this.tooltip.innerHTML = '<button id="popup" (click)="showname()" >Copy!</button>';
document.body.appendChild(this.tooltip);
}
showname() {
console.log("Hi User");
}
Could anyone help me to find the solution?
You won't have access to the document object everywhere.
So, you shouldn't be using document functions to do DOM manipulations. All these DOM Manipulations should be done only using Rendere2. If there's anything that you want to access on the DOM, you should do it using #ViewChild
Here's an Example:
import { Component, Renderer2, ElementRef, ViewChild } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
name = 'Angular';
#ViewChild('tooltip') tooltip: ElementRef;
constructor(private renderer: Renderer2) {}
createtooltip() {
this.renderer.setAttribute(this.tooltip.nativeElement, 'class', 'my-button');
const button = this.renderer.createElement('button');
this.renderer.setProperty(button, 'id', 'popup');
this.renderer.setProperty(button, 'innerText', 'Copy');
this.renderer.listen(button, 'click', (event) => {
this.showname();
})
this.renderer.appendChild(this.tooltip.nativeElement, button);
}
showname() {
console.log("Hi User");
}
}
In template:
<button (click)="createtooltip()">Create Tooltip</button>
<div #tooltip>
</div>
In CSS:
p {
font-family: Lato;
}
.my-button {
position:absolute;
background:black;
color:white;
padding:4px;
z-index:10000;
border-radius:2px;
font-size:12px;
box-shadow:3px 3px 3px rgba(0,0,0,.4);
opacity:0;
transition:opacity 1s linear;
}
Here's a Sample StackBlitz for your reference.
I solved this problem by the other way
<a ngFor="let link of links" (click)="actions[link]()">Click</a>
`
actions: any = {
link1: () => this.func1(),
link2: () => this.func2()
}
`
angular doesnt compile the dynamic created HTML elements . u have to use ng-template like this :
<ng-template #myTemplate>
<div styles="...">
<button id="popup" (click)="showname()" >Copy!</button>
</div>
</ng-template>
It would be better to create the button with directs like *ngFor or *ngIf rather than create the elements like you do with Jquery.
This is due to the nature of Angular, which prioritizes and eases the usage of directives over simple javascript.
to do this you can :
HTML :
<button id="popup" (click)="showname()" *ngIf='elements.showNameButton==true' >Copy!</button>
** TS :**
elements={
showNameButton:false
}
createtooltip(){
this.elements.showNameButton =true;
}
showname() {
console.log("Hi User");
}
Problem: I want a single component (spacer) that will have width 100% and a height that can be input wherever it appears in the HTML (home.html in this test):
number 1
<spacer height="'200px'"></spacer>
no more
The spacer.html:
<div class="container-fluid spaceContainer" [ngStyle]="{'height': 'height'}">
spacer is here <<<--- this text is just for testing
</div>
The scss:
.spaceContainer {
width: 100%;
border: 1px solid red;
display: flex;
flex-direction: row;
}
Spacer.ts:
import {Component, Input, OnInit} from '#angular/core';
#Component({
selector: 'spacer',
templateUrl: './spacer.component.html',
styleUrls: ['./spacer.component.scss']
})
export class SpacerComponent implements OnInit {
#Input() height: string;
constructor() {
}
ngOnInit() {
console.log('height is '+ this.height);
}
}
When it runs, the console.log gives: height is '200px'
but the height of the red-bordered box is just enough to hold the 'spacer is here' text.
I struggle understanding binding a bit so I've tried:
<spacer height="200px"></spacer>
Console: height is 200px, which I thought would work but no change. Not understanding attr, I tried variants of attr.height.
This has to be easy and may help clear up my misunderstanding of how binding works.
Thanks in advance,
Yogi
Your mistake is located at this line:
[ngStyle]="{'height': 'height'}"
^^^^^^^^^^
it should be just height
You're binding height to string 'height' but you should bind it to height property of your component something like:
[ngStyle]="{'height': height}">?
or
[style.height]="height"
I am trying the following lines to set the background image.but it not works. what are the way set background image in constantly in my application.
app.component.html
<div [ngStyle]="{'background' : 'url(./images/trls.jpg)'}">
<router-outlet></router-outlet>
<alert></alert>
</div>
You can use ngStyle to set background for a div
<div [ngStyle]="{background-image: 'url(./images/' + trls.img + ')'}"></div>
or you can also use built in background style:
<div [style.background-image]="'url(/images/' + trls.img + ')'"></div>
This works for me:
put this in your markup:
<div class="panel panel-default" [ngStyle]="{'background-image': getUrl()}">
then in component:
getUrl()
{
return "url('http://estringsoftware.com/wp-content/uploads/2017/07/estring-header-lowsat.jpg')";
}
Below answer worked for angular 4/5.
In app.component.css
.image{
height:40em; background-size:cover; width:auto;
background-image:url('copied image address');
background-position:50% 50%;
}
Also in app.component.html simply add as below
<div class="image">
Your content
</div>
This way I was able to set background image in Angular 4/5.
If you plan using background images a lot throughout your project you may find it useful to create a really simple custom pipe that will create the url for you.
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'asUrl'
})
export class BackgroundUrlPipe implements PipeTransform {
transform(value: string): string {
return `url(./images/${value})`
}
}
Then you can add background images without all the string concatenation.
<div [ngStyle]="{ background: trls.img | asUrl }"></div>
In Html
<div [style.background]="background"></div>
In Typescript
this.background=
this.sanitization.bypassSecurityTrustStyle(`url(${this.section.backgroundSrc}) no-repeat`);
A working solution.
What is the recommended way to dynamically set background image in Angular 4
this one is working for me also for internet explorer:
<div class="col imagebox" [ngStyle]="bkUrl"></div>
...
#Input() background = '571x450img';
bkUrl = {};
ngOnInit() {
this.bkUrl = this.getBkUrl();
}
getBkUrl() {
const styles = {
'background-image': 'url(src/assets/images/' + this.background + '.jpg)'
};
console.log(styles);
return styles;
}
I wanted a profile picture of size 96x96 with data from api. The following solution worked for me in project Angular 7.
.ts:
#Input() profile;
.html:
<span class="avatar" [ngStyle]="{'background-image': 'url('+ profile?.public_picture +')'}"></span>
.scss:
.avatar {
border-radius: 100%;
background-size: cover;
background-position: center center;
background-repeat: no-repeat;
width: 96px;
height: 96px;
}
Please note that if you write background instead of 'background-image' in [ngStyle], the styles you write (even in style of element) for other background properties like background-position/size, etc. won't work. Because you will already fix it's properties with background: 'url(+ property +) (no providers for size, position, etc. !)'. The [ngStyle] priority is higher than style of element. In background here, only url() property will work. Be sure to use 'background-image' instead of 'background'in case you want to write more properties to background image.
A very easy solution is to declare the image with a loading class and remove this class when image is loaded. You can then customize the placeholder in CSS, as it should be.
HTML :
<img class="persona-avatar loading" #avatar
(load)="avatar.classList.remove('loading'); "
src="/assets/img.png"
alt=""/>
SCSS :
.persona-avatar {
width: 50px;
height: 50px;
&.loading {
background-image: url("data:image/png;base64,iVBORw0K...");
background-size: contain;
}
}
It is better to use a base64 image to not have the same load problem with the placeholder.
This can be done with a custom directive, instead of having a method that will execute in a loop affecting performance.
In my case I would created a BackImgDirective like this:
import {Directive, ElementRef, Input, OnInit, Renderer2} from "#angular/core";
#Directive({ selector: '[appBackImg]'})
export class BackImgDirective implements OnInit {
#Input() appBackImg = ''
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngOnInit() {
this.setBackImg()
}
setBackImg(){
this.renderer.setStyle(this.el.nativeElement, 'background-image', 'url(' + this.appBackImg + ')')
}
}
And use it like this:
<div [appBackImg]="slide.image"> </div>
I have two components: BuilderComponent and InputTextComponent.
With drag and drop I put a directive in the sortableList element of the BuilderComponent template. With the inspector I can see the directive appears in the sortablelist DOM element:
<div class="row">
<div class="col-md-12">
<ul id="sortableList">
<zbjfb-input-text></zbjfb-input-text>
</ul>
</div>
How can I force the BuilderComponent to detect that the content of the template is changed since the last compile and then re-render the template with the new added directive so I can see the new compiled InputTextComponent.
BuilderComponent:
import { Component } from '#angular/core';
#Component({
selector: 'zbjfb-builder',
template: '
<div class="row">
<div class="col-md-12">
<ul id="sortableList">
</ul>
</div>
</div>
'
})
export class BuilderComponent {}
InputTextComponent:
import { Component } from '#angular/core';
#Component({
selector: 'zbjfb-input-text',
templateUrl: './input-text.component.html',
styleUrls: ['./input-text.component.css']
})
export class InputTextComponent {}
Any idea?
Thanks in advance.
If your drag'n'drop solution is HTML5 based, maybe you can make use of HostListener decorator from '#angular/core' to hack your drag and drop exchange logic:
Add something like this on your drop area component:
#HostListener('drop', ['$event'])
onDrop(event: any) {
//do something
}
And something like this on your draggable component:
#HostListener('dragstart', ['$event'])
onDrag(event: any) {
//do something
}
HostListeners provide you ways to implement conventional listeners for element events like onmousedown, onkeyup, ondrop, ondrag.
Then think of some logic to identifiy what's being dragged/dropped and change the drag area component model. Here's some functional code I've made inspired by W3Schools topic on drag'n'drop:
import { Component, HostListener, Input } from '#angular/core';
// The draggable component
#Component({
selector: 'dragme',
template: `
<div draggable="true">
Drag {{name}}!
</div>
`,
styles: [`
[draggable] {
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
user-select: none;
-khtml-user-drag: element;
-webkit-user-drag: element;
background-color: #AAA;
border: 1px solid black;
padding: 24px;
margin: 12px;
}
`]
})
export class DragMe {
#Input()
name:string = "";
#HostListener('dragstart', ['$event'])
onDrag(event:any){
event.dataTransfer.setData("name",this.name);
}
}
// The drop area component
#Component({
selector: 'drop',
template: `
<div class="drop">
Drop over me!
<ul>
<li *ngFor="let i of items">{{i}}</li>
</ul>
</div>
`,
styles: [`
.drop{
border: 1px solid black;
padding: 24px;
}
`]
})
export class DropOverMe {
items:string[] = [];
#HostListener('dragover', ['$event'])
onDragover(event:any){
event.preventDefault();
}
#HostListener('drop', ['$event'])
onDrop(event:any){
event.preventDefault();
var name = event.dataTransfer.getData("name");
this.items.push(name);
}
}
// The functional example
#Component({
selector: "drag-example",
template: `
<dragme name="Bob"></dragme>
<dragme name="Alice"></dragme>
<dragme name="Carl"></dragme>
<drop></drop>
`
})
export class DragExample{
}
Full code at: https://github.com/rafaelodon/angular2-html5-dragndrop-example