0

So I have this function that has a timeout that changes every 3s:

 setActiveImage(promotions) {
    for (let i = 0; i <= promotions.length - 1; i++) {
      setTimeout(()=> {
        this.activeImage = 'http://myrul/public/Commercials/' + promotions[i].commercial_file[0].file;
      }, 3000*(i)); //CHANGE PICTURE EVERY 3s

    }
  }

Now I want change the time (3000) to a custom variable that I get from promotions. This is promotions:

enter image description here

So every picture, or instance of i, has it's own time.

Here is what I did:

for (let i = 0; i <= promotions.length - 1; i++) {

  var x = promotions[i].time; //GET TIME
  var y = +x; //TURN STRING TO NUMBER
  var z = y * 1000; //TURN SECOND INTO MILISECONDS

  var promoDisplayTime = z; //CUSTOM TIME

  setTimeout(()=> {
    this.activeImage = 'http://myurl/Commercials/' + promotions[i].commercial_file[0].file;
  }, promoDisplayTime*(i));

}

This in theory should work, but the timer goes way off. The first picture should last 4s, but lasts 3s. The second picture should last 3s but lasts 6s. Third picture should be 10s but lasts 4s...

I don't see what I am doing wrong. Why is the timer off even though I am sending the correct variable promoDisplayTime.


StackBlitz with dummy data: https://stackblitz.com/edit/angular-r6ejw9?file=src%2Fapp%2Fapp.component.html

IkePr
  • 900
  • 4
  • 18
  • 44
  • You're not using correctly setTimeout in loop. Please, see https://stackoverflow.com/questions/5226285/settimeout-in-for-loop-does-not-print-consecutive-values – Alexander Jan 27 '20 at 12:03
  • Don't increment it by `i`. You only want the time from the promotions - `promoDisplayTime`. – Vladimir Bogomolov Jan 27 '20 at 12:09
  • His problem is not that, it's rather, that `PromoDisplayTime * i` is absolutely not what he wants. Lets say the first ten images should take one second, and the eleventh should take ten seconds. For the eleventh image, `PromoDisplayTime * i` will be `10000 * 10` (loop starts at 0), so almost two minutes. He has to sum all the values. – ASDFGerte Jan 27 '20 at 12:09

4 Answers4

1

Here is one of the possible solutions with the async function and setTimeout().

With async function, we make sure that the next slide is not executed before the first one is.

The thing that you should watch out with the setTimeout is that the timer will execute the given function AFTER the time expires.

So the working example should be something like this:

activeImage: string = 'https://pngimage.net/wp-content/uploads/2018/06/start-png-.png';

  promotions = [
    {
      time: 6,
      pic: 'https://pngimage.net/wp-content/uploads/2018/06/1-an-png-1.png'
    },
    {
      time: 3,
      pic: 'https://upload.wikimedia.org/wikipedia/commons/1/10/MRT_Singapore_Destination_2.png'
    },
    {
      time: 4,
      pic: 'https://upload.wikimedia.org/wikipedia/commons/a/aa/L%C3%ADnea_3_CAMETRO.png'
    }
  ];

  ngOnInit() {
      this.setActiveImage();
  }

  setActiveImage() {
    let _this = this;
    countDown();

    function printer(i) {

      return new Promise(resolve => {
        _this.activeImage = _this.promotions[i].pic;
        setTimeout(function () {
          resolve(true);
        }, _this.promotions[i].time * 1000);
      });
    }

    async function countDown() {
      for (var i = _this.promotions.length -1; i >= 0; i--) {
        await printer(i);
      }
    }

  }

Live demo: https://stackblitz.com/edit/angular-qwtgm2

Milos Segic
  • 111
  • 1
  • 2
  • 8
0

When working within an Angular context, I'd recommend to embrace to power of RxJs.

const MOCKED_DATA = [
  {
    time: "6",
    pic: "https://pngimage.net/wp-content/uploads/2018/06/1-an-png-1.png"
  },
  {
    time: "3",
    pic:
      "https://upload.wikimedia.org/wikipedia/commons/1/10/MRT_Singapore_Destination_2.png"
  },
  {
    time: "4",
    pic:
      "https://upload.wikimedia.org/wikipedia/commons/a/aa/L%C3%ADnea_3_CAMETRO.png"
  }
];

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  promotions$ = from(MOCKED_DATA).pipe(
    map(promotion => of(promotion.pic).pipe(delay(1000 * +promotion.time))),
    mergeAll(),
    startWith("https://pngimage.net/wp-content/uploads/2018/06/start-png-.png")
  );
}

and then in your template:

<img [src]="promotions$ | async" alt="Responsive image of item">

Live demo: https://stackblitz.com/edit/angular-xjcrgd?file=src/app/app.component.ts

Edit: I'm not sure whether the time is relative or not in your question but after reading it again I think it is. If that's the case, then you could build a remap function to have an absolute time like that:

const remapToAbsoluteTime = (data: ServerContent[]): LocalContent[] =>
  data.reduce((acc, x) => {
    if (!acc.length) {
      acc.push({
        ...x,
        time: +x.time
      });
    } else {
      acc.push({
        ...x,
        time: acc[acc.length - 1].time + +x.time
      });
    }

    return acc;
  }, []);

Then you'd have the photos ordered 1, 2, 3.

Live demo: https://stackblitz.com/edit/angular-6c4pqt?file=src/app/app.component.ts

maxime1992
  • 22,502
  • 10
  • 80
  • 121
0

I see two mistakes:

  1. You count i from 0. The result is that the first value (6 seconds) becomes 0 when you calculate timeout value so first function is executed almost immediately. Then the second value (3 seconds) is used for showing the second picture and is multiple by i which is 1 at that point (so you see the first one lasting 3 seconds). The third picture follows the same logic.

  2. You call all setTimeouts at the same time using i as some sort of separator. To achieve what you need you should call setTimeout for picture n + 1 after the setTimeout for picture n ends.

Ivan Kashtanov
  • 674
  • 4
  • 17
0

per this answer Is setTimeout a good solution to do async functions with javascript?

setTimeout(function(){...}, 0) simply queues the code to run once the current call stack is finished executing.

so what's happens is the for loop finishes with promotions[i].time = 4 and the setTimeout piece of code runs with that value (at least this is what I see in your code in StackBlitz)...

Yaniv Amrami
  • 377
  • 2
  • 12