I have a simple component to display a list of items. I have a ngfor loop and inside it a checkbox with a [(ngModel)]. Everything works as expected.
<div *ngFor="let armor of armorList">
<input type="checkbox" [(ngModel)]="armor.obtained" />
<span>{{armor.name}}</span>
</div>
I have create a get property called name like below. (I have removed the implementation itself just for the example)
get name(): string {
console.log("test");
return "test";
}
What I think is odd is that, if I click on the checkbox of one item on the list, this get property is being called for all the items on the list twice. I would assume that it should not be called at all, since I am just updating one property of one item, not related to the rest.
https://plnkr.co/edit/KqDpuw1QeXOCFNODbqiW?p=preview
I have create the example above. If you click on one of the checkbox you can see that "test" is being displayed 6 times on the console.
Any ideas?
Regards
Leo
That's because angular rechecks the model again after updating the views to verify that it hasn't changed. This is done to ensure the stability of the model and that updating the view doesn't update the model again and you start an infinite loop. If angular finds that the model has changed after updating, it will throw ExpressionChangedAfterItHasBeenCheckedError error.
Note that this only happens in development mode.
For further reading : see this article
It will be called twice only in the development mode for verifying that the moel has not been changed. If model will be changed in the second call, it will throw error. So you can detect the wrong cases at the development stage. When you build your bundles in the production mode, it will call once.
Related
I'm currently facing a problem with Meteor and React, where i know some partly solutions but they don't work and imo none of them is pointing in the true direction.
The situation:
All is about an fitness app: I have a structure that represents exercises for customers, while each exercise can have a defined number of sets (a set is how often a exercise should be done). Each set has some properties (all the user can manipulate within the font-end).
Now i have the following component structure with some map-functions (state properties are in {}):
Training {customers,exercises,datetime,otherinfos}
- Overview {customers,exercises}
exercises.map():
- Exercise {exercise,customers}
customers.map():
- Customer {exercise,customer}
exercise.sets.map()
Set {exercise, customer, set, valuesofset}
From a UI-perspective (react) this all works without problems.
Now the idea is to have a button "Save" within the Training component. When the button is pressed, I want to save the state of all Set-Components in a "sets" collection (if it has other values than the default placeholder ones) and at the same time save the Training-Component in a "trainings" collection. But the training should also include information about what Sets are integrated (so at least the Set._id should be in the Training-Component state at time of Saving.
Here now my ideas so far:
Create refs from Training all the way down to all Sets and then, when pressing "Save" iterate over all refs and call a "Mongo.insert" from all Sets. Here i have the problem that i cannot return the inserted _id. Of course i could call a different function in each Component from Set all the way back to Training, but imo this is an overflow.
Try to manage the state of all sets within the Training state by calling a nested function. As i have onChangeHandler on the Inputs, this would always call a method in Training and check which one of the Sets was changed and then changes it. I have tried it this way, but it led to a very bad performance.
Create a temp-ID for Training, forward it to to the Sets (using the componentWillReceiveProps method) and when in Set, insert the Set in the database with the temp-ID. Then receive all Sets with temp-ID and use it to add the Training in the database. --> imo very complicated and I don't really want to do a database call if it is not necessary.
So currently i don't know how to solve this problem. The reason i try to separate "sets" and "trainings" is given through the fact, that later on i would like to give information about the last Set right next to the new empty Set whenever one is on the database. Any tips are welcome!
EDIT:
As suggested, there is also the possibility to solve the problem with Session. So therefor i have added the following code to Set:
componentDidMount() {
Tracker.autorun(() => {
Session.set(`set_${this.state.id}`, {
...this.state
});
});
}
My idea was then to iterate over all Session-Keys from Training which start with "set_" - unfortunately there is no function to that holds all Keys.
Second idea was to to use an array as value for a Session-pair. However, it's quite a procedure to handle the update of the reactive Set component (copy array from session, check whether an element is available or not, create a new one or update the existing one).
EDIT2:
I think i got a solution with Session:
Object.getOwnPropertyNames(Session.keys)
did the trick to get all SessionKeys! Thank you for your help!
If you do not want to use Redux or pass parent bound callbacks in the child component, you can try Session to store data at app level which can be accessed(set/get) in any component
https://docs.meteor.com/api/session.html
In your case, you may set values of "Set" in Session and access it in Training. You may also need https://guide.meteor.com/react.html#using-withTracker. Using withTracker will help in doing reactive update of the database on change of any Session variable.
I have a notifications page generated by a ng-repeat looking like this:
The list is generated like this:
<div id="notifications" class="page_content">
<div class="notification center" notification-view notification="notification" providers="notifications.providers" ng-repeat="notification in notificationsObjects">
</div>
After the user completed the notification (and the data is updated in the database) I update the notificationsObjects array by removing the completed notification. I do this with the following function:
$scope.removeNotificationFromNotificationList = function(notification){
for(var i = 0; i<$scope.notificationsObjects.length; i++){
if($scope.notificationsObjects[i].id == notification.id){
var indexToRemove = $scope.notificationsObjects.indexOf($scope.notificationsObjects[i]);
var updatedArray = $scope.notificationsObjects;
updatedArray.splice(indexToRemove, 1);
$scope.notificationsObjects = updatedArray;
}
}
}
But after updating the array, this happens although the data is still available:
The 2 arrays (before and after updating) look exactly what I expect but for some reason the directive for a single notification doesn't have the notification object anymore. These are the 2 notificationsObjects arrays (before and after removing the completed notification):
I thought by just recalculating the notifications and reset the new $scope.notificationsObjects would solve my problem, and it does, but it reloads my whole DOM and I don't want that. I just want the completed notification to disappear from the array (and so, remove it from the DOM). Does anyone have a clue what causes this problem, or does anyone have a better solution to solve this?
Thanks in advance!
I solved it now but didn't really find the cause. I noticed some variables in the single notification directive not being set on the $scope. Even though they were not depending on changes, I coupled them to the $scope and the problem was solved.
I currently list a set of options for ng-options from a resource that contains JSON.
[
{
id:23,
name:"Other"
},
{
id:24,
name:"Crew"
},
{
id:25,
name:"Announcer"
},
{
id:26,
name:"Producer"
},
{
id:27,
name:"Cameraman"
},
{
id:28,
name:"Monitor"
}
]
This is all added into my scope at $scope.broadcaster = response.data. I then loop through everything in my options by a simple ng-options.
<select ng-model="selectedrole" ng-options="roles as roles.name for roles in broadcaster" ng-init="selectedrole=broadcaster[0]">
</select>
Everything goes good once the page loads up. I can select from my list of names and the ng-init starts on the first selection as anticipated. However I have a few issues that I can't seem to resolve.
1) In my doc I set up {{selectedrole}} and I expected to see the contents of my model on the page reflected by my current selection. Instead I see no output at all. I can't tell if the model is even being updated properly so I'm not sure if I can use it in my formdata.
2) This select is generated on click so users can select a role for more than one person. However since the model is the same, any selection is copied over which ever one is changed. I need to find a way to output the model to check the data, but make that model dynamic so i can get a list of results from all the different selections.
EDIT:
Now this is really crazy. I mocked it up in a plunker and its at least showing the model just fine. Are there some conflictions I should worry about that would stop the model from updating? I literally copy pasted my data and its working in plunker but not my project.
http://plnkr.co/edit/GbdOmdjj1M3OuZKO5cSq?p=preview
EDIT2:
Even more info. As I stated before, the select is created when a user clicks on a function that creates an ng-repeat with user information and a new select option for role. If I place the {{selectedrole}} within that ng-repeat I actually get all the data returned when I user selects it. It seems that since the click creates a push of new data, it will not work outside each item. The issues now is that every instance has its own model so i need to figure out how to gather all this data from each ng-repeat and post it to the form. I'll try to update as I work through the issue.
App.PhotoUpload = Ember.TextField.extend
type: "file"
change: (evt) ->
# #get('controller') gives - App.PhotoUpload
#get('controller').send('uploadImage')
In above code,if I debug it and check #get('controller').constructor it returns App.PhotoUpload, which is not right and it doesn't call uploadImage action which is in my route.
When I change Ember.TextField to Ember.View, #get('controller') works as expected and uploadImage action is called. But, as Im doing auto update to server on image upload, I need to stick with TextField.
I have these questions:
1. What are ways to access another controller from Emberview ?
2. How can I get access to right controller from Ember.TextField ??
The problem here is that the included input fields were recently modified to extend Ember.Component instead of Ember.View. One of the primary differences between a component and a view is that the controller property of a component is itself. Honestly, I'm not totally sure why that change was made. Having said that, it's still possible to access the controller from the context of where the text field was defined using the targetObject property.
Update:
I looked into this a bit more and it turns out that not all input fields were turned into components, only the TextField and TextArea. It appears the primary reason for turning them into components was to expose some helpful action handlers since components provide that capability.
I have the following html that is bound to an object containing id and status. I want to translate status values into a specific color (hence the converter function convertStatus). I can see the converter work on the first binding, but if I change status in the binding list I do not see any UI update nor do I see convertStatus being subsequently called. My other issue is trying to bind the id property of the first span does not seem to work as expected (perhaps it is not possible to set this value via binding...)
HTML:
<span data-win-bind="id: id">person</span>
<span data-win-bind="textContent: status converter.convertStatus"></span>
Javascript (I have tried using to modify the status value):
// persons === WinJS.Binding.List
// updateStatus is a function that is called as a result of status changing in the system
function updateStatus(data) {
persons.forEach(function(value, index, array) {
if(value.id === data.id) {
value.status = data.status;
persons.notifyMutated(index);
}
}, this);
}
I have seen notifyMutated(index) work for values that are not using a converter.
Updating with github project
Public repo for sample (not-working) - this is a really basic app that has a listview with a set of default data and a function that is executed when the item is clicked. The function attempts to randomize one of the bound fields of the item and call notifyMutated(...) on the list to trigger a visual updated. Even with defining the WinJS.Binding.List({ binding: true }); I do not see updates unless I force it via notifyReload(), which produces a reload-flicker on the listview element.
To answer your two questions:
1) Why can't I set id through binding?
This is deliberately prevented. The WinJS binding system uses the ID to track the element that it's binding to (to avoid leaking DOM elements through dangling bindings). As such, it has to be able to control the id for bound templates.
2) Why isn't the converter firing more than once?
The Binding.List will tell the listview about changes in the contents of the list (items added, removed, or moved around) but it's the responsibility of the individual items to notify the listview about changes in their contents.
You need to have a data object that's bindable. There are a couple of options:
Call WinJS.Binding.as on the elements as you add them to the collection
Turn on binding mode on the Binding.List
The latter is probably easier. Basically, when you create your Binding.List, do this:
var list = new WinJS.Binding.List({binding: true});
That way the List will call binding.as on everything in the list, and things should start updating.
I've found that if I doing the following, I will see updates to the UI post-binding:
var list = new WinJS.Binding.List({binding: true});
var item = WinJS.Binding.as({
firstName: "Billy",
lastName: "Bob"
});
list.push(item);
Later in the application, you can change some values like so:
item.firstName = "Bobby";
item.lastName = "Joe";
...and you will see the changes in the UI
Here's a link on MSDN for more information:
MSDN - WinJS.Binding.as
Regarding setting the value of id.
I found that I was able to set the value of the name attribute, for a <button>.
I had been trying to set id, but that wouldn't work.
HTH
optimizeBindingReferences property
Determines whether or not binding should automatically set the ID of an element. This property should be set to true in apps that use Windows Library for JavaScript (WinJS) binding.
WinJS.Binding.optimizeBindingReferences = true;
source: http://msdn.microsoft.com/en-us/library/windows/apps/jj215606.aspx