I need to add class to an element on an individual element scroll. I created a slackblitz example. I know how to add a class on whole body scroll. But, I need to add on particular element scroll.
In this example I need to add class on scrolling the div#paragraph.
Thanks in advance.
https://stackblitz.com/edit/angular-changeclassonelementscroll
You can create a directive that listens to its host scroll event. Something like would work:
#Directive({
selector: '[appScroll]'
})
export class ScrollDirective {
#Input() scrollClass: string;
constructor(private el: ElementRef, private renderer: Renderer2) { }
#HostListener("scroll", [])
onScroll() {
if (this.el.nativeElement.scrollTop > 20) {
this.renderer.addClass(this.el.nativeElement, this.scrollClass)
}
}
}
I forked your code example here
PS: try to avoid accessing the DOM directly using document.getElementById. Always use Angular utilities for that.
You could try adding an event to the paragraph element in your template:
<div id="paragraph" (scroll)="onDivScroll()">
and then add a function in your component that gets called
onDivScroll(){
this.document.getElementById('paragraph').classList.add('green');
}
you'll have to add your additional logic as needed, but you should be able to turn the individual element text green this way.
Related
I want to invoke a method on click of Any div with a given class. In JQuery it was a trivial one liner. Do we have something similar in Angular?
I know we can use a (click) binding to invoke a method on click. But that would require a separate binding for each instance of the div. I want a generic way to bind every div for the given class - as we used to do with JQuery selectors
In Angular you can create a custom directive, assign it to the required blocks and create a click listener via #HostListener.
For example:
HTML
<div appMyDirective>
div 1
</div>
<div appMyDirective>
div 2
</div>
Directive
#Directive({
selector: '[appMyDirective]'
})
export class MyDirectiveDirective {
constructor() { }
#HostListener('click') onClick() {
console.log('Привет')
}
}
May be it suit you
Angular has absolutely different approach. Instead of querying the DOM, we typically bind the event handlers in the markup.
<button (click)="onSave()">Save</button>
Event binding
Can you please elaborate on what you are trying to achieve,will specify the basic of using click and ngClass in angular see if it helps
In you HTML file
<div class="my_class" (click)="clickDiv()"
[ngClass]="isActive ? 'active' : 'notActive'">
Some content
</div>
In .ts file
isActive : boolean = false ;
clickDiv(){
this.isActive = !this.isActive;
}
I wanted to add an "i" element as a child element to HTMLDivElement
but styles are not applied to the newly inserted "i" element.
How can I make sure to apply the same styles to newly added "i" element?
I want to apply the the styles that I have set in the scss file for the new element.
(when I am inserting the new element in the .ts file, I can set the same styles using JS(TS), but I intend to use the scss/css styles, is there any way to use the scss/css styles for the newly added elements?)
accommodation-view-data.html
<div class="star-rating" #starRating>
<i class="bi bi-star-fill"></i> <!-- styles are applied to this element successfully -->
</div>
accommodation-view-data.component.scss
i{
font-size: 1.6rem;
padding: 0 0.25rem ;
color: #ffbe0f;
}
accommodation-view-data.component.ts
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '#angular/core';
#Component({
selector: 'app-accommodation-view-data',
templateUrl: './accommodation-view-data.component.html',
styleUrls: ['./accommodation-view-data.component.scss']
})
export class AccommodationViewDataComponent implements OnInit, AfterViewInit {
#ViewChild('starRating')
starRatingElem!: ElementRef;
constructor() { }
ngOnInit(): void {
}
ngAfterViewInit(): void {
/* When I append the the new element to the DOM,
* the styles that I have set in the scss file, are not applied for the new element */
const starIconElement = document.createElement('i');
starIconElement.classList.add('bi');
starIconElement.classList.add('bi-star-fill');
(this.starRatingElem.nativeElement as HTMLDivElement).append(starIconElement);
}
}
This is a normal behavior, When you write style that is integrated in the styleUrls, every single class is reworked to be specific to the component, it's the style scope (note the i[ng-content-lep-c52]in the screenshot below).
So adding an element in the dom like you do is not recommended, instead prefer toggling it thanks to a ngIf or here for stars with a ngFor or else you'll have hard times trying to make it dynamic and bind values with the component model.
Like you can see in this screenshot, the first element is properly matched with the scoped style while the new element isn't.
NOT RECOMMENDED AND DEPRECATED: To bypass this you can use the ::ng-deep pseudo class to make a style global, and therefor getting overrided by scoped style as you can see in this screenshot comming from this stackblitz.
You can find more information about styles in angular here
I would like to create a parent directive which shows or hides children based on the values of the children. To do this, i've taken the approach of a parent structural directive, and a child directive with values. For simplicity without the values:
<div *appParent>
<div appChild>1</div>
<div appChild>2</div>
<div appChild>3</div>
<div appChild>4</div>
</div>
To access the children, I use the following line in the parent directive:
#ContentChildren(AppChildDirective, { read: AppChildDirective, descendents: true }) children: QueryList<AppChildDirective>;
This query list is always empty. However, when I change it to a non-structural, it works fine. Stackblitz demo here
I assume this is due to the fact the structural directive creates a parent ng-template, which #ContentChildren then looks inside to find the original component, meaning that the query actually goes nowhere.
What approach can I take to access the children of the original component and not the template? Or do I need to take another approach to handle my requirements?
ContentChildren seem to not work on structural directives. However, this can be achived by injecting the parent directive in the child and then registering the child in the parent by calling a function.
#Directive({
selector: '[appChild]'
})
export class ChildDirective {
constructor(parent: ParentDirective) {
parent.registerChild(this);
}
}
#Directive({
selector: '[appParent]'
})
export class ParentDirective {
registerChild(child: ChildDirective) { /*...*/ }
}
Side notes
If you also want to be able to use the child directive without the parent directive, change the child's constructor like this to make the parent optional:
constructor(#Optional() parent: ParentDirective) {
parent?.registerChild(this);
}
You can also use this approach recursively by injecting a directive in its own constructor. If you do so, also add #SkipSelf() in the constructor to really get the parent:
#Directive({
selector: '[appRecursive]'
})
export class RecursiveDirective {
constructor(#Optional() #SkipSelf() parent: RecursiveDirective) {
parent?.registerChild(this);
}
registerChild(child: RecursiveDirective) { /*...*/ }
}
There are 4 steps there:
1.) Click on category
2.) Show filtered products
3.) Select filtered products
4.) Display selected products in most right part of screen /3rd child component/
What I would like to achieve is next:
When I click on product (3rd step), product is added to 'right' component, and there I would like to change a font size of quantity so it might look like animation, like make font bigger for example 28 and make it small again for example 18.
Products are added to the 3rd component by using service which is shared between child components. This is how it looks :
Thanks guys
Cheers
First of all, add a new rule to the order-quantity-number class:
transition: font-size 1s;
then define another selector in css:
.order-quantity-number.selected {
font-size: 48px;
}
then basically you just need to add this 'selected' class to the span element and the font-size will be animated. After 1s (anim is completed), you need to remove the class from the element and the text will shrink. I hope it answers the question :)
EDIT: Implementation details
Template:
Add reference to the span element so that it is accessible from code
<span class="order-quantity-number" #ref>{{receiptItem.quantity}}</span>
ts:
Add the following line to the class to use 'ref' from the template.
#ViewChild('ref') elRef: ElementRef;
Add setTimeout() call to the click handler that triggers the animation to remove selected class after 1s:
onClick() {
...
// 1. add 'selected' class to the span element
this.elRef.nativeElement.classList.add('selected');
// 2. remove it after 1s
setTimeout(() => {
this.elRef.nativeElement.classList.remove('selected');
}, 1000);
}
You can write simple #Directive that implements AfterViewInit interface in which you will add a class with bigger font-size and then watch for event transitionend and remove class.
something like this
#Directive({
selector: `[fontAnimation]`
})
export class FontAnimationDirective implements AfterViewInit {
constructor(
private hostElement: ElementRef
) { }
ngAfterViewInit() {
const el = this.hostElement.nativeElement as HTMLElement;
el.classList.add('animate-font-size');
el.addEventListener('animationend', (ev: TransitionEvent) => {
if (ev.propertyName == 'font-size') {
el.classList.remove('animation-font-size');
}
})
}
}
Warning: transitionend will trigger event for every property that has transition, so we need to check if propertyName is equal to font-size.
All you need to do is create proper css class. Don't forget to import it to proper NgModule
I currently need to be more specific in my functionality for a class toggle that needs to only occur in the children within the element being clicked. I have a class of ".node" and when this is clicked it should toggle a class on it's child ".node-dropdown". I have multiple of these nodes with (click)="showNodeDropdown" and currently all the node dropdowns are being hidden or shown at the same time.
HTML
<div class="node" *ngIf="authService.isInternalUser()" (click)="showNodeDropdown()">
<span class=" fa fa-clock-o node-icon"></span>
<p>Menu Title</p>
Keep in mind there are multiple of these elements using (click)="showNodeDropdown()" throughout my page and this is just one example.
JS
showNodeDropdown() {
$('.node-dropdown').toggleClass('hidden');
}
I tried changing my JS to be something like $(this).children('.node-dropdown).toggleClass('hidden');
however (this) is currently defined only as "object Object"
Is there a way I can get the element that was clicked and set it to (this) so that I can toggle only on it's children? Thanks in advance for any help.
Here are two Angular ways to get the clicked element:
Pass the event target to the handler:
<div (click)="showNodeDropdown($event.target)" ... >
Define a template reference variable, and pass it to the handler:
<div #myDiv (click)="showNodeDropdown(myDiv)" ... >
In both cases, the event handler can be written as:
showNodeDropdown(element: HTMLElement) {
let nodes = element.querySelectorAll(".node-dropdown");
for (let i = 0; i < nodes.length; i++) {
nodes[i].classList.toggle("hidden");
}
}
or, using jQuery:
showNodeDropdown(element: HTMLElement) {
$(element).children(".node-dropdown").toggleClass("hidden");
}
It is recommended not to use jQuery inside of Angular Applications. If you need to select DOM Elements use #ViewChild instead:
https://angular.io/api/core/ViewChild
Here an example:
Controller:
#ViewChildren(TestComponent) test: QueryList<TestComponent>
Template:
<ng-container *ngFor="let player_ of players; let i = index">
<test></test>
</ng-container>
to print the <test> Elements just do the following inside the TypeScript Controller:
console.log(this.test)
So, you can access them like any variabled declared in the Controller.
You can pass $event object here:
(click)="showNodeDropdown($event)"
and then use
showNodeDropdown(event) {
$(event.target).toggleClass('hidden');
}
My recomendation is not use jquery, instead create a Directive
import { Directive, HostListener, HostBinding } from '#angular/core';
#Directive({
selector: '[appNodeDropdown]'
})
export class NodeDropdownDirective {
#HostBinding('class.hidden') isOpen = false;
#HostListener('click') toggleOpen(evenData: Event){
this.isOpen = !this.isOpen;
}
}
and use it like this
<div appNodeDropdown *ngIf="authService.isInternalUser()">
of course remember to register the directive in the "Declarations" array of module of the component.
Although I know it is best not to use JQuery in my angular application thanks to #messerbill , I still needed to come up with an answer to handle this issue using JQuery. I was able to set an ID to each div that utilized this (click) and pass the ID through the click like so:
<div class="node" id="booking-node" (click)="showNodeDropdown('#booking-node')">
showNodeDropdown (id: string) {
$('.node-dropdown').addClass('hidden');
$(id).children('.node-dropdown').toggleClass('hidden');
}
This allowed me to gain the results that I was looking for. Keep in mind this is not the best answer, as not using JQuery at all would be the ideal solution, however I did not have this option so this is the JQuery work around.