I have an issue with [ngClass]. It doesn't add or remove the class if the Circles in the HTML aren't moving. If the circles are moving it works fine.. Does somebody know why? I dont find anything in the internet.
The isSidebarMenuOpen value is coming from a other component. I got every time the right value. This is why I think the issue is with adding the css class to the div.
If the sidebar opens, I just want to move the map to the right side with a css class
<div [ngClass]="{'open': isSidebarMenuOpen}">
....svg code (circles and other stuff)
</div>
Service.ts
isSidebarMenuOpenSubject = new Subject<any>();
isSidebarMenuOpen = this.isSidebarMenuOpenSubject.asObservable();
getIsSidebarMenuOpen(isSidebarMenuOpen: boolean) {
this.isSidebarMenuOpenSubject.next(isSidebarMenuOpen)
}
Sidebar-Component
toggleSidebarMenu() {
this.isSidebarMenuOpen = !this.isSidebarMenuOpen;
this.resizeService.getIsSidebarMenuOpen(this.isSidebarMenuOpen);
}
map-component
isSidebarMenuOpen = false;
ngOnInit(): void {
this.resizeService.isSidebarMenuOpen.subscribe(value =>{
this.isSidebarMenuOpen = value
});
Related
I wanted to make a toggle dark theme for my project which will work in every component
the resources i found was on angular material. but i dont wanna use that,
is there any way to do it?
please help!
In order to do that, on your toggle button add a click functionality and change properties and accordingly change colors too. Something like this:
// In your HTML
<div [class.dark-class]="isDark" [class.white-class]="!isDark">
<button (click)="isDark = !isDark">Toggle background</button>
</div>
// In your SCSS file, have 2 class, one called dark-class and the other white-class
.dark-class { background: black }
.white-class { backgrond: white }
// In your component.ts file, add a boolean value 'isDark' and set it to false initially
private isDark: boolean = false;
So here what happened is, initially since the boolean value is dark, the div will have a 'white-class' background since i added a '!isDark' condition on the [class.white-class] and when you click the button, i am changing 'isDark' to '!isDark' which means, the 'isDark' will now become true and then the background changes to a dark color.
This is one approach where you can follow.
For your other question: How to do this globally? you can do something like this:
// If you have a service which is being used across all the components, you can use it otherwise create a new one like this:
1) Create a file and name it a commonService
// Inside common service:
#Injectable({ provideIn: 'root' })
export class CommonService {
globalIsDark: boolean = false;
setGlobalDark(value: boolean) {
this.globalIsDark = value;
}
getGlobalDark(): boolean {
return this.globalIsDark;
}
}
// Now in your styles.scss file, add the dark and white classes
.dark-class { background: black }
.white-class { backgrond: white }
// In every component, wherever you want to use, do this:
constructor(private commonService: CommonService) {}
isComponentBGDark: boolean;
ngOnInit() {
// If you want to use what's there, do this
this.isComponentBGDark = this.commonService.getGlobalDark();
// If you want to set the theme to dark, do this:
this.commonService.setGlobalDark(true);
}
// In your component HTML
<div [class.dark-class]="isComponentBGDark"
[class.white-class]="!isComponentBGDark"
>
</div>
I've got a line of text right now, and when that line of text is being overflown something gets set to true which let's me load in a tooltip! Code below:
Template
<div>
<p #tooltip [tooltip]="/* ShowToolTipSomeHow? ? name : null */" delay="300">{{name}}</p>
</div>
This is where it should check if it should show the tooltip or not. As you can see it should somehow detect if the tooltip should be shown or not, I have no idea how and that's my question right now.
Component
#ViewChildren('tooltip') private tooltips!: QueryList<ElementRef>;
ngAfterViewInit(): void {
this.tooltips.changes.subscribe((tts: QueryList<ElementRef>) => {
tts.forEach((tooltip, index) => {
this.checkTooltipTruncated(tooltip);
});
});
}
private checkTooltipTruncated(tooltip: ElementRef) {
// Checks if the text has overflown
const truncated = this.isTextTruncated(tooltip);
if (truncated) {
// Change the ShowToolTipSomehow? value?
}
}
In the component it somehow changes some value that the tooltip can detect so that it can update itself to hide the tooltip. The additional problem is that it's not 1 tooltip to change, but infinite tooltips (so basically 1 or more).
My question is, how would I do this because I'm pretty stuck.
Create an array that holds boolean values and using the tooltip elements index set the value in the array.
ts:
#ViewChildren('tooltip') private tooltips!: QueryList<ElementRef>;
tooltipsVisible: boolean[];
ngAfterViewInit(): void {
if (!this.tooltipsVisible) {
this.tooltipsVisible = new Array(this.tooltips.length).fill(false);
}
this.tooltips.changes.subscribe((tts: QueryList<ElementRef>) => {
tts.forEach((tooltip, index) => {
this.checkTooltipTruncated(tooltip, index);
});
});
}
private checkTooltipTruncated(tooltip: ElementRef, index) {
// Checks if the text has overflown
const truncated = this.isTextTruncated(tooltip);
if (truncated) {
this.tooltipsVisible[index] = true;
// Change the ShowToolTipSomehow? value?
}
}
html:
<div>
<p #tooltip [tooltip]="tooltipsVisible[i] ? ...." delay="300">{{name}}</p>
</div>
You should create your p elements using *ngFor you can have the index available in your template...
I have a side drawer where I'm showing the current cart products selected by the user. Initially, I have a <p> tag saying the cart is empty. However, I want to remove it if the cart has items inside. I'm using an OOP approach to design this page. See below the class I'm working with.
I tried to use an if statement to condition the <p> tag but this seems the wrong approach. Anyone has a better way to do this. See screenshot of the cart in the UI and code below:
class SideCartDrawer {
cartProducts = [];
constructor() {
this.productInCartEl = document.getElementById('item-cart-template');
}
addToCart(product) {
const updatedProducts = [...this.cartProducts];
updatedProducts.push(product);
this.cartProducts = updatedProducts;
this.renderCart();
}
renderCart() {
const cartListHook = document.getElementById('cart-items-list');
let cartEl = null;
if (this.cartProducts.length === 0) {
cartEl = '<h2>You Cart is Empty</h2>';
} else {
const productInCartElTemplate = document.importNode(
this.productInCartEl.content,
true
);
cartEl = productInCartElTemplate.querySelector('.cart-item');
for (let productInCart of this.cartProducts) {
cartEl.querySelector('h3').textContent = productInCart.productName;
cartEl.querySelector('p').textContent = `£ ${productInCart.price}`;
cartEl.querySelector('span').textContent = 1;
}
}
cartListHook.append(cartEl);
}
}
By the way, the <p> should reappear if the cart is back to empty :) !
With how your code is setup, you would want to reset the list on each render. You would do this by totally clearing out #cart-items-list. Here is a deletion method from this question
while (cartListHook.firstChild) {
cartListHook.removeChild(cartListHook.lastChild);
}
But you could use any method to delete the children of an HTML Node. To reiterate, you would put this right after getting the element by its id.
P.S. You probably want to put more code into your for loop, because it seems like it will only create cart-item element even if there are multiple items in this.cartProducts.
I am using ng2-dragula for drag and drop feature. I am seeing issue when I drag and drop first element(or any element) at the end and then try to add new item to the array using addNewItem button, new item is not getting added to the end. If i don't drop element to the end, new item is getting added at the end in UI.
I want new items to be displayed at the bottom in any scenario. Any help is appreciated.
This issue is not reproducible with Angular 7. I see this happening with Angular 9
JS
export class SampleComponent {
items = ['Candlestick','Dagger','Revolver','Rope','Pipe','Wrench'];
constructor(private dragulaService: DragulaService) {
dragulaService.createGroup("bag-items", {
removeOnSpill: false
});
}
public addNewItem() {
this.items.push('New Item');
}
}
HTML
<div class="container" [dragula]='"bag-items"' [(dragulaModel)]='items'>
<div *ngFor="let item of items">{{ item }}</div>
</div>
<button id="addNewItem" (click)="addNewItem()">Add New Item
I edited the stackblitz from the comment to help visualize the issue. This seems to be triggered when a unit is dragged to the bottom of the list. Updated stackblitz : https://stackblitz.com/edit/ng2-dragula-base-ykm8fz?file=src/app/app.component.html
ItemsAddedOutOfOrder
You can try to restore old item position on drop.
constructor(private dragulaService: DragulaService) {
this.subscription = this.dragulaService.drop().subscribe(({ name }) => {
this.dragulaService.find(name).drake.cancel(true);
});
}
Forked Stackblitz
Explanation
There is some difference between how Ivy and ViewEngine insert ViewRef at specific index. They relay on different beforeNode
Ivy always returns ViewContainer host(Comment node)ref if we add item to the end:
export function getBeforeNodeForView(viewIndexInContainer: number, lContainer: LContainer): RNode|
null {
const nextViewIndex = CONTAINER_HEADER_OFFSET + viewIndexInContainer + 1;
if (nextViewIndex < lContainer.length) {
const lView = lContainer[nextViewIndex] as LView;
const firstTNodeOfView = lView[TVIEW].firstChild;
if (firstTNodeOfView !== null) {
return getFirstNativeNode(lView, firstTNodeOfView);
}
}
return lContainer[NATIVE]; <============================= this one
}
ViewEngine returns last rendered node(last <li/> element)ref
function renderAttachEmbeddedView(
elementData: ElementData, prevView: ViewData|null, view: ViewData) {
const prevRenderNode =
prevView ? renderNode(prevView, prevView.def.lastRenderRootNode!) : elementData.renderElement;
...
}
The solution might be reverting the dragged element back to original container so that we can let built-in ngForOf Angular directive to do its smart diffing.
Btw, the same technique is used in Angular material DragDropModule. It remembers position of dragging element and after we drop item it inserts it at its old position in the DOM which is IMPORTANT.
I've got an html element with overflow: auto, but I want to give it a border only when it's scrollable.
How do I evaluate the element's size from within ngClass without getting any kind of null errors?
Note: The element's enclosing div doesn't get rendered until after getting a response from an observable.
Attempt 1:
The html element is set up like this:
<div ngIf="!loading">
<div id="{{someID}}" [ngClass]="{'border-class': isScrollable}"> ... </div>
</div>
In my ngOnInit, I call a function to see if the given element can be scrolled.
ngOnInit() {
// this.loading gets set to false after an observable is returned
/* ... */
// scroll check
let e = document.getElementById(`${this.someID}`);
if (element !== null) {
this.isScrollable = e.scrollHeight > e.clientHeight;
}
}
If I don't check for null, I get errors. If I do check for null, then even if I have scrollable content, when the page is loaded, the border doesn't show up.
I thought the issue might be with this.loading, so I added the scroll check within the observable response, but after loading was set to false. Still no border.
Attempt 2:
<div #textDiv [ngClass]="{'border-class': isScrollable}"> ... </div>
#ViewChild('textDiv') element: ElementRef;
/* ... */
ngAfterViewInit() {
this.isScrollable = this.element.scrollHeight > this.element.clientHeight;
}
But the border still doesn't show up on scrollable content when the page is loaded.
Attempt 3:
The only thing that has worked, is this hot mess:
setTimeout( () => {
this.isScrollable = this.element.scrollHeight > this.element.clientHeight;
});
Is there a way I can get this to work without calling setTimeout?
The problem is here:
<div ngIf="!loading">
<div id="{{someID}}" [ngClass]="{'border-class': isScrollable}"> ...
</div>
The isScrollable is not updating the value after it changes.
You can fix this, using a get and returning the value when ngAfterViewInit was already executed:
export class CustomComponent implements AfterViewInit {
private afterViewInitExecuted = false;
#ViewChild('textDiv') element: ElementRef;
public get isScrollable() {
if(this.afterViewInitExecuted) {
return this.element.scrollHeight > this.element.clientHeight;
}
return false;
}
ngAfterViewInit() {
this.afterViewInitExecuted = true;
}
}
Then in your html:
<div ngIf="!loading">
<div id="{{someID}}" [ngClass]="{'border-class': isScrollable() }"> ...
</div>
With that, it should work.