Get dimensions of an image after render finishes, in Angular - javascript

I am trying to get the rendered size of an image in a component. Using the (load) event, I am getting the size of the image as it is displayed at that moment (pic1) and the "final" size after the page fully renders. I guess I can use ngAfterViewChecked but that would mean I am constantly calculating that size when the only meaningful instance for that is when the window opens or resizes

An alternate approach that you can use is, subscribe to changes in Window: resize event using the HostListener in order to calculate image dimensions, as shown in the following code snippet.
import { HostListener } from '#angular/core';
#HostListener('window:resize', ['$event'])
// Invoke function to compute the image dimensions here
}
Note: You can also invoke the function to compute the dimensions of the said image inside AfterViewInit lifecycle hook rather than on load event.
Another approach is to calculate image dimensions by listening to changes in the Window: resize event using the fromEvent, as shown in the code snippet below.
import { fromEvent } from 'rxjs';
changeSubscription$: Subscription
ngOnInit() {
this.windowResizeEventChanges$ = fromEvent(window, 'resize').subscribe(event => {
// Invoke function to compute the image dimensions here
});
}
ngOnDestroy() {
this.windowResizeEventChangesn$.unsubscribe() // Unsubscribe to stop listening the changes in Window: resize
}
Read more about fromEvent here.

I ended up using a Mutation Observer, watching for attribute changes, attached to the item that contains the image. In my case there is a need for some management because it doesn't just run once, but it solves the issue
ngAfterViewInit() {
this.refElement = this.appsContainer.nativeElement.querySelector('item') as HTMLElement;
const config = { attributes: true };
this.mutationObserver = new MutationObserver((mutation) => {
const target = mutation[0].target as HTMLElement;
//some code here to execute based on some conditions and to remove the observer
}
});
this.mutationObserver.observe(this.refElement, config);}

Related

VueJS: Determine the source that triggers the watch callback

I watch a property of an object device.flow. When the property changes, the callback gets triggered. That all works and is fine.
I only want though that the callback gets triggered if the user actually manipulates the form component that uses that watched property as model.
There's also a periodic API call to get the current data of the device as it might change due to other circumstances than just user input. The slider should then adjust accordingly as well but the web socket event should not be emitted because that is unnecessary.
To illustrate what I want to achieve, I attached a simple component below and added a comment, where I want to distinguish the source of the property change.
Is this even possible?
<script setup lang="ts">
import type DeviceAirValve from "../../../model/DeviceAirValve";
import {reactive, ref, watch} from "vue";
import {useSocketIO} from "../../../plugins/vueSocketIOClient.js";
import type {Socket} from "socket.io-client";
interface Props {
device: DeviceAirValve
}
const props = defineProps<Props>();
const io = useSocketIO() as Socket;
let device = reactive<DeviceAirValve>(props.device);
watch(
() => device.flow,
flow => {
// Determine whether the callback gets called because
// the value was changed by the user through the v-slider component,
// or if the device.flow property has been updated by something else
// (for example a periodic poll from an API) -> isFormInput variable
if (isFormInput) {
flowChangeHandler(flow)
}
}
);
const flowChangeHandler = (newFlow: number): void => {
io.emit('deviceUpdate', {
deviceId: props.device.deviceId,
data: {flow: newFlow}
});
};
</script>
<template>
<v-slider v-model="device.flow"></v-slider>
</template>
One solution is to replace the watcher with an event handler for v-slider's update:modelValue event (the event that drives v-model, which is only triggered by user input):
<v-slider #update:modelValue="flowChangeHandler" />
demo

Angular Template Interpolation Slow to Respond

My Angular template needs to display a rapidly changing value, driven by mousemove events, which I am retrieving from my NgRx Store. The Store appears to be keeping up with the data changes but the resulting value displayed in the template lags behind and appears to only refresh when the mouse stops moving.
A component with approximately 300 DOM elements detects mousemove events and handles them outside of ngZone as follows:
ngAfterViewInit() {
const eventElement = this.eventDiv.nativeElement;
this.move$ = fromEvent(eventElement, 'mousemove');
this.leave$ = fromEvent(eventElement, 'mouseleave');
/*
* We are going to detect mouse move events outside of
* Angular's Zone to prevent Change Detection every time
* a mouse move event is fired.
*/
this.ngZone.runOutsideAngular(() => {
// Check we have a move$ and leave$ objects.
if (this.move$ && this.leave$) {
// Configure moveSubscription.
this.moveSubscription = this.move$.pipe(
takeUntil(this.leave$),
repeat()).subscribe((e: MouseEvent) => {
e.stopPropagation();
this.mouseMove.emit(e);
});
};
});
A parent component handles the resulting mouseMove event and still outside ngZone performs some Calculations to ascertain which element the mouse is over. Once the result has been calculated a function is called, passing in the calculated result, and within this function I dispatch an NgRx Action within ngZone using this.ngZone.run(() => { dispatch Action here }.
I can see that the Store reacts quickly to the changing data. I then have a separate component responsible for displaying the result. An Observable listens to the Selector's changing values and displays the result using interpolation.
Curiously I added an RxJs tap into the Observable declaration as follows:
public mouseoverLocationName$: Observable<string | null>;
constructor(
public store: Store<fromPilecapReducers.PilecapState>
) {
this.mouseoverLocationName$ = this.store.pipe(
select(fromPilecapSelectors.PilecapMapSelectors.selectMouseoverLocationName),
tap(locationName => {
console.log(`mouseoverLocation$: ${locationName}`);
})
);
}
The console logs out the locationName value nice and quickly. However the html element displaying the string is very slow and, as I said earlier, only appears to update when the mouse stops moving. The template code is as follows:
<h2>{{mouseoverLocationName$ | async}}</h2>
I've got to the point now where I can't see the wood for the trees! any suggestions or guidance very welcome.

How to refresh page when web page is rotated?

I'm using Next JSs, I'm having trouble finding a solution how to refresh the page when it's rotated
const app () => {
useEffect(()=>{
window.addEventListener("orientationchange", function() {
window.location.reload()
});})
return(<>some code</>);
}
I would strongly recommend you don't do that.
But there are a couple of problems with your implementation of it:
To hook up an event listener on component mount, you need an empty dependency array on your useEffect. Otherwise, your effect callback is called on every render which, in your case, will add multiple handlers.
You aren't removing the handler when the component is unmounted. You should return a cleanup callback from the useEffect callback.
The orientationchange event is deprecated and has poor browser support. Instead, listen to change on Screen.orientation which has better browser support. Alternatively, just listen for resize, since a change in screen orientation that you care about will trigger a resize event, and mobile devices are getting more and more sophisticated with how the screen real estate is used (split screen, etc.). It's possible not all of those would cause a change in orientation, but you seem to care about a change in size.
Separately, app isn't a valid component name. Component functions must start with a capital letter.
Fixing all of those:
const App () => {
useEffect(()=>{
const handler = () => {
// Handle the change in orientation here (I recommend **not** reloading)
};
Screen.orientation.addEventListener("change", handler); // Or window resize
return () => {
// On unmount, remove the handler
Screen.orientation.removeEventListener("change", handler); // Or window resize
};
}, []);
// ^^−−−− tells `useEffect` to only call your callback once, when the component
// is first mounted.
return (<>some code</>);
}

How to prevent change detection on scroll? ( Angular )

I have a large component, and I would like to avoid unnecessary change detections to increase performance.
I added changeDetection: ChangeDetectionStrategy.OnPush to the #Component.
I would like to update the style of an element if it is scrolled. Unfortunately Angular runs the change detection, every time I scroll that element.
I tried to add an event listener to the native element to avoid this, but the change detection is still running again when I scroll:
#ViewChild('my_element') my_element_ref: ElementRef;
ngOnInit() {
this.my_element_ref.nativeElement.addEventListener('scroll', this.updateStyle);
}
ngAfterViewChecked() {
console.log('check');
}
The ngAfterViewChecked is called even if this.updateStyle is an empty function.
But if I comment out this.my_element_ref.nativeElement.addEventListener('scroll', this.onScroll), then ngAfterViewChecked is not called anymore.
How is it possible to call a function when an element is scrolled, but avoid Angular's change detection?
I would suggest you use ngZone.runOutsideAngular.
constructor (private zone : NgZone) {}
ngAfterViewInit () : any {
this.zone.runOutsideAngular(() => {
window.addEventListener('scroll', (e)=> {
console.log( 'scroll event fired' );
});
});
}
You might want to look at ChangeDetectorRef API.
Specifically you would detach the change detection for the component using detach() (maybe in the constructor like below) and then mark component for change in your scroll function via markForCheck().
constructor(public cd: ChangeDetectorRef) {
this.cd.detach();
}
this.cd.markForCheck();
Below links for your reference.
https://blog.angularindepth.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f
https://angular.io/api/core/ChangeDetectorRef
just inject NgZone in your constructor and then subscribe to an event like this:
this.ngZone.runOutsideAngular(() => {
....addEventListener('...);
});
keep in mind that change detection will not be called in this case, so you won't be able to change styles with template binding. It should be changed by native javascript or using Renderer2

Angular: RequestAnimationFrame and ChangeDetectorRef for updating view

In Angular, after a view has initialized, one part of it might be recalculated dynamically; in particular:
When someStream$ updates
I read the value of an already rendered element in the DOM
And set the value of another element (its height) according to the read value
an attribute listener in the template sets the height accordingly to the viewHeightvalue
So the code looks like this:
this._subs.push(this.someStream$.subscribe(() => {
this.zone.runOutsideAngular(() => {
requestAnimationFrame(() => {
const spaceTop: number = this.hostEl.nativeElement.getBoundingClientRect().top;
this.viewHeight = window.innerHeight - spaceTop;
this.cdr.detectChanges();
});
});
}));
<someElement [style.height.px]="viewHeight"></someElement>
I need ChangeDetectorRef.detectChanges(); to apply the changes instantly. My question is, why do I also need requestAnimationFrame? Why does the code not work without it? From my understanding requestAnimatoinFrame triggers re-rendering (in a browser-optimized way), but NgZone, activated by ChangeDetectorRef, should do that already.

Categories

Resources