setInterval won't change image on Safari, but on Chrome - html

I have a mock array that distributes data. One component uses this data to display a list of cases. Each case has allocated images. When one case is being hovered, only then these images are being displayed, but only one at a time - every interval of 300ms the images changes.
My code works, but I have trouble with Safari - the image won't change. Somehow Safari can't handle it. Increasing the interval from 300m to 3000ms made it work, but that's not the way I want it to work.
Looking at the code in safari proves that the image actually switches every 300ms, since the img source changes - but the change won't be displayed.
BTW I tried it with Chrome and it worked fine.
export class CaseListComponent implements OnInit {
counter = 1;
cases;
interval;
image: string;
array = [];
mouseEnter(url: string, url2: string, url3: string, name: string) {
clearInterval(this.interval);
this.array = [url, url2, url3];
this.image = this.array[0];
this.changeImage();
}
changeImage() {
this.interval = setInterval(() => {
this.image = this.array[this.counter];
this.counter = this.counter === 2 ? 0 : this.counter + 1;
}, 300);
}
mouseLeave() {
clearInterval(this.interval);
this.image = null;
this.highlightedItem = null;
}
constructor(private casesService: CasesService) {}
ngOnInit() {
this.cases = this.casesService.data;
}
}
<div class="container-fluid d-flex justify-content-center">
<div class="row">
<div class="col-12 text-center" *ngFor="let case of cases" [class.z-index]="highlightedItem === case.name">
<p class="d-inline-block" routerLink="/cases/{{ case.id }}" (mouseenter)="mouseEnter(case.image, case.image2, case.image3, case.name)" (mouseleave)="mouseLeave()"
[style.color]="highlightedItem !== case.name && highlightedItem !== null ? '#f1f1f1' : '#33393D'">{{ case.name }}</p>
</div>
</div>
<img *ngIf="!!image" [src]="image" alt="image" class="position-fixed align-self-center">
</div>

Following my comment, you should use observables.
They are heavily used in Angular and you can rely on it for your change detection.
Here is a simple example of the observables in action, without Angular. Simply adapt this to your code (and ask if you have any issue) to make Angular handle the picture changes.
Sandbox
import { fromEvent, Subscription, timer } from "rxjs";
import {} from "rxjs/operators";
const container = document.querySelector(".container");
let subscription: Subscription;
let count = 0;
const cycler = timer(0, 500);
fromEvent(container, "mouseenter").subscribe(() => {
subscription = cycler.subscribe(() => {
count++;
container.textContent = count;
});
});
fromEvent(container, "mouseleave").subscribe(() => {
subscription && subscription.unsubscribe();
container.textContent = "";
count = 0;
});

Related

HTML doesn't update properly on child element after sorting an array of items on parent component when using updated()

I am sorting an array of so called 'activities' in my customElement using LitElement:
#property({ type: Array }) private activityListLocal: Array<Activity> = [];
in the parent customElement called "find-activity".
Each activity is being rendered here.
${repeat(
this.activityListLocal,
activity =>
html` <div class="activity-container">
<div class="activity">
<activity-info .activity=${activity}></activity-info>
</div>
<div class="activity" class="rating">
<activity-rating
.activity=${activity}
#appactivityremoveclick=${() => this.deleteActivity(activity)}
></activity-rating>
</div>
</div>`
)}
This is how it looks visually:
2 activities marked for some impression
On clicking the button "Highest Rating", I am sorting the list of activities:
sortActivityListLocal() {
this.activityListLocal = [...this.activityList];
this.activityListLocal = this.activityListLocal.sort((a, b) => (a.avgRating < b.avgRating ? 1 : -1));
}
if (category === 'all') {
this.activityListLocal = this.activityList;
} else if (category === 'Highest Rating') {
this.sortActivityListLocal();
if (this.activityListLocal.length === 0) {
this.nothingHere.style.display = 'block';
}
}
//....
}
Note: this.activityList is a local copy of the server response.
In the image, you see the two sliders, which should be updated to move with the activity if the position on the page changes. The issue: The "my rating" slider does not properly "move" with the activity, if it has been changed/dragged after the page has been loaded for the first time.
Before:
Activities are loaded in properly, cinema has a higher rating than Tennis
After:
Activities are sorted properly, all sliders are correctly "moved" if "myRating" has not been changed/dragged
But if the slider was dragged after inital load in, and then selecting the "highest rating" category and therefore sorting the array, it stays in place:
Before:
After loading
Dragging the slider (not even requesting an update with a click on the refresh icon, issue happening in both cases)
Modification leading to the issue
After:
Issue visible
The interesting thing, the slider has the correct! value in the html inspector, but the display is not showing it. Why is this happening?
Code of the component holding the sliders:
import { LitElement, html } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
import { httpClient } from '../../../http-client';
import { PageMixin } from '../../page.mixin';
import { Activity, Rating } from '../find-activity';
import componentStyle from './activity-rating.css';
#customElement('activity-rating')
// eslint-disable-next-line #typescript-eslint/no-unused-vars
class ActivityRatingComponent extends PageMixin(LitElement) {
static styles = componentStyle;
#property({ reflect: true }) activity = {} as Activity;
#property() rating = {} as Rating;
#query('#deleteButton') private deleteButton!: HTMLImageElement;
private currentSliderValue = -1;
async updated() {
console.log(
`Personal rating for ${this.activity.title} is ${this.activity.personalRating}, avgRating ${this.activity.avgRating}, currentSliderValue ${this.currentSliderValue}`
);
this.currentSliderValue = this.activity.personalRating ? this.activity.personalRating : 0;
console.log(`Current slider value after: ${this.currentSliderValue}`);
if (this.activity.deletepermission === false) this.deleteButton.style.display = 'none';
else this.deleteButton.style.display = 'inline';
}
render() {
return html`
${this.renderNotification()}
<div class="outer-rating">
<p>${this.activity.motivationtitle}</p>
<div class="slidecontainer">
<label for="overallRating">Overall Rating</label>
<input
type="range"
min="0"
max="100"
value=${this.activity.avgRating ? this.activity.avgRating : 0}
class="slider"
id="overallRating"
disabled
/>
</div>
<div class="slidecontainer">
<label for="myRating">My Rating</label>
<input
type="range"
min="0"
max="100"
value=${this.activity.personalRating ? this.activity.personalRating : '0'}
class="slider"
id="myRating"
#change="${(e: Event) => this.readcurrentSliderValue(e)}"
/>
<img id="personalSlider" src="/refresh.png" alt="update" #click=${this.savecurrentSliderValueToDb} />
<img
class="remove-task"
src="/deleteicon.png"
alt="update"
id="deleteButton"
#click="${this.confirmDelete}"
/>
</div>
</div>
`;
}
confirmDelete(e: Event) {
const target = e.target as HTMLInputElement;
if (target) {
const result = confirm('Want to delete?');
if (result) {
this.emit('appactivityremoveclick');
}
}
}
readcurrentSliderValue(e: Event) {
const target = e.target as HTMLInputElement;
if (e) {
this.currentSliderValue = Number(target?.value);
console.log('Read new slider value ' + Number(target?.value));
}
}
async savecurrentSliderValueToDb() {
const partialRating: Partial<Rating> = {
activityid: this.activity.id,
rating: Number(this.currentSliderValue) //userID is not included here as it is being provided by the auth Middleware on patch request.
};
await httpClient.patch(`rating/${this.activity.id}${location.search}`, partialRating);
const responseRatingAll = await httpClient.get(`rating/findAverageRating/${this.activity.id}` + location.search);
try {
this.activity.avgRating = (await responseRatingAll.json()).results;
this.activity.personalRating = partialRating.rating ? partialRating.rating : 0;
} catch (error) {
this.showNotification((error as Error).message, 'error');
}
this.requestUpdate();
}
emit(eventType: string, eventData = {}) {
const event = new CustomEvent(eventType, {
detail: eventData,
bubbles: true,
composed: true
});
this.dispatchEvent(event);
}
}
Visual confirmation that slider has the correct value, but doesn't show it.
Thank you :)
Edit: In addition to the answer below - specifically for the case where "you want to force a value to be set on an element". Lit has an optimization where "if a value hasn't changed, don't do anything". Rendering the same value to an expression will not cause the expression to update. To make sure Lit updates the expression if the underlying DOM value has changed use the live directive.
The native browser input elements default behavior is:
When the value attribute is changed, update the input elements value property.
After a manual user interaction (such as typing into the input element if it is a text input), the value attribute no longer updates the input property.
After the value property has been updated the attribute no longer causes the property to update.
Therefore by setting the value property the value updates.
Because of that browser behavior, in Lit you can use a property expression to set the value property.
I.e.: <input .value=${this.activity.avgRating ? this.activity.avgRating : 0}.
Below is an example of the browser input behavior. Click the two buttons. One will update the value attribute, the other the value property.
Then interact with the input. Type in it. Now the attribute button will stop working.
const inputEl = document.querySelector('input')
const getRandomValue = () => String(Math.random()).slice(0, 5)
document.querySelector("#btn-attr")
.addEventListener("click", () => {
inputEl.setAttribute('value', getRandomValue())
});
document.querySelector("#btn-prop")
.addEventListener("click", () => {
inputEl.value = getRandomValue()
});
<input value="12345">
<button id="btn-attr">Change input attribute</button>
<button id="btn-prop">Change input property</button>

How to change Background image every x amount of seconds

I'm pretty new to Angular and programming in general.
I wanted to change the background image of my Page by using the setInterval method. It should change every second but for some reason, it changes much faster.
Component:
export class AppComponent implements OnInit {
images: Image[] = [];
changeBackgroundCounter = 0;
constructor(private imagesService: ImagesService) {}
getImage() {
setInterval(() => {
this.changeBackgroundCounter = this.changeBackgroundCounter + 1;
if (this.changeBackgroundCounter > this.images.length - 1) {
this.changeBackgroundCounter = 0;
}
}, 1000);
return this.images[this.changeBackgroundCounter].image;
}
ngOnInit() {
this.images = this.imagesService.getImages();
console.log(this.images[0]);
}
}
Template:
<div [ngStyle]="{'background-image': 'url('+ getImage() + ')'}" [ngClass]="{imageBackground: getImage()}">
Stackblitz link
In your template, you have
<div [ngStyle]="{'background-image': 'url('+ getImage() + ')'}" [ngClass]="{imageBackground: getImage()}">
This means angular keeps calling the getImage() method to find out what the background should be. This will happen very frequently. Each time the method is called, a new interval is created, so there end up being loads of them. You can see this by putting a line of logging within your interval and you will see how often it's being triggered.
setInterval(() => {
console.log('interval triggered'); // <------- add this line to see how often this code is running
this.changeBackgroundCounter = this.changeBackgroundCounter + 1;
if (this.changeBackgroundCounter > this.images.length - 1) {
this.changeBackgroundCounter = 0;
}
}, 1000);
To fix your problem, you need to call getImage() only once, which can be done within ngOnInit(). The template can get the image from images[this.changeBackgroundCounter].image.
You're complicating your code for nothing. Create a variable, equal to a string, and assign it a new value every X seconds in your ngOnInit() !
Then set the background image equals to that variable, and voilĂ  !
Here is what it look like in code :
export class AppComponent implements OnInit {
images: Image[] = [];
actualImage: string;
changeBackgroundCounter = 0;
constructor(private imagesService: ImagesService) {}
ngOnInit() {
this.images = this.imagesService.getImages();
this.actualImage = this.images[0].image;
setInterval(() => {
this.changeBackgroundCounter++;
if (this.changeBackgroundCounter > this.images.length - 1) {
this.changeBackgroundCounter = 0;
}
this.actualImage = this.images[this.changeBackgroundCounter].image;
}, 5000);
}
}
I kept as much as possible of your inital code. My new variable is called actualImage, I set a default value in my ngOnInit, right after you get all your images from your service.
Then I call setInterval and set a new value to actualImage every 5 seconds !
https://stackblitz.com/edit/angular-setinterval-f5bghq
CARE: When using setInterval, be used to clear it on ngOnDestroy(), it can lead to some weird bugs you don't want to get involved in.
Simply create an other variable, type any, and do the following :
this.interval = setInterval(() => {...})
ngOnDestroy() {
clearInterval(this.interval);
}

How to make a page can be swiped left and right in angular? [duplicate]

I'm new to Angular 2 and am looking for a way to implement a good tab touch swipe navigation for mobile users with a swipe transition to the next tab view.
So far I've found a package called angular2-useful-swiper although am not to keen on using it as I end up initializing my components early even though they are not in view.
Does anyone know a good way to implement a tab swipe based navigation for Angular 2? Any feedback will be greatly appreciated. : )
For the swipe detection here is a simpler solution than adding HammerJS:
In app.component.html:
<div (touchstart)="swipe($event, 'start')" (touchend)="swipe($event, 'end')">
App content here
</div>
In app.component.ts:
private swipeCoord?: [number, number];
private swipeTime?: number;
swipe(e: TouchEvent, when: string): void {
const coord: [number, number] = [e.changedTouches[0].clientX, e.changedTouches[0].clientY];
const time = new Date().getTime();
if (when === 'start') {
this.swipeCoord = coord;
this.swipeTime = time;
} else if (when === 'end') {
const direction = [coord[0] - this.swipeCoord[0], coord[1] - this.swipeCoord[1]];
const duration = time - this.swipeTime;
if (duration < 1000 //
&& Math.abs(direction[0]) > 30 // Long enough
&& Math.abs(direction[0]) > Math.abs(direction[1] * 3)) { // Horizontal enough
const swipe = direction[0] < 0 ? 'next' : 'previous';
// Do whatever you want with swipe
}
}
}
Note: I tried the HammerJS solution but configuring it to ignore mouse gestures was impossible because you don't have direct access to the Hammer object. So selecting some text was forcing navigation to the next page...
You can use HammerJS to implement for touch actions, You can follow this plunker for example.
Include hammer.js file
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.js"></script>
or
npm install hammerjs --save
For browser touch support with hammerjs, include
<script src="http://cdn.rawgit.com/hammerjs/touchemulator/master/touch-emulator.js"></script>
<script>
Import in app.module.ts
import { HammerGestureConfig, HAMMER_GESTURE_CONFIG } from '#angular/platform-browser';
export class MyHammerConfig extends HammerGestureConfig {
overrides = <any>{
'swipe': {velocity: 0.4, threshold: 20} // override default settings
}
}
#NgModule({
imports: [BrowserModule],
declarations: [AppComponent],
bootstrap: [AppComponent],
providers: [{
provide: HAMMER_GESTURE_CONFIG,
useClass: MyHammerConfig
}] // use our custom hammerjs config
})
plunker link for example
To implement tabs angular2-material is a good place to start, follow this link
I created a directive from #Elvis Metodiev answer and #pikiou :
swipe.directive.ts
import { Directive, EventEmitter, HostListener, Output } from '#angular/core';
#Directive({ selector: '[swipe]' })
export class SwipeDirective {
#Output() next = new EventEmitter<void>();
#Output() previous = new EventEmitter<void>();
swipeCoord = [0, 0];
swipeTime = new Date().getTime();
constructor() { }
#HostListener('touchstart', ['$event']) onSwipeStart($event) {
this.onSwipe($event, 'start');
}
#HostListener('touchend', ['$event']) onSwipeEnd($event) {
this.onSwipe($event, 'end');
}
onSwipe(e: TouchEvent, when: string) {
this.swipe(e, when);
}
swipe(e: TouchEvent, when: string): void {
const coord: [number, number] = [e.changedTouches[0].clientX, e.changedTouches[0].clientY];
const time = new Date().getTime();
if (when === 'start') {
this.swipeCoord = coord;
this.swipeTime = time;
} else if (when === 'end') {
const direction = [coord[0] - this.swipeCoord[0], coord[1] - this.swipeCoord[1]];
const duration = time - this.swipeTime;
if (duration < 1000 //
&& Math.abs(direction[0]) > 30 // Long enough
&& Math.abs(direction[0]) > Math.abs(direction[1] * 3)) { // Horizontal enough
const swipeDir = direction[0] < 0 ? 'next' : 'previous';
if (swipeDir === 'next') {
this.next.emit();
} else {
this.previous.emit();
}
}
}
}
}
tour.component.component.ts
<div
...
swipe
(next)="onRotateNext()"
(previous)="onRotatePrevious()"
>
...
</div>
First install hammerjs and action touch-action polyfill:
$ npm install hammerjs hammer-timejs
Then add the imports to 'app.module.ts' so they will be used/bundled:
import 'hammerjs';
import 'hammer-timejs';
Now you can handle the events for the actions:
Rotate
Pinch
Press
Pan
Tap
Swipe
For example you can say:
<li *ngFor="let employee of employeesList;" (swiperight)="myswiperight(employee)" (swipeleft)="myswipeleft(employee)">
Or:
<div (panstart)="onPanStart($event)" (panmove)="onPan($event)">
Reference: https://saschwarz.github.io/angular2-gestures-slides/#/
I managed to come up with a write-once-use-everywhere type of function which I put in a dir called "gestures" and then created a file called "swipe.ts" and put this inside.
let swipeCoord = [0, 0];
let swipeTime = new Date().getTime();
export function swipe(e: TouchEvent, when: string): void {
const coord: [number, number] = [e.changedTouches[0].clientX, e.changedTouches[0].clientY];
const time = new Date().getTime();
if (when === 'start') {
swipeCoord = coord;
swipeTime = time;
} else if (when === 'end') {
const direction = [coord[0] - swipeCoord[0], coord[1] - swipeCoord[1]];
const duration = time - swipeTime;
if (duration < 1000 //
&& Math.abs(direction[0]) > 30 // Long enough
&& Math.abs(direction[0]) > Math.abs(direction[1] * 3)) { // Horizontal enough
const swipeDir = direction[0] < 0 ? 'next' : 'previous';
if (swipeDir === 'next') {
alert('swipe next');
} else {
alert('swipe prev');
}
}
}
}
Then import into the desired component, like so:
import {swipe} from '../../gestures/swipe';
And create a function called:
onSwipe(e: TouchEvent, when: string) {
swipe(e, when);
}
In the HTML of the desired component, go with this:
<div (touchstart)="onSwipe($event, 'start')"
(touchend)="onSwipe($event, 'end')">
<!-- whatever content you have goes here -->
</div>
PS - credit goes to #pikiou. I just came up with a higher level of abstraction, which to me makes a lot more sense.

Why are my React rendered HTML elements changing positions after refresh?

I have a react component that I am using as checkpoint to check if the user has viewed a certain section of the site.
class ContentSection extends Component {
constructor(props) {
super(props);
}
render() {
const allParagraphs = [];
for (let i = 0; i < this.props.paragraphs.length; i++) {
let p = this.props.paragraphs[i];
allParagraphs.push(
<Paragraph key={i} image={p["img"]} text={p["text"]} />
);
}
return (
<div className="cs">
<ContentSectionCheckPointContainer
uniqueIndex={this.props.uniqueIndex}
/>
<h4 className="sectionTitle">THIS IS A SECTION!!!</h4>
{allParagraphs}
</div>
);
}
}
And this is the ContentSectionCheckPointContainer
const mapDispatchToProps = dispatch => {
return {
unlock: index => dispatch(Unlock_Index_Action(index))
};
};
const mapStateToProps = state => {
return {
CheckPoints: [...state.CheckPoints]
};
};
class ContentSectionCheckPoint extends Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
this.myRect = null;
this.checkVisibility = this.checkVisibility.bind(this);
}
componentDidMount() {
this.checkVisibility();
window.addEventListener('scroll', this.checkVisibility);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.checkVisibility);
}
checkVisibility() {
if (this.myRef.current) {
let rect = this.myRef.current.getBoundingClientRect();
var viewHeight = Math.max(
document.documentElement.clientHeight,
window.innerHeight
);
let b = !(rect.bottom < 0 || rect.top - viewHeight >= 0);
if (b !== this.props.CheckPoints[this.props.uniqueIndex]) {
if (b) {
this.props.unlock(this.props.uniqueIndex);
}else{
this.props.unlock(this.props.uniqueIndex);
}
}
}
}
render() {
this.checkVisibility();
return (
<div ref={this.myRef} className="cscp">
{this.props.CheckPoints[this.props.uniqueIndex] && <p>hi</p>}
</div>
);
}
}
const ContentSectionCheckPointContainer = connect(
mapStateToProps, mapDispatchToProps)(ContentSectionCheckPoint);
As you can see I ran a visibility check on scroll, which works fine. However, I wanted to also run the visibility check immediately when the page is loaded before any scrolling occur.
It is to my understanding that componentDidMount is when React already rendered an element for the first time, so I wanted to do the check then. However, I was trying to render two ContentSection components, each containing their own check point. The latter check point for unknown reason is positioned higher than it appears on screen during componentDidMount, resulting in my visibility check returning true even though it is not visible. If I refresh the page, its position is correct again and the visibility check is fine.
This problem only seem to occur during the first time when I open up a new tab to load my code, but no longer occurs after a refresh on that tab.
Any idea why?

How to use Observables/ real-time change only one entry in table in angular2+

I am stuck with an issue in angular4 and node.js app. I display the data in a table that has 7 columns via *ngFor . Now the issue is , i need to dynamically and on real-time basis update the last column . i.e. if the column -> Active ... is Yes , then i display a green color and when the Active ...is No, then i display a red color. But then i am not sure how to update the last column and only the last column real-time.
[1]
I thought of using Observables code in init from where i call the table data, but that will only keep on showing the loader always , as the data will keep on uploading regularly after a few seconds and disturb the entire table view.
[2]
And i have shown the color based on an if condition, but in all the entries, it shows only green color.
code -
interface.component.ts
export class MasterInterfaceComponent implements OnInit {
activeCheck;
activeValue;
activeRedColor;
activeGreenColor;
ngOnInit() {
console.log("beginning of func");
Observable.interval(15000).subscribe(x => {
console.log("Called")
this.viewData();
});
}
viewData() {
this.loading = true;
var url = config.url;
var port = config.port;
this.http.post("http://" + url + ":" ...... ('Token') }) })
.map(result => this.result = result.json(),
)
.subscribe((res: Response) => {
this.loading = false;
this.records = res;
console.log("xxxx interface view result data ",this.result)
console.log("XXXXXXXXXXXX interface view res data ", res);
this.activeCheck = this.result;
for (let obj of this.activeCheck){
for (let key in obj){
if (key == "ACTIVE_YN"){
if (obj[key] == "Y"){
this.activeRedColor = false;
this.activeGreenColor = true;
console.log("this.activeGreenColor = true;");
}
else if (obj[key] == "N"){
this.activeRedColor = true;
this.activeGreenColor = false;
console.log("this.activeGreenColor = false;");
}
}
}
}
});
}
interface.component.html
<tbody>
<tr *ngFor="let data of result |filter:filter| orderBy : 'IF_ID'|
paginate: { itemsPerPage: 5, currentPage: p }; let i =
index">
<td class="text-center" style="width:8%">
<a [hidden]= "accessIdHide" [routerLink]="['/master-system/update-
interface']" (click)="forUpdate(data)" data-toggle="tooltip"
title="Update" style="color:#ffffff;;float:left"
type="link">
<span class="glyphicon glyphicon-plus-sign"></span>
</a>{{data.IF_ID}}
</td>
<td>{{data.IF_CD}}</td>
<td>{{data.IF_NAME}}</td>
<td>{{data.IF_DESCRIPTION}}</td>
<td>{{data.SRC_SYS_NAME}}</td>
<td>{{data.TRGT_SYS_NAME}}</td>
<td >
<img [hidden]= "activeGreenColor" src = "url" width="10" height =
"10">{{data.ACTIVE_YN}}
<img [hidden] = "activeRedColor" src = "url" width="10" height =
"10">{{data.ACTIVE_YN}}
</td>
</tr>
</tbody>
First of all, you do not need two switches, but the way you had it is ok, but for our discussion, let's just say we have one switch "isActive".
I think your code should work, but you need to make sure the subscription is firing, ex.
.subscribe((res: Response) => {
console.log(res);
If this code fires every time the data changes, then your img should be turned on and off
<img *ngIf="isActive" src="url">
or
<img [ngClass]="{ isActive: green }">
Either of the above is fine. So just try to debug your observable to work the way you wanted, ex. when it should be fired and in which frequency.