Need help understanding why I need to wrap $.append in a run loop


#1

Hi guys, can someone explain why I can only append to a div if I wrap Ember.run. if I do append, it says cannot append to a view that’s already been loaded. Also, does Ember force you to run DOM manipulation via Ember Run Loop? What is the Run Loop? Thanks ahead of time.

// works
    actions: {
            submit(){
                let input = $('#chat-textarea').val();
                console.log('input', input);
                this.userSubmit(input);
                this.botResponse("Oh that's really cool");
            }
        },
        userSubmit(response){
            let target = Ember.$('#chatroom');
            Ember.run.once(function(){
                let target = Ember.$('#chatroom');
                target.append(response);
            });
        },

        // doesn't work
    actions: {
            submit(){
                let input = $('#chat-textarea').val();
                console.log('input', input);
                this.userSubmit(input);
                this.botResponse("Oh that's really cool");
            }
        },
        userSubmit(response){
            let target = Ember.$('#chatroom');
            target.append(response);
        },

#2

Which Ember version are you using? I believe you can use append (Twiddle) (without wrapping it inside run loop) but that’s overall bad practise. You should generally restrain from manually managing DOM elements.

As for the Ember run loop, bear with me. Imagine you have some js value, for example a message, that you wish to display a user: So this is your object:

var object = {
   message: 'Welcome'
};

And this is your template (template scope is object):

<span id="message">{{message}}</span>

So, back in the days, long before ember and long before any templating engine, this was done manually, ie you would have $('#message').text('Welcome back');. Basically, using jQuery, you would select an element and set its content. This made javascript code pretty unmaintainable and js code and its template would be tightly coupled.

Then KnockoutJS came to the scene. With KnockoutJs you were able to disconnect your template and js code, so that template would reflect state of your objects. This is called data binding. With this you are able to define your object as:

var obj = {
   message: ko.observable('Welcome')
}

And your template:

<span class="message" data-bind="text: message"></span>

So whenever you want to change a message you just use obj.message('Welcome back);` and the appropriate message changes. But then, if you have multiple updates, lets say:

obj.message('Message 1');
obj.message('Message 2');
obj.message('Message 3');
...
obj.message('Message n');

You would be left with Message n, but at all these steps DOM element would be updated immediately. You can look at DOM manipulation as IO operations vs CPU operations, it’s pretty slow. If you have application that constantly updates DOM elements, that would be pretty slow application. Never the less, with this approach you had your templates and js code pretty maintainable.

Then AngularJS came to the scene. Just like KnockoutJS above, AngularJS also had data binding but now without the constant DOM update if the property changes. With angular you would have your object:

var obj = {
   message: 'Welcome'
}

and your template:

<span class="message" ng-model="message"></span>

You can now change the message by using obj.message = 'New message'. But this would not propagate immediately to DOM, instead, angular introduced concept of digest loop or digest cycle. Basically, you can update you object in the beginning of the cycle and on the end angular would propagate your changes to DOM. So:

/// Digest cycle start
obj.message = 'Message 1';
obj.message = 'Message 2';
obj.message = 'Message 3';
...
obj.message = 'Message n';
/// Digest cycle end

On the end of digest cycle angular would update DOM element with the message (Message n) and thats just one DOM manipulation, versus n DOM manipulation in previous example. That made things pretty fast. Also if you had this example:

/// Digest cycle start
obj.message = 'Message 1';
/// Digest cycle end
...
/// Digest cycle start
obj.message = 'Message 1';
/// Digest cycle end

this would update DOM element only once even there was two digest cycles. Angular introduced concept of dirty checking for the object properties. Basically, it would run through your object properties and see if anything changed (it would hold previous state, so it can know if something changed). If you had large object and properties changed a lot, this would make things a bit slow.

Then Ember came to scene. Ember sticked with the concept of digest cycle, called it run loop and removed dirty checking. So like Angular, you have a loop (a cycle) in the background, where DOM update occurs. With the set methods, you can explicitly tell Ember what has changed, so in the run loop, changes to DOM can be applied. So if you have setTimeout/setInterval/ done/fail on ajax call, you might want to wrap those functions inside a run loop, so ember would know there is something happening and there would be some changes to DOM.

Then React came to the scene… :slight_smile:

I know this is a rough explanation, but I hope it explains a bit about run loop and how ember got here. Please, correct me if I made some mistakes. Cheers.


#3

@kklisura Thank you for helping me understand the run loop. I guess its smart enough to do the updates that matter.

If i am making a chatroom app. What would be a good approach if i were to refrain from using append? This feature is dynamic everytime user creates a message we will have to append it to the target dom element?


#4

What’s wrong with using templates?

{{#each messages as |message|}}
  {{message}}
{{/each}}
Ember.Controller.extend({
  messages: Ember.computed(function() {
    return Ember.A();
  }).readOnly(),

  actions: {
    sendMessage(msg) {
      this.get('messages').pushObject(msg);
    }
  }
});

#5

@lightblade this approach saves me so much time. Thank you so much also it is less code. I will have to redo what i did but it is definitely worth it ty!


#6

Quick note @lightblade no need for a computer fresh ember array. You can simply do: messages:[] Ember is smart enough to observe pushed items into that array.


#7

Nope, that is bad practice. The reason is because anything you define on the class is attached to the prototype. This isn’t much of a problem for primitives. But for mutable objects like array, this causes states to leak between object instances. Since controllers are singletons, the effect is not apparent in your app. As soon as you write test for your controller, you’ll see it becoming a problem.

This is a hidden problem that leaves inexperienced people scratching their head on why their test is failing for no reason.


#8

Holy cow! I never knew that.

So since its a computed with no dependant key it will continue to use the initially generated array for subsequent get calls… clever!

Do you need to do that for objects other than arrays?


#9

Yes, any mutable objects needs to be wrapped. Alternatively, you can create a CP macro that do a deep clone of A template object.


#10

Hi @lightblade this is good information! thank you for sharing.