The scenario of my project is:
when user clicks on "Animals" tile it should expand and other tiles should replace. Similarly, on click of "Plants" tile it should expand and other tiles should reorder.
===========1st View:============
===========2nd View==============
1st page shows, tile view
2nd page shows, the expanded div on click of the tile.
Please help me achieve this.
My code is as below. I am able to show hide the expanded tile but I am unable to hide the tile on which the user clicked and reordering the tile. Any hints to achieve this? I dont want expanding and contracting animations.
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular 6';
showDiv:boolean = false;
showDetails():void{
this.showDiv = !this.showDiv;
}
}
<div *ngIf="showDiv" style="width:450px;height:150px;border:2px solid black">
Expanded div
</div>
<br/>
<div (click)="showDetails()" style="width:100px;height:100px;border:2px solid black;clear:both;float:left">
tile 1
</div>
<div style="width:100px;height:100px;border:2px solid black;float:left;margin-left:10px">
tile 2
</div>
<div style="width:100px;height:100px;border:2px solid black;float:left;margin-left:10px">
tile 3
</div>
I would suggest you use flexbox ( css ) for this.
On click of these tiles just change the flexbox properties.
I would recommend using the bootstrap classes like col-lg-12 with ngClass and variable whose value changes on click on the base of this variable change the bootstrap classes from col-lg-3 to col-lg-12
This problem can be solved using ViewChild decorator but I don't think such complicated solution is needed for this problem. We can instead simplify logic behind displaying tiles.
First of all we can move tile definitions to component and store them in tiles array;
tiles = [
{ name: 'Animals' },
{ name: 'Plants'},
{ name: 'Organisms'},
];
Then we create variable representing currently expanded tile and array with all not expanded tiles.
expandedTile = null;
notExpandedTiles = this.tiles; // at the beginning all tiles are not expanded
Having all this data, template can be refactored with usage of *ngFor directive. Additionaly inline styles were moved to separate css file.
<div *ngIf="expandedTile" class="expanded-tile">
{{expandedTile.name}}
</div>
<br/>
<div class="tiles">
<div *ngFor="let tile of notExpandedTiles;" (click)="showDetails(tile)" class="tile">
{{tile.name}}
</div>
</div>
When user click on some not expanded tile, it'll be passed as argument to showDetails function which will modify appropriate data.
showDetails(clickedTile) {
// assign clickedTile to expandedTile
this.expandedTile = clickedTile;
// Assign array of all tiles except clickedTile to notExpandedTiles
this.notExpandedTiles = this.tiles.filter(tile => tile !== clickedTile);
}
css:
.tile, .expanded-tile {
border: 2px solid black;
}
.tile {
display: inline-block;
width: 100px;
height: 100px;
margin-right: 10px;
}
.expanded-tile {
width: 450px;
height: 150px;
}
demo: https://stackblitz.com/edit/angular-8wekfm
Related
I'll explain. I've a 1D array of objects. I want to loop through this array. There is a property in that object i.e. widgetType. Depending on this type only I've to decide the grid size in css. Here's the template:
<div class="grid-container">
<div *ngFor="let widget of staticKpi">
<p>widget.value</p>
</div>
</div>
Here's the css:
.grid-container {
display: grid;
grid-template-columns: auto auto auto;
padding: 10px;
}
Right now I'm getting 3 columns because of this line grid-template-columns: auto auto auto;. I want to set the number of columns based on widgetType because different widget types have a different arrangement. For example two widgets can have 6 values but one is 2X3 and the other type can be 3X2. I want something like this:
<div <div [ngClass]="{'classname' : condition}"></div>
<div *ngFor="let widget of staticKpi">
<p>widget.value</p>
</div>
</div>
But it is outside the loop. Whats the best approach for this. Please help.
I think easiest approach is to calculate the style in TS upon receiving the widgets data, e.g. (I'm doing a lots of guessing here, since no actual code was provided, but it should get you on track):
staticKpi: Widget[];
gridContainerStyle: string;
getData(): void {
this.kpiService.getData().subscribe(data => {
this.staticKpi = data;
this.gridContainerStyle = calculateStyle(data);
});
}
private calculateStyle(widgets: Widget[]): string {
// Implement actual logic based on passed widgets
const columnsValue = 'auto auto auto';
return {
'display': 'grid',
'grid-template-columns': columnsValue,
'padding': '10px',
}
}
And in template:
<div [ngStyle]="gridContainerStyle">
<div *ngFor="let widget of staticKpi">
<p>widget.value</p>
</div>
</div>
I know how to use ::ng-deep to apply styles to specific component's internal parts, but I'm looking for a more correct solution that does not require the parent to know about the components parts, and specifically to set dimensions of the component (width and hight).
Here's the full scenario:
#Component({
selector: 'app-root',
template: '<h1>Hello World!<h1><app-green-box></app-green-box>',
styles: [ 'app-green-box { width: 50%; }' ],
})
export class AppComponent {
}
#Component({
selector: 'app-green-box',
template: '<div class="green-box">Bye</div>',
styles: [ '.green-box { border: solid black 1px; background: green; }' ],
})
export class GreenBoxComponent {
}
As you can see, AppComponent would like a green box that is 50% wide. GreenBoxComponent does not have a way to know that (another parent might want it at 70% or at 10%).
Unfortunately, the above code doesn't work (I'm not exactly sure why - the custom element doesn't actually take up space as it has no visual presentation by itself, and so it can't be a bounding box for the content?).
Using ::ng-deep requires the parent component to know that GreenBoxComponent uses a <div> for its internal box implementation, but is there a way to have the component just "copy" the styles from the host to whatever internal part it needs to apply these to?
Actually, after trying to explain to myself the reason that the custom element isn't a bounding box (in the question), I figured out the problem: the default display for the custome element is inline so it doesn't obey the size restrictions of the parent.
The solution is for GreenBoxComponent to understand that it represents a box and not an inline element and that parents would like to treat it as a box - so it should force its host element to have display: block, like so:
#Component({
selector: 'app-green-box',
template: '<div class="green-box">Bye</div>',
styles: [
':host { display: block; }',
'.green-box { border: solid black 1px; background: green; }',
],
})
export class GreenBoxComponent {
}
I am trying to create a text editor and have created a custom element in angular using an editable div.
I will have to set some initial value to the content of the div in some cases and capture the typed data in the div in some cases.
But I am getting three different behaviors for the same div based on the initial value that is set.
I want the div to behave like a regular input field but I don't want to use the actual Input element.
I have made a working example of the problem in the Stackblitz demo.
The components code is as below.
The template HTML for the element component:
<div contentEditable='true' (input)="update($event)">{{value}}</div>
<p>{{value}}</p>
The typescript file for the component element:
import { Component, Input } from '#angular/core';
#Component({
selector: 'element',
template: `<div contentEditable='true' (input)="update($event)">{{value}}</div>
<p>{{value}}</p>
`,
styleUrls: ['./element.component.css']
})
export class ElementComponent {
#Input() value: string;
update(event) {
this.value = event.target.innerText;
}
}
The CSS:
div{
background: black;
margin: auto;
color:white;
font-size: 2em;
}
The three different behaviors are:
If you do not set an initial value to the value input field, as you
type in the div, the text gets appended to itself continuously.
If you provide an initial value to the value input field, as you type
in the div, the cursor resets it to the initial position.
Whether you provide an initial text or you type something in the empty div, If you select all the text from the div and hit delete or backspace
and start typing in it. It behaves as a normal textbox.
I am expecting the 3rd behavior, in example after you clear the element the way it behaves.
Possible explanation: You haven't declared the bound value.
Workaround:
Adding:
[textContent]="model" when declaring template div
model;
ngOnInit(){
this.model = this.value;
}
inside ElementComponent export class
Stackblitz Demo
Related:
How to use [(ngModel)] on div's contenteditable in angular2?,
https://github.com/KostyaTretyak/ng-stack/tree/master/projects/contenteditable
I have 2 different html file,
test1.html
test2.html
test2.component.ts
1) test1.html :
<div style="width:100%;height:20%"></div>
2) test2.html :
If I click rectangle function, test1.html border radius should change as 0px.
If I click roundCorder function, test1.html border radius should change as 10px;
<img (click)="rectangle()" [src]="imgSrc" (mouseover)="rectangleHover()" (mouseout)="rectangleOut()" style="height:100px;float:left;margin:15px;" >
<img (click)="roundCorner()" [src]="imgSrc1" (mouseover)="roundHover()" (mouseout)="roundOut()" style="height:100px;float:left;margin:15px;">
3) test2.components.ts :
roundCorner(){
// want to change test1.html file div border radius as 10px;
}
You can simply pass the HTML and add the styles to it as below,
HTML
<div #divElement style="width:100%;height:20%"></div>
<button (click)="rectangle(divElement)">Rectangle</button>
<button (click)="circle(divElement)">Circle</button>
Typescript
rectangle(divElement) {
divElement.style.borderRadius = '0px'
console.log(divElement.style)
}
circle(divElement) {
divElement.style.borderRadius = '50%'
console.log(divElement.style)
}
Stakblitz
Is test1 is your parent component and test2 is your child component. If thats the case, you can emit an event using EventEmitter() with value rectangle/square from Child component. You can read that event in parent component and update the DOM using ngStyle.
test1.html
<div #elem1 style="width:100%;height:20%"></div>
test2.html
<img #elem2 (click)="rectangle()" [src]="imgSrc" (mouseover)="rectangleHover()" (mouseout)="rectangleOut()" style="height:100px;float:left;margin:15px;" >
<img (click)="roundCorner()" [src]="imgSrc1" (mouseover)="roundHover()" (mouseout)="roundOut()" style="height:100px;float:left;margin:15px;">
test2.component.ts
import {ElementRef,Renderer2} from '#angular/core';
#ViewChild('elem2') el:ElementRef;
constructor(private rd: Renderer2,sharedService:SharedService) {
this.el=this.sharedService.get();
}
roundCorner(){
// want to change test1.html file div border radius as 10px;
this.el.style.border-radius='10px';
}
rectangle(){
this.el.style.border-radius='0px';
}
shared.service.ts
#Injectable()
class SharedService{
let elem1:any
set(element:any)
{
this.elem1=element;
}
get(){
return this.elem1;
}
test1.component.ts
#ViewChild('elem1'):ElementRef
constructor(sharedService:SharedService,elementRef:ElementRef){
}
ngOnInit()
{
this.sharedService(this.elem1);
}
What you have to use is a shared service to access this dom element reference variable between both the components and then set the property of border-radius respectively
import {SharedService} in both the component.ts files and app.module.ts and put SharedService under providers array in app.module.ts
i have several divs and if a div is clicked, i want to hightlight the div by changing the background-color of the div as well as the text in the div(i.e. make it active). I have done this before using JQuery by defining a class in my css and dynamically removing and adding the class. How can this be accomplished in Angular2. From researching and i shouldn't be manipulating the DOM as this is a no no in angular2. I have looked online for several examples but no success. Please
One way is to make an attribute directive. Fortunately for you, Angular has a guide for doing almost exactly what you are looking for but with hover events. To respond to clicks instead, for the 'Respond to user action' section...
... replace this:
#HostListener('mouseenter') onMouseEnter() {
/* . . . */
}
#HostListener('mouseleave') onMouseLeave() {
/* . . . */
}
with this:
#HostListener('click') onMouseClick() {
/* . . . */
}
To acheive a result where clicking a div that is already highlighted will unhighlight it, the full directive code would be:
import { Directive, ElementRef, HostListener, Input } from '#angular/core';
#Directive({
selector: '[myHighlight]'
})
export class HighlightDirective {
private _defaultColor = 'red';
private el: HTMLElement;
private highlighted: boolean = false;
constructor(el: ElementRef) { this.el = el.nativeElement; }
#Input('myHighlight') highlightColor: string;
#HostListener('click') onMouseClick() {
this.highlighted ? this.highlight('') : this.highlight(this.highlightColor || this._defaultColor);
}
private highlight(color: string) {
this.el.style.backgroundColor = color;
}
}
To use the directive, you apply it to an element like so:
<!-- With default color -->
<div [myHighlight]>Click to highlight me!</div>
<!-- With custom color -->
<div [myHighlight]="yellow">I'm a div, la di da.</div>
Just set a property depending on what item was clicked and set a class if the value matches the one of the current item. Use CSS to apply styles:
#Component({
styles: [`
div.highlight {
background-color: red;
}`
],
template: `
<div (click)="highlighted = 1" [class.highlight]="highlighted === 1">div1</div>
<div (click)="highlighted = 2" [class.highlight]="highlighted === 2">div2</div>
<div (click)="highlighted = 3" [class.highlight]="highlighted === 3">div3</div>
`
})
class MyComponent {
hightlighted:number = 0;
}