Undesirable scope bleed between components in Ember - ember.js

I have a component [ui-button] (github) where I'm adding a wrapping component called ui-buttons (demo here). The problem is that the wrapping component seems to receive registrations not only from its children but from ALL children that are on the page! Effectively this property is acting like a static variable across all instances of ui-buttons. I didn't even know you could do this and in this case its definitely an undesirable effect.
In the demo link above try clicking on the the "disable the group" button and notice that it disables ALL buttons. So what am I doing?
Structurally it looks like this:
{{#ui-buttons as |group|}}
{{ui-radio-button title='foo' group=group}}
{{ui-radio-button title='bar' group=group}}
{{ui-radio-button title='baz' group=group}}
{{/ui-buttons}}
In this process I have child elements (e.g., ui-radio-button) register themselves with ui-buttons. The item-level registration code is:
_registration: on('init', function() {
const group = this.get('group');
if(group) {
group._registerItem(this);
}
}),
and the group-level registration code is:
_registerItem: function(child) {
console.log('registering %o with %o', child, this.get('elementId'), this.get('_registeredItems.length'));
this.get('_registeredItems').pushObject(child);
},
If you note the group-level registration has a "console.log" statement and that produces very encouraging results (it recognizes the element ID of the group as being distinct) alongside worrisome results (the registry "length" continues to grow across all):
I suspect this is down to async or this complexities but I'm now at a loss on how to proceed.

Your problem is this line of code
_registeredItems: new A([]),
That creates a single array one time (I believe when the component is parsed - on application load) and all your components are using it..
Best bet is to change it to
_registeredItems: Ember.computed(function() {
return new A([]);
})
When _registeredItems is first accessed it creates the new array.

Related

Emberjs inside of get computed making request to backend multiple times cause infinite loop

I have a table basically in every row i have get function that makes a backend request with store service. But somehow when there is one row it works expect, but when there is multiple rows it always try to recalculate get function which makes sending infinite request to backend. I am using glimmer component
I cannot use model relation on ember side at this point, there is deep chain on backend side. Thats why i am making backend request.
get <function_name>() {
return this.store.query('<desired_model_name>', { <dependent1_id>: <dependent1_id_from_args>, <dependent2_id>: <dependent2_id_from_args> });
}
I fixed this problem with using constructor. But do you have any idea why this get function re-calculate all the time? Dependent_ids are constant.
Weird thing is when results are [] empty array it does not re calculate every time. Even the query results are same it still try to recalculate every time and making infinite request to backend.
But do you have any idea why this get function re-calculate all the time?
When something like this happens, it's because you're reading #tracked data that is changed later (maybe when the query finishes).
because getters are re-ran every access, you'll want to throw #cached on top of it,
// cached is available in ember-source 4.1+
// or as early as 3.13 via polyfill:
// https://github.com/ember-polyfills/ember-cached-decorator-polyfill
import { cached } from '#glimmer/tracking';
// ...
#cached
get <function_name>() {
return this.store.query(/* ... */);
}
this ensures a stable object reference on the getter that the body of the getter only re-evaluates if tracked data accessed within the getter is changed.
Weird thing is when results are [] empty array it does not re calculate every time. Even the query results are same it still try to recalculate every time and making infinite request to backend.
Given this observation, it's possible that when query finishes, that it's changing tracked data that it, itself is consuming during initial render -- in which case, you'd still have an infinite loop, even with #cached (because tracked-data is changing that was accessed during render).
To get around that is fairly hard in a getter.
Using a constructor is an ok solution for getting your initial data, but it means you opt out of reactive updates with your query (if you need those, like if the query changes or anything).
If you're using ember-source 3.25+ and you're wanting something a little easier to work with, maybe ember-data-resourecs suits your needs
the above code would be:
import { query } from 'ember-data-resources';
// ...
// in the class body
data = query(this, 'model name', () => ({ query stuff }));
docs here
This builds off some primitives from ember-resources which implement the Resource pattern, which will be making a strong appearance in the next edition of Ember.

Ember.computed not updating with Ember.Array

I'm currently working on a application that works with with websockets to implement a real-time chat (along with many other things).
However, I have some issues with a Ember.computed member that observes a Ember.NativeArray.
I tried a few things found while googling the issue, but none of them seem to work!
My array is a Ember.NativeArray defined in a service like this:
chatMessages: Ember.A()
My computed property, in my component, is defined as such:
messages: Ember.computed('liveSocket.chatMessages', function() {
return this.get('liveSocket').get('chatMessages').toArray().reverse();
}),
And I set objects to the array in my service as follows:
this.get('chatMessages').set(data.id, null);
If I observe the array directly, nothing changes (I may be confused with that, but isn't that the point of embedding an array in a Ember.Array object?). Now, I can easily watch the array for new elements, by observing the liveSocket.chatMessages.length property. Everything works well and new messages are correctly added. The problem is when I try to remove messages. The websocket server is badly engineered, and changing it is a non-possibility: it doesn't remove the indexes of deleted messages, and returns null objects instead.
So I not only need to watch changes to the array's length, but also to its elements. I tried adding liveSocket.chatMessages.#each to my observed elements list, but that doesn't work either. To sum it up:
// Never updates
Ember.computed('liveSocket.chatMessages', ...)
// Updates only on push/pop (as expected)
Ember.computed('liveSocket.chatMessages.length', ...)
// Still only updates on push/pop
Ember.computed('liveSocket.chatMessages.length', 'liveSocket.chatMessages.#each', ...)
Am I missing something here? Is there a way to observe an array's length and all the elements it contains? Is my approach to this wrong? I am using Ember 2.6 and Ember Data 2.6.1.
Thanks!
I assume in chatMessages you have objects with property title.
So if you want to trigger every time title of any objects changed, you need to compute on :
Ember.computed('liveSocket.chatMessages.#each.title', ...);

Meteor - Subscription Does not Update Collection Display Until Refresh (Cache in Template?)

I have a template ("ImageView") displaying collection data, and I use it at two different routes to display different content: at one route it is used to display all data, and at another to display data that belongs to specific user.
Because my layout has various components so I embed the ImageView template within the main template of each route ("Album" and "Profile").
I set up the template level subscription at the two main templates like so:
Template.profile.onCreated ( function() {
Tracker.autorun(function() {
Meteor.subscribe('images', Meteor.userId());
});
});
and
Template.Album.onCreated (function() {
Tracker.autorun(function() {
Meteor.subscribe('images');
}); })
and I have my publication function like so at the server:
Meteor.publish('images', function ( user_id) {
findQuery={}
if (user_id) {
findQuery = {userId: user_id};
}
return Images.find(findQuery);
});
The problem is that, when I navigate between the route between the two pages, the template "ImageView" will not update its content reactively, until I press refresh. (If I enter the url by hand and press enter it will also work fine). Are there cache within the template? But they seemed to be created and destroyed as expected when route changes (checked in onCreated and onDestroyed callbacks).
I have console logged inside the publish function and I made sure the publication is changed when I navigate between the two routes, but the client side is not updating accordingly. I have also tried reactive-publish package, Template.subscriptionReady,FlowRouter.reload() and they don't solve the issue.
Can anyone give me insights on what the issue might be? Btw, I am using FlowRouter.
Any inputs are appreciated. Thank you very much.
You shouldn't use Tracker.autorun in onCreated because you would have to stop the computation manually when the template is destroyed. Use this.autorun instead. It uses the Template autorun.

How to make ArrayController ignore DS.Store's transient record

I have a list of clients displayed through a ClientsController, its content is set to the Client.find() i.e. a RecordArray. User creates a new client through a ClientController whose content is set to Client.createRecord() in the route handler.
All works fine, however, while the user fills up the client's creation form, the clients list gets updated with the new client record, the one created in the route handler.
What's the best way to make RecordArray/Store only aware of the new record until the record is saved ?
UPDATE:
I ended up filtering the list based on the object status
{{#unless item.isNew}} Display the list {{/unless}}
UPDATE - 2
Here's an alternative way using filter, however the store has to be loaded first through the find method, App.Client.find().filter() doesn't seem to behave the way the two methods behave when called separately.
// Load the store first
App.Client.find();
var clients = App.Client.filter(function(client){
console.info(client.get('name') + ' ' + client.get('isNew'));
return !client.get('isNew');
});
controller.set('content',clients);
Few ways to go about this:
First, it's very messy for a route/state that deals with a list of clients to have to go out of its way to filter out junk left over from another unrelated state (i.e. the newClient state). I think it'd be way better for you to delete the junk record before leaving the newClient state, a la
if(client.get("isNew")) {
client.deleteRecord();
}
This will make sure it doesn't creep into the clientIndex route, or any other client list route that shouldn't have to put in extra work to filter out junk records. This code would ideally sit in the exit function of your newClient route so it can delete the record before the router transitions to another state that'll called Client.find()
But there's an even better, idiomatic solution: https://gist.github.com/4512271
(not sure which version of the router you're using but this is applicable to both)
The solution is to use transactions: instead of calling createRecord() directly on Client, call createRecord() on the transaction, so that the new client record is associated with that transaction, and then all you need to do is call transaction.rollback() in exit -- you don't even need to call isNew on anything, if the client record was saved, it obviously won't be rolled back.
This is also a useful pattern for editing records: 1) create a transaction on enter state and add the record to it, e.g.
enter: function(router, client) {
this.tx = router.get("store").transaction();
this.tx.add(client);
},
then the same sort of thing on the exit state:
exit: function(router, client) {
this.tx.rollback();
},
This way, if the user completes the form and submits to the server, rollback will correctly/conveniently do nothing. And if the user edits some of the form fields but then backs out halfway through, your exit callback will revert the unsaved changes, so that you don't end up with some dirty zombie client popping up in your clientIndex routes display it's unsaved changes.
Not 100% sure, could you try to set the content of ClientsController with
Client.filter(function(client){
return !client.get('isNew'));
});
EDIT: In order to make this work, you have to first load the store with Client.find().

Ember.js router events as functions not working

I have set up some basic routing in my app by using the examples at http://emberjs.com/guides/outlets/#toc_the-router
Within the root I have some events that trigger from view actions e.g:
gotoStepOne: Ember.Route.transitionTo('stepOne'),
gotoStepTwo: Ember.Route.transitionTo('stepTwo'),
gotoStepThree: Ember.Route.transitionTo('stepThree'),
gotoStepFour: Ember.Route.transitionTo('stepFour'),
gotoStepFive: Ember.Route.transitionTo('stepFive'),
Full example router code at http://jsfiddle.net/hellosmithy/WdjXT/
This all works fine at the moment. The problem is that I'd like to add other code into these events. For example:
gotoStepOne: function() {
if (someCondition) {
Ember.Route.transitionTo('stepOne');
}
someOtherFunction();
}
However doing this breaks the routing without throwing any errors. It just no longer transitions.
Specifically I only want transitions to happen if a certain state is met - something has been selected or input by the user at each stage before they can proceed. Is there a workaround for this, or should I be abstracting this functionality elsewhere?
The way I understand the router is, that it is the representation of the application's state.
Specifically I only want transitions to happen if a certain state is met - something has been selected or input by the user at each stage before they can proceed.
So the user inputting or selecting something puts your application in a certain state which is reflected by the router.
IMHO it should be something like this in a view (or controller):
userDidSomething: function(condition) {
if (condition) {
App.get('router').send('stepOne');
}else{
someOtherFunction();
}
}