How to change Background image every x amount of seconds - html

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);
}

Related

Navigation Menu: Alternating colors for badges (colors should be in sequential order)

This is the function defined in MainService.ts, it can change the color set in badgesColorSet ,I have 3 colors defined in the json config already and i want these 3 colors to change everytime I open the website lets it is red then i refresh the page it should be green and then i refresh again it should be blue. so is this function correct and should i use for loop ?and I think i need to divide it by something so it increments and goes from 0 ,1 ,2 as index?
getIteriateColor(){
//gets color out of color set from turnkey.config file for badges
let badgesColorSet = 0; badgesColorSet < Array.length; badgesColorSet++;
console.log(badgesColorSet);
return badgesColorSet;
the colors are defined in turnkey-config.json
"badgesColorSet":["#ffff00","#f51307","#0cc902"],
this code is in the mainservice to define the background color of the material badge
badge: {bg: this.getNextColor() , fg: 'white' , title: moduleBadge},
Assuming getNextColor() calls getIteriateColor() to get the next color.
On getIteriateColor() let's loop through "badgesColorSet":["#ffff00","#f51307","#0cc902"] and starting again from [0] when iterate reaches [2].
To remember what color was last used we should store it somewhere on the client where the state remains (e.g localStorage), that way we know what color to choose next.
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
badgesColorSet = ['#ffff00', '#f51307', '#0cc902'];
badgesColorSelected: string;
constructor() {
this.getIteriateColor();
}
getIteriateColor() {
// if there is no color in localStorage, set the first color
if (!localStorage.getItem('badgesColorSelected')) {
localStorage.setItem('badgesColorSelected', this.badgesColorSet[0]);
} else {
// if there is color, select the next color
const storageColor = localStorage.getItem('badgesColorSelected');
const colorIndex = this.badgesColorSet.indexOf(storageColor);
if (colorIndex + 1 > this.badgesColorSet.length - 1) {
this.badgesColorSelected = this.badgesColorSet[0];
localStorage.setItem('badgesColorSelected', this.badgesColorSet[0]);
} else {
this.badgesColorSelected = this.badgesColorSet[colorIndex + 1];
localStorage.setItem('badgesColorSelected',this.badgesColorSet[colorIndex + 1]
);
}
}
}
}
Working example: https://stackblitz.com/edit/angular-ivy-mw7s49?file=src%2Fapp%2Fapp.component.ts
Or for backend something similar except without localStorage.
badgesColorSet: string[] = ['#ffff00', '#f51307', '#0cc902'];
badgesColorSelected: string;
getIteriateColor() {
if (!this.badgesColorSelected) {
this.badgesColorSelected = this.badgesColorSet[0];
} else {
let nextColorIndex = 0;
for (let i = 0; i < this.badgesColorSet.length; i++) {
if (this.badgesColorSet[i] === this.badgesColorSelected) {
if (i <= this.badgesColorSet.length - 2) {
nextColorIndex = i + 1;
break;
}
}
}
this.badgesColorSelected = this.badgesColorSet[nextColorIndex];
}
console.log('current color is: ', this.badgesColorSelected);
}
badge: {bg: badgesColorSelected , fg: 'white' , title: moduleBadge},
I think the best way is to use [ngClass] and condition by pointing to the css classes that you have predefined with those colors.
In Component:
interface Link {
label: string;
route: string;
icon: string;
}
links: Link[] = [ // your links ]
Inside Template:
<nav>
<a *ngFor="let link of links; let odd = odd" [href]="link.route" [class.odd]="odd">{{link.label}}</a>
</nav>
If you want to "make something" when you refresh, you use localstorage I imagine you can use some like
color
badgesColorSet=["#ffff00","#f51307","#0cc902"]
ngOnInit(){
let index=localStorage.getItem('indexColor')!=undefined?
+localStorage.getItem('indexColor'): -1
index=(index+1)%3;
localStorage.setItem('indexColor',''+index)
this.color=this.badgesColorSet[index]
}
See that at first, if not exist localstorage.getItem('indexColor') (that's undefined) You makes index=0 and store "0", the next time you store "1","2","0","1","2"... -localStorage only admit "strings" it's because you use ''+index to convert to string and +localStorage.getItem('indexColor') to conver to number
The use of ìndex=(index+1)%3 makes that the value of index becomes 0,1,2,0,1,2,0,1,2...
NOTE: You can use also sesionStorage (Just replace in code localstorage by sessionStorage)
this is for the function i did some change to Joosep.P thanks to him .
getIteriateColor() {
if (!this.badgesColorSelected) {
this.badgesColorSelected = 0;
} else {
const colorIndex = this.badgesColorSelected;
if (colorIndex + 1 > this.badgesColorSet.length - 1) {
this.badgesColorSelected = this.badgesColorSet[0];
} else {
this.badgesColorSelected = this.badgesColorSet[colorIndex + 1];
}
}
console.log('current color is: ', this.badgesColorSelected);
return this.badgesColorSelected;
}
}
this is for the config
"badgesColorSet":["#f51307","#0cc902","#ffff00","#03fcf8","#03fcb1"],

Angular ngOnInit() - loop into a file HTML

I would like to display the values following -> 0 1 2 without using an array.
export class AppComponent {
ngOnInit() {
for (let i = 0; i < 3; i++) {
console.log('Number ' + i);
}
}
}
Into the file HTML what should I put to retrieve my values?
Here is my code here Stackblitz
Thank you for your help.
Html is bound with some values from the .ts file which are declared on class level as fields
export class AppComponent {
field1: any
....
Local variables like the ones that you use in a for loop are not bound in the html file and can't be used there.
Html is bound and has access to either public class field variables or public methods. You could make a public method to return something from a for loop and then the html could invoke that public method and render the return value.
example
public method1(): string {
let text = '';
for (let i = 0; i < 3; i++) {
if (text === ''){
text = text + i;
} else {
text = text + ' ' + i;
}
}
return text;
}
and then on .html file you can do
<div> {{method1()}} </div>
But the method ngOnInit() that you use is a method which is invoked automatically by angular in the angular lifecycle so shouldn't be used to achieve that.
I built a pipe that generates a sequence of consecutive numbers (starting with 0) that you can use directly inside of the template, not even needing the for in the ngOnInit method.
#Pipe({
name: 'sequenceGenerator'
})
export class SequenceGenerator implements PipeTransform {
transform(numberOfItems?: number): number[] {
const length = numberOfItems ?? 0;
const sequence = Array.from({ length: length }, (_, i) => i);
return sequence;
}
}
And you can use it like this:
<div *ngFor="let item of (3 | sequenceGenerator)">
{{item}}
</div>
Have a look at the stackblitz example for further reference.

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.

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

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;
});

Aurelia update value of bound item in another class

I guess the question boils down how to i pass the instance of a property to another class.
I have something like this:
import timerClass from "./timer";
export class App {
constructor() {
this.timeLeft = 6; //<--- I want to update this
new timerClass(this.timeLeft);
}
activate() {
}
}
and
export default class {
constructor(time) {
this.initialTime = time;
setInterval(function () {
if (--time < 0) {
time = this.initialTime; //<--- From here
}
}, 1000);
}
}
Time is passed in but not reflected in the view when updated.
In knockout this was easy as all observables are functions an I could pass it round all over the place. How would i do the same here, should I wrap it in a function too?
When you call
new timerClass(this.timeLeft);
you pass your variable by value, i.e. the timer just gets 6 and there is no way to modify it there. The easiest way to fix this is indeed pass the callback function. I made it work with the following code.
timer.js:
export default class {
constructor(time, callback) {
this.initialTime = time;
this.currentTime = time;
setInterval(() => {
if (--this.currentTime < 0) {
this.currentTime = this.initialTime;
}
callback(this.currentTime);
}, 1000);
}
}
app.js:
constructor(){
this.timeLeft = 6;
var timer = new timerClass(this.timeLeft, v => this.timeLeft = v);
}
So I did some more reading and came across the aurelia-event-aggregator
http://aurelia.io/docs#the-event-aggregator
This allowed me to try a different angle. As my timer is eventually going to become a game loop this pub/sub way of doing it will work quite nicely.
Im still quite green with the syntax so I imagine its doing some things not entirely "best practice" but hope it helps someone.
main.js
import {inject} from 'aurelia-framework';
import {EventAggregator} from 'aurelia-event-aggregator';
import TimerClass from "./timer";
#inject(EventAggregator)
export class Main {
constructor(eventAggregator) {
this.eventAggregator = eventAggregator;
this.timer = new TimerClass(this.eventAggregator);
this.eventAggregator.subscribe('gameLoop', currentTime => {
this.timeLeft = currentTime
});
}
activate() {
this.timer.start();
}
}
timer.js
export default class Timer {
constructor(eventAggregator) {
this.eventAggregator = eventAggregator;
}
start(){
var initalTime = 5;
var currentTime = initalTime;
setInterval(() => {
if (--currentTime < 0) {
currentTime = initalTime;
}
this.eventAggregator.publish('gameLoop', currentTime);
}, 500);
}
}
main.html
<template>
<div>
<h2>Time Left:</h2>
<div>${timeLeft}</div>
</div>
</template>