I'm trying to adapt the following code into Angular, concerning the Text element:
function setAxisValue(name, value)
{
var axis = document.getElementById(name)
if(!axis)
return
var text = document.getElementById(name+"Text")
if(!text)
return
axis.style.width = ((100*value).toFixed(1) + "%");
text.innerHTML = ((100*value).toFixed(0) + "%");
if (text.offsetWidth > axis.offsetWidth)
text.style.visibility = "hidden";
else
text.style.visibility = "visible";
}
This applies to a good number of elements depending on various names and corresponding values.
I resolved the innerHTML part by using a {{...}}
interpolation and the width part with a [style.width]="..."
(putting the expression instead of the ellipsis of course).
But I have no idea how to do the visibility thing, the following:
<span id="...Text" [style.visibility]="getVisibility()">
works, syntax-wise, but the issue is that the function needs to access the DOM element on which it's called, to extract its offsetWidth and its parent's offsetWidth (yes, the axis element is systematically the text element's parent).
How can I do that ?
Some ideas I pursued :
The event listener syntax (keyup)="..."
has a special variable $event
to access the event that caused the call (or pass it to a function), but there's no matching $element
for square-bracket property binding (which would have given a handy [style.visibility]="getVisibility($element)"
).
I read about adding some code in a sort of onInit method on the Component class, to add a callback to all elements subject to a selector expression. The good aspect is that the DOM element is available to the callback, since it's the object I'm calling addEventListener on. The bad aspect is that I have no idea what event it should listen to : the actuation of the visibility should be every time the offsetWidth of the two elements are subject to change (including when the window gets resized, for instance).
Finally, I saw something called ViewChild / ViewChildren, supposedly giving the Component access to DOM objects. The issue is that what little I understand of it, it doesn't seem that the selector matches the thing I want to seek : the .someclass div span
CSS selector expression. Instead, it seems to only obey Angular-specific mechanics to select DOM elements.
Also, suppose I get in the Component the set of all the elements this calculation is supposed to happen to, and I attach a binding function [style.visibility]="getVisibility()"
to each of them. There's still the issue of how, when I'm inside the function, I'm supposed to guess which element (among all those in the set) I'm supposed to read the offsetWidth of (and the parent's offsetWidth) to then compute and return the visibility.
For such scenarios, you need to reuse DOM manipulations and pass in different data based on the element. Directive is the best solution, you can move all the DOM manipulation to the directive.
@Directive({
selector: '[DynamicAxis]',
standalone: true,
})
export class DynamicAxisDirective {
@Input() name!: string;
@Input() value: number = 0;
constructor(private element: ElementRef, private renderer: Renderer2) {}
ngOnChanges() {
var axis = document.getElementById(this.name);
if (!axis) return;
axis.style.width = (100 * this.value).toFixed(1) + '%';
const text = this.element?.nativeElement;
text.innerHTML = (100 * this.value).toFixed(0) + '%';
this.renderer.setStyle(
text,
'visibility',
text.offsetWidth > axis.offsetWidth ? 'hidden' : 'visible'
);
}
}
We pass in the input variables as input properties using [name]="test"
property binding and use them to execute the logic.
We use the ElementRef
which is the element reference on which the directive is placed. I will place it on the span element.
<span id="...Text" DynamicAxisDirective [name]="'test'" [value]="70">
We can use Renderer2
to set styles, classes, etc. Why to use this instead of traditional JS, because the data is santized automatically.
I would prefer you pass in axis
as a template Reference variable, than use `getElementById, since that is more the angular approach.
<div id="test" #axis>axis</div>
...
<span id="...Text" DynamicAxisDirective [name]="'test'" [value]="70" [axis]="axis">
Then you can use this passed in value and get rid of the get element by ID part also.
So that everything is done the angular way.