0

I am having a problem with action handling in Ember controller. I want to run some function continuously after edit button is clicked in hbs. I have tried it like this in action.

openEditWindow() {
  this.set('someChangingValue', true);
},

Here is the function that reacts to action someChangingValue change.

someChangingValue: false,
  someTestFunction: observer('someChangingValue', function() {
  var test = this.get('someChangingValue');

  if(test === true){
    Ember.run.later((function() {
      this.functionThatIsRunningEachTwoSeconds();
    }), 2000);
  } else {
    console.log('this should not do anything');
  }
}),

But this runs functionThatIsRunningEachTwoSeconds only once. Also tried the same functionality with changing someChangingValue to false if true and otherwise, that put me in an infinite loop of observing property.

Thanks!

Liam
  • 27,717
  • 28
  • 128
  • 190
Ramix
  • 41
  • 8

2 Answers2

0

Ember.run.later runs function only once. It is said clear in docs

Also, do you use very old version of ember? Ember.run.later is outdated and you supposed to use partial import import { later } from '@ember/runloop'; instead of that

As for your task, there is at least two ways

Using ember-concurrency addon

Install ember-concurrency and write in controller:

import { task, timeout } from 'ember-concurrency';

export default Controller.extend({
  infiniteTask: task(function* () {
    while(true) {
      this.functionThatIsRunningEachTwoSeconds();
      yield timeout(2000);
    }
  }).drop(),
});

Template:

{{#if infiniteTask.isIdle}}
  <button onclick={{perform infiniteTask}}>Start</button>
{{else}}
  <button onclick={{cancel-all infiniteTask}}>Stop</button>
{{/if}}

This addon is helpful in lot of situations, read it's docs to understand why you might need it

Creating a function that will recursively call itself

It's a classical JS approach to repeat some action, but in vanilla JS we use setTimeout instead of ember's later.

import { later, cancel } from '@ember/runloop';

export default Controller.extend({
  infiniteFuction() {
    this.functionThatIsRunningEachTwoSeconds();
    this.set('infiniteTimer', later(this, 'infiniteFuction', 2000));
  },
  startInfiniteFunction() {
    //clear timer as safety measure to prevent from starting few 
    //"infinite function loops" at the same time
    cancel(this.infiniteTimer);
    this.infiniteFuction();
  },
  stopInfiniteFunction() {
    cancel(this.infiniteTimer);
    this.set('infiniteTimer', undefined);
  }
});

Template:

{{#unless infiniteTimer}}
  <button onclick={{action startInfiniteFunction}}>Start</button>
{{else}}
  <button onclick={{action stopInfiniteFunction}}>Stop</button>
{{/unless}} 
Gennady Dogaev
  • 5,902
  • 1
  • 15
  • 23
0

Just to clarify what's wrong with your current code (and not necessarily promoting this as the solution), you must change the value for the observer to fire. If you set the value to true, and then set it to true again later without ever having set it to false, Ember will internally ignore this and not refire the observer. See this twiddle to see a working example using observers.

The code is

init(){
    this._super(...arguments);
    this.set('count', 0);
    this.set('execute', true);
    this.timer();
  },
timer: observer('execute', function(){
    //this code is called on set to false or true
    if(this.get('execute')){
      Ember.run.later((() => {
        this.functionThatIsRunningEachTwoSeconds();
        }), 2000);
      // THIS IS SUPER IMPORTANT, COMMENT THIS OUT AND YOU ONLY GET 1 ITERATION
      this.set('execute', false);
    }
  }),
functionThatIsRunningEachTwoSeconds(){
    let count = this.get('count');
    this.set('count', count + 1);
    this.set('execute', true);
}

Now that you know what's wrong with your current approach, let my go back again on record and suggest that this is not a great, intuitive way to orchestrate a repeated loop. I recommend the Ember-Concurrency as well since it is Ember lifecycle aware

If you handled the edit button in the route, you could super cleanly cancel it on route change

functionThatIsRunningEachTwoSeconds: task(function * (id) {
    try {
      while (true) {
        yield timeout(2000);
        //work function
      }
    } finally {
      //if you wanted code after cancel
    }
}).cancelOn('deactivate').restartable()

Deactivate corresponds to the ember deactivate route hook/event:

This hook is executed when the router completely exits this route. It is not executed when the model for the route changes.

mistahenry
  • 8,554
  • 3
  • 27
  • 38