Element class names with different values in Angular2 - javascript

I'm trying to update multiple circle bars on a array of data coming from the database.
Here is my html:
<div class="row pie-charts">
<ba-card *ngFor="let goal of pGoal; let i = index" class = "pie-chart-item-container col-xlg-3 col-lg-3 col-md-6 col-sm-12 col-xs-12">
<div class="pie-chart-item">
<div class = "pie-chart-item">
<div class="circle-{{i}}"></div>
</div>
</div>
<div class="description">
<div>{{ goal.Name }}</div>
<div class="description-stats">{{ goal.GoalDescription }}</div>
</div>
<i class="chart-icon i-person"></i>
</ba-card>
</div>
Here is my angular code:
ngOnInit() {
this._personalgoalsService.getGoalSummary()
.subscribe( result => {
this.pGoal = result;
jQuery('.circle-0').circliful({
percent: 33
});
jQuery('.circle-1').circliful({
percent: 88
});
})
}
If I hard code the class name when I run circliful (or any other js chart) they all get updated with the same values. There must be easy way to create multiple objects of the same class names with different values. So I tried to use the ngFor and add the - index number. It seems when I create a class or id dynamically in html it doesn't get created correctly in the dom and then I can't find it in the js.
Thanks,
Edit:
Basically I was trying to update the same "element" with different values. I wasn't sure how to create multiple circle graphs of the same type with different values.
<div class="row pie-charts">
<circle *ngFor="let goal of goals | async" [goal]="goal"></circle>
</div>
So I created a component and passed in my goal object. But I still wasn't getting a unique element to create the circle. Actually my array of goals was 2, but I was getting 4 graphs drawn with the same default value.
Then I created a CircleDirective :
#Directive(
{
selector: '[myCircle]'
}
)
export class CircleDirective implements OnInit{
#Input() goal : iPersonalGoalModel;
private el: HTMLElement;
constructor(el: ElementRef) {
this.el = el.nativeElement;
}
ngOnInit() {
jQuery(this.el).circliful({
percent : this.goal.GoalCompletionPercentage
});
}
}
#Component({
selector: 'circle',
encapsulation: ViewEncapsulation.None,
directives: [CircleDirective],
providers: [],
styles: [require('./circle.css')],
template: require('./circle.html')
})
export class CircleComponent implements OnInit {
#Input() goal : iPersonalGoalModel;
ngOnInit() {
console.log(this.goal.GoalCompletionPercentage);
}
}
So now my component is using the circle directive. This directive is called in the circle.html.
<div myCircle
class="circle"
[goal]="goal"
data-dimension="250"
data-info="Sweet"
data-width="30"
data-fontsize="38"
data-fgcolor="#61a9dc"
data-bgcolor="#eee"
data-fill="#ddd"
data-total="100"
>
</div
Notice two things here. I'm using the attribute myCircle and I'm passing the goal through to the circle directive. I know there are some ways to clean this up, but this works. It only displays two circle graphs and with the two correct values.
The frustration was I knew what the problem was, but I ran around the solution. The problem right now there is multiple solutions for the same problem that are simplistic in nature. I hope this helps someone in the future.

Related

Adding a eventListener to elements with Class

i have this challenge and i cant figure out how can i implement a function to get the click event on the squares. I want to get the click event on every button with square class.
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-area',
template: `
<div id="statusArea" className="status">Next player: <span>X</span></div>
<div id="winnerArea" className="winner">Winner: <span>None</span></div>
<button id="reset">Reset</button>
<section>
<div class="row" *ngFor="let row of [1, 2, 3]">
<button *ngFor="let col of [1, 2, 3]" class="square" style="width:40px;height:40px;"></button>
</div>
</section>
`,
styles: []
})
export class MainAppComponent implements OnInit {
ngOnInit() {
const btn = document.getElementById('reset')
const squares = Array.from(document.querySelectorAll('.square'))
squares.forEach(square =>{
square.addEventListener('click', function handleClicks(event){
console.log('botão clicado');
})
})
btn.addEventListener('click', function handleClick(event){
console.log("clicado");
})
}
}
Just to make sure that this is clear: You shouldn't be poking around with the DOM in Angular.
The framework already provides mechanisms for Adding Events, Properties Binding and HTML selection (among dozens of more things)
In your case, you were trying to add the click event manually, but the framework got you covered by simply adding the (click)="onSquareClick()" to your button element in the template.
<button *ngFor="let col of [1, 2, 3]" class="square" style="width:40px;height:40px;" (click)="onSquareClick()"></button>
If you really need to play with the DOM, Angular introduces the concept of Directives together with a few utilities that can be handy in such scenarios.
Here you can find some examples

Is there an Angular way of doing: document.getElementById(id).style.display = "none";

I have a div with the id of 1. I'm trying to set the display to none dynamically. Is there an Angular way of doing this. Currently, I'm using vanilla javascript. I was asking about doing this dynamically because there will be over 60 divs that will be created from an array.
In my html
<div *ngFor="let item of items; i = index;">
<div id={{i}} (click)=hideDiv()></div>
</div>
In my method
hideDiv() {
return document.getElementById('1').style.display = "none";
}
That works but I'm looking for the Angular way of doing the above.
It was suggested that I use #ViewChild. Here's what I've changed. I can't use a Template Reference Variable as the html divs are created dynamically. Unless someone can let me know how to create the template variables dynamically. Although I don't think it's possible to create template variables with a loop.
#ViewChild('imgId', { static: true }) elementRef: ElementRef<HTMLDivElement>;
imgId: string;
Then in the method I have:
this.imgId = event.path[0].attributes[1].value;
this.elementRef.nativeElement.style.display = "none";
The event.path[0].attributes[1].value gets me the id of the image. The imgId shows when I console log it. It's still not changing the display on the div to none. Also I'm getting the error:
Cannot read properties of undefined (reading 'nativeElement')
Yes, you can use the ViewChild query in Angular to do this. In your component, define a query like this:
#ViewChild('#1') elementRef: ElementRef<HTMLDivElement>;
Implement the AfterViewInit interface in your component, and inside it, use this:
this.elementRef.nativeElement.style.display = "none";
You can simply use ngIf for this
Component
shouldDisplay: boolean = true;
hide(): void {
this.shouldDisplay = false;
}
show(): void {
this.shouldDisplay = true;
}
Html
<button (click)="hide()">Hide</button>
<button (click)="show()">Show</button>
<div *ngIf="shouldDisplay">this is the content</div>
Here is the working example
This is the Angular way:
template
<div *ngIf="showMe"></div>
or
<div [hidden]="!showMe"></div>
TypeScript:
showMe: boolean;
hideDiv() {
this.showMe = false;
}
For dynamic items where your don't know how many you will get the best approach would be to add a directive that would store and adjust that for you:
#Directive({ selector: '[hide-me]' })
export class HideDirective {
#Input() id!: string;
#HostBinding('style.display')
shouldShow: string = '';
}
then in your component just address them by ID:
#Component({
selector: 'my-app',
styleUrls: ['./app.component.css'],
template: `
<div *ngFor="let item of items; let index = index;">
<div hide-me id="{{index}}" (click)="hideDiv(index)">Some value</div>
</div>
`,
})
export class AppComponent {
#ViewChildren(HideDirective) hideDirectives!: QueryList<HideDirective>;
items = [null, null, null];
hideDiv(id: number) {
this.hideDirectives.find((p) => p.id === id.toString()).shouldShow = 'none';
}
}
Stackblitz: https://stackblitz.com/edit/angular-ivy-pnrdhv?file=src/app/app.component.ts
An angular official example: https://stackblitz.com/edit/angular-ivy-pnrdhv?file=src/app/app.component.ts
How about passing the div reference to the hideDiv method directly in the Dom using a template variable like this.
<div *ngFor="let item of items; i = index;">
<div #divElement (click)=hideDiv(divElement)></div>
And in your hide div method you will have access to the element directly
hideDiv(div) { div.style.display = "none";}
Here is a Stackblitz example
https://stackblitz.com/edit/angular-ivy-w1s3jl
There are many ways to do this, but in my opinion this is a simple solution the achieves your goal with less code.
PS:
It is always recommended to use the angular Renderer2 to manipulate Dom elements. This service has the method setStyle which you can use for your code.

Angular Updating Parent div Class when Click

I have the following code and i wish to update the parent class when click on the image. The image will call "SelectVariation" method when clicked. Is there any way to do this?
component.html :
<div class="clr-row">
<ng-container *ngFor="let variOption of product.variOptions">
<div class="card clickable clr-col-2 variationCard"
*ngFor="let variOptionTwo of variOption.variOptionTwos"> //Update this class
<div class="card-img" (click)="selectVariation(variOptionTwo.id, $event)">
<clr-tooltip>
<img src="{{variOptionTwo.url}}" class="variationImgScale" clrTooltipTrigger>
<clr-tooltip-content clrPosition="top-right" clrSize="m" *clrIfOpen>
<span>{{variOption.optName}} - {{variOptionTwo.optName}}</span>
</clr-tooltip-content>
</clr-tooltip>
</div>
</div>
component.ts :
selectVariation(id: number, event: any) {
//Update parent class
}
In the child component use
#Output() variableHere = new EventEmitter<>();
this.variableHere.emit(this.variableToSend);
Then in the parent associate the variable to a method in html child template definition:
<app-child (variableHere)="manageVariable($event)"></app-achild>
In the parent component define the method and do a variable equels the result of the method for example:
manageVariable(event) {
this.variableToUpdate = event;
}
If you have to check if the variable has changed his state call what you need to check in an ngDoCheck().
Take the advantage of the EventEmitter in angular with output
parent.component.html
<my-child-comp (onSelectVariation)="myVariation($event)" ></my-child-comp>
parent.component.ts
myVariation(myVars) {
console.log(myVars)
}
child.component.html
<button (click)="onSelectVariation.emit(myData)">trigger variation</button>
child.component.ts
#Output() onSelectVariation = new EventEmitter();
Name which you have defined in the output should be used as a event in it host element in parent

Angular - Service injecting dynamic component?

I have a working code which injects any component via a service to the HTML:
ModalWindow.ts:
#Component({
selector: 'modal-window'
template: `
<div class="modal-dialog" role="document">
<div class="modal-content"><ng-content></ng-content></div>
</div>
`
})
export class ModalWindow {
}
Modalcontent.ts :
#Component({
selector: 'modal-content'
template: `
I'm beeing opened as modal!
`
})
export class ModalContent {
}
ModalService.ts :
/*1*/ #Injectable()
/*2*/ export class ModalService {
/*3*/
/*4*/ constructor(private _appRef: ApplicationRef, private _cfr: ComponentFactoryResolver, private _injector: Injector) {
/*5*/ }
/*6*/
/*7*/ open(content: any) {
/*8*/ const contentCmpFactory = this._cfr.resolveComponentFactory(content);
/*9*/ const windowCmpFactory = this._cfr.resolveComponentFactory(ModalWindow);
/*10*/
/*11*/ const contentCmpt = contentCmpFactory.create(this._injector);
/*12*/ const windowCmpt = windowCmpFactory.create(this._injector, [[contentCmpt.location.nativeElement]]);
/*13*/
/*14*/ document.querySelector('body').appendChild(windowCmpt.location.nativeElement);
/*15*/
/*16*/ this._appRef.attachView(contentCmpt.hostView);
/*17*/ this._appRef.attachView(windowCmpt.hostView);
/*18*/ }
/*19*/ }
App.ts:
#Component({
selector: 'my-app',
template: `
<button (click)="open()">Open modal window</button>
`,
})
Result (when click a button which calls this service method ) :
I already know what contentCmpFactory and windowCmpFactory are (lines #8,9)
But I don't udnerstnad what's going on later. Regarding lines #11,#12 - the docs says "creates a new component".
Questions :
1 - line #12 : What does [[contentCmpt.location.nativeElement]] do ? (the docs says its type is projectableNodes?: any[][] - What do they mean ??)
2 - line #14 : What does [[windowCmpt.location.nativeElement]] do ?
3 - line #16,#17 : what and why do I need them if I already did appendChild ? (docs says : Attaches a view so that it will be dirty checked. - so ?).
PLUNKER
Answers:
1) Angular takes ComponentFactory and create component instance with given element injector and with array of projectable nodes
windowCmpFactory.create(this._injector, [[contentCmpt.location.nativeElement]]);
1.1 Element Injector will be used when angular will resolve dependency
const value = startView.root.injector.get(depDef.token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR);
Here is also simple illustration of dependency resolution algorithm for app without lazy loading. With lazy loading it will look a litte more complicated.
For more details see design doc element injector vs module injector
1.2 Projectable nodes are the node elements, which are "projected"(transcluded) in the ng-content that we have in the template of our component.
In order to project something our component template has to contain ng-content node.
#Component({
selector: 'modal-window',
template: `
<div class="modal-dialog">
<div class="modal-content">
<ng-content></ng-content> // <== place for projection
</div>
</div>
`
})
export class ModalWindow {
We can use component above in parent component template as follows:
<modal-window>
<modal-content></modal-content>
<div>Some other content</div>
</modal-window>
So our final result will look like:
<modal-window>
<div class="modal-dialog">
<div class="modal-content">
<modal-content></modal-content> // our projectable nodes
<div>Some other content</div> // replaced ng-content
</div>
</div>
</modal-window>
So when we're passing projectable nodes to create method
windowCmpFactory.create(this._injector, [[contentCmpt.location.nativeElement]]);
we do the same things as described above.
We'are getting reference (contentCmpt.location) to the host element of created early contentCmpt component. This is modal-content element. And then angular will do all magic to project it in ng-content place.
In example above i added one div
<modal-window>
<modal-content></modal-content>
<div>Some other content</div> <== here
</modal-window>
So the real code should looks like:
let div = document.createElement('div');
div.textContent = 'Some other content';
windowCmpFactory.create(this._injector, [[contentCmpt.location.nativeElement, div]]);
In conclusion Why is projectableNodes an any[][]?
2) During the next line
document.querySelector('body').appendChild(windowCmpt.location.nativeElement);
we're getting reference to created in memory modal-window element. ComponentRef allows us to do this because it stores reference to the host element in location getter
export abstract class ComponentRef<C> {
/**
* Location of the Host Element of this Component Instance.
*/
abstract get location(): ElementRef;
and then inseting it in document.body tag as last child. So we see it on the page.
3) Let's say our ModalContent has not just static content but will perform some operations for interaction.
#Component({
selector: 'modal-content',
template: `
I'm beeing opened as modal! {{ counter }}
<button (click)="counter = counter + 1">Increment</button>
`
})
export class ModalContent {
counter = 1;
}
If we remove
this._appRef.attachView(contentCmpt.hostView);
then our view will not being updated during change detection cycle because we created view via ComponentFactory.create and our view is not part of any item in change detection tree (unlike creation via ViewContainerRef.createComponent). Angular opened API for such purposes and we can easily add view to root views https://github.com/angular/angular/blob/master/packages/core/src/application_ref.ts#L428 and after that our component will be updated during Application.tick https://github.com/angular/angular/blob/master/packages/core/src/application_ref.ts#L558

how to mix a decision of with an observer and synced actions? (Angular)

I have six square views in my project, I have to options to click on them:
click on one and and it get selected with some css fade color on top of it to show its selected. this already works.
select couple of them and having the same css fade color to show the squares are selected. this works to some degree but messed with something else.
so to recognize which mode im at I have radio buttom that call multipleSelectionMode.
so this is the code:
this is my square component html file.
<div *ngIf="oneChoosed" layout="row" layout-align="center center" class="one-chose">
</div>
<div *ngIf="multipleChoosed" layout="row" layout-align="center center" class="omult-chose">
</div>
in this component class I have 2 inputs I get from the main screen component:
#Input() oneChoosed: boolean;
#Input() multipleChoosed: boolean;
this is have I update this inputs from the main screen component html file:
[oneChoosed]="isOneChoosed(squares)"
[multipleChoosed]="isMultipleChoosed(squares)"
and in the main screen component class I get the click events and the id's of the squares, it looks like this:
export class mainScreenComp implements OnInit, OnDestroy {
multipleSelectionMode: Observable<boolean>;
oneChoosenIds: string[];
multipleChosenIds: string[];
_oneChoseSquareIdsSubscription: Subscription;
_multipleChoseSquaresIdsSubscription: Subscription;
ngOnInit() {
this.multipleSelectionMode = this.brainComp.curSelectionTyp.map(selecType => selecType === SelectionType.multiple);
this._oneChoseSquareIdsSubscription = this.brainComp.choosingOption.subscribe(
(chos: ChoosingOption) => {
switch (chos.option) {
case ChoosingOption.oneChose:
this.oneChoosenIds = chos.chosedIds.squares;
break;
case ChoosingOption.finishedOneSel:
this.oneChoosenIds = undefined;
break;
}
}
);
this._multipleChoseSquaresIdsSubscription = this.brainComp.choosingOption.subscribe(
(chos: Chosed) => {
this.multipleChosenIds = chos.squares;
}
);
}
isOneChoosed(square: SquaresIQ): boolean {
return (this.oneChoosenIds ? this.oneChoosenIds.includes(square.squareId) : false);
}
isMultipleChoosed(square: SquaresIQ): boolean {
return (this.multipleChosenIds ? this.multipleChosenIds.includes(square.squareId) : false);
}
so basically I add squares id's to the arrays oneChoosenIds and multipleChosenIds and check if they empty or not to decide what to return,
this works, BUT, when im in oneChoosing state where I can
only choose one, when I click on it I see both css square covers that shows that the square is selected...cause they have different design I can notice it.
so it means this *ngIf="oneChoosed" and this *ngIf="multipleChoosed" both returned true...so I thought to put extra condition with my observer multipleSelectionMode in the _multipleChoseSquaresIdsSubscription subscription, but this is async cause its an observer and the other actions are sync so its not working properly. do you have a suggestion for me to solve it?

Categories

Resources