Ember custom helper doesn't recompute

I have a custom helper named comt-helper which accepts 2 params and returns a boolean. This helper determines which template to be used based on the parameters.

{{#if (comt-helper commentsActivity commentType)}} {{partial “comments/empty”}} {{else}} {{partial “comments/view”}} {{/if}}

Initially comt-helper returns true (computed with commentsActivity and commentType) and “comments/empty” partial is rendered. Later I modify commentsActivity and the helper fails to recalculate with the modified object and still the same template is rendered. I want my custom helper to behave like built-in ember helpers (if, unless, each - recalculates with the modified object). Looking out for suggestions …

@Mohana_Priya what does the code for comt-helper look like?

1 Like

It looks something like this

import { helper as buildHelper } from ‘@ember/component/helper’; export default buildHelper(function(params) { let comment = params[0], type = params[1], retValue = false; switch (type) { case “all”: if(comment[0] !== undefined) { retValue = false; } else { retValue = true; } break; case “unresolved”: if(comment[0] !== undefined) { if((comment.filterBy(‘isOpen’, true)).length > 0) { retValue = false; } retValue = true; } else if (comment.isReply && comment.parentComment.isOpen) { retValue = true; } else if (!(comment.isReply) && comment.isOpen) { retValue = true; } break; case “resolved”: if(comment[0] !== undefined) { if((comment.filterBy(‘isOpen’, false)).length > 0) { retValue = false; } retValue = true; } else if (comment.isReply && comment.parentComment.isOpen !== true) { retValue = true; } else if (!(comment.isReply) && comment.isOpen !== true) { retValue = true; } break; } return retValue; });

sorry for poor formatting

I’ve tried recreate your helper here with some naive comment data and it appears to trigger re-renders.

Is it possible the switching logic of your helper is not updating correctly?

If you put console.log throughout your helper code is it not being called again, or is it giving an incorrect result?

If it’s not running again, how are you updating the commentsActivity object? Are you just updating a property or replacing the commentsActivity object?

Also just an FYI, you can insert code snippets which retains your code’s formatting. For example, triple backticks, ```, for multi-line code and single backticks for inline code. For example,

import { helper as buildHelper } from '@ember/component/helper';

export default buildHelper(function(params) {
  let comment = params[0],
      type = params[1],
      retValue = false;
  
  switch (type) {
    case 'all':
      if(comment[0] !== undefined) {
        retValue = false;
      } else {
        retValue = true;
      }
      break;
    case 'unresolved':
      if(comment[0] !== undefined) {
        if((comment.filterBy('isOpen', true)).length > 0) {
          retValue = false;
        } retValue = true;
      } else if (comment.isReply && comment.parentComment.isOpen) {
        retValue = true;
      } else if (!(comment.isReply) && comment.isOpen) {
        retValue = true;
      }
      break;
    case 'resolved':
      if(comment[0] !== undefined) {
        if((comment.filterBy('isOpen', false)).length > 0) {
          retValue = false;
        }
        retValue = true;
      } else if (comment.isReply && comment.parentComment.isOpen !== true) {
        retValue = true;
      } else if (!(comment.isReply) && comment.isOpen !== true) {
        retValue = true;
      }
      break;
  }
  return retValue;
});
1 Like

Thank you. Actually I was using insertAt(0) to add a new object to commentsActivity. So the problem was with insertAt(0).

1 Like

In another case, again it is failing. Here i am changing the values of an object inside my controller and the helper fails to recompute them.

Sample code here:

My template looks like,

{{#if (my-helper info context)}}
    <span>Warning</span>
{{/if}}

In my controller,

changeAction: function() {
    let that = this,
        info = that.get("info");
    set(info, "showWarning", true);
}

my helper,

import { helper as buildHelper } from '@ember/component/helper';

export default buildHelper(function(params) {
    let that = this,
        info = that.get("info");
    if(info.showWarning ) {
        return true;
    } else {
        return false
    }
});

Hey,

  1. There no need to assign this to “that” variable.
  2. Helper doesn’t have access to “this”, you should get info from params
import { helper as buildHelper } from '@ember/component/helper';

export default buildHelper(function(params) {
    let [info, context] = params; // I'm using deconstruction here (check here for more details https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)
    if(info.showWarning ) {
        return true;
    } else {
        return false
    }
});

Another thing to be aware of is the problem of “interior mutation”.

When you call a helper like (my-helper info), Ember will rerun the helper if the value of info changes. But Ember.set(info, 'showWarning', true) does not change the value of info itself. It’s changing a property inside info. In that case, Ember will not rerun the helper.

There are a few choices for how to solve this problem.

  1. The best choice is tracked properties, which makes this whole problem a non-problem and can automatically react to changes like this one, as long as you mark showWarning as @tracked. As of this moment, tracked properties haven’t shipped in a stable release yet, but I’m including them here for future readers of this thread. And you can already use them, if you’re willing to test Ember Canary.

  2. Sometimes you can re-arrange things so that the interior properties you care about are passed explicitly into the helper. So instead of (my-helper info), it would be (my-helper info.showWarning). This makes the helper know it needs to recompute if info.showWarning changes.

  3. You can replace your helper with a computed property on the component you’re working in. If it’s needed in many components, you can put the implementation in a separate file and import it into each component that needs it.

  4. You can replace your helper with a computed property on the object stored in info. This works if you don’t need to give different answers based on some additional data that’s not stored in info. You need to make sure info extends from import EmberObject from '@ember/object'.

  5. You can write a helper that knows how to do interior observation and recompute itself. You would use a class-based Helper that has an observer watching the interior property and calling recompute when the value changes. This is a complicated solution that is easy to get wrong.

3 Likes

Thanks @CezaryH. I an not using “this” either, i just wanted to give a sample code and didn’t look into that.

Thanks for your clarification @ef4. :slightly_smiling_face:

1 Like