In my Menu controller I have a function to save a Menu. The $scope.menu variable holds the object that represents my menu data. The saveMenu function sends an Ajax call off to a REST API endpoint and then receives the updated menu back as a response. The problem is that when I assign $scope.menu to the response all my data bindings in the HTML template break. Suddenly all the menu data disappears.
The controller code.
$scope.saveMenu = function() {
var menu = $scope.createJsonMenuRequest();
var method = "PUT";
var url = FoodUrls.foodAPI + "menus/" + menu.id;
var req = {
method: method,
url: url,
headers: {
"Content-Type": "application/json"
},
data: angular.toJson(menu)
};
$http(req).success(function(data) {
$scope.menu = $.extend(true, {}, data);
});
};
The createJsonMenuRequest function simply goes through the menu and removes some properties from the copy that the API doesn't like.
Why does the binding to the HTML template break?
Updated
Before the assignment statement in the success function, the $scope.menu looks something like this.
{
name: "My Menu",
sections: [
{ $$hashKey: "object:17", id: 1, name: "blarg"}
]
}
Afterwards it looks like this...
{
name: "My Menu",
sections: [
{ id: 1, name: "blarg-edited"}
]
}
It loses the $$hashKeys that Angular is putting in there when the menu is originally created. Not sure what the significance of that is.
I would not recommend using jQuery's extend functions against any property on an angular $scope. $scope is a complicated object with lots of pointers to specific properties and such. I would recommend using angular.merge, as it should do a better job of merging the objects correctly without breaking the scope.
https://docs.angularjs.org/api/ng/function/angular.merge
I can't comment yet, so I'll just put this here.
How far did you debug?
I'd look at $scope.menu before the update, and then again after in your success method.
What's your data / template look like?
And just for my own curiosity, why the deep $.extend ? Could be totally valid, I've just never used it in this way.
Saw your update, the $hashkey shouldn't be an issue, if you don't want it, you angular.copy(data) or simply put a track by in your ng-repeat :
data-ng-repeat="item in menuItems track by $index"
Related
Having an object similar to:
const data = {
tasks: {
projects: [
name:'Project Name',
filters: [
{
name:'First Project Filter',
checked:false,
onChange:(event) => {
console.log(this.checked)
}
},
...
],
...
],
...
},
...
}
The problem at hand is how to reference the checked property without drilling through the entire object.
In the case above, it throws an error because this is undefined, so referencing this.checked is invalid.
I could extract from the event data properties so that I can get the
whole reference such as tasks.projects[0].filters[0].checked, but I
am wondering if an easier method is available.
The ideal solution would be a way to reference the surrounding properties of the function without traversing the entire object. Surely the function has a way to know that it is inside of an object so maybe something like parent().checked ?
If relative: I am using node.js and react to use this object to render a filtered sidebar that works with context to filter the data-set. I don't think that is relative as this seems like a pure JavaScript OOP situation.
I'm building an angular component that renders a table, and I'm running into some issues with the sorting function. The scope in this case looks like this:
$scope.listState = {
sortBy: '<string>',
sortReverse: <bool>,
headings: {...},
list: [
{
rowCols: {
user: 'timfranks#gmail.com',
name: 'Tim Franks',
since: '11/6/12'
}
rowState: {...}
},
{
{
user: 'albertjohns#sbcglobal.net',
name: 'Alber Johns',
since: '11/12/13'
},
rowState: {...}
},
{
{
user: 'johnsmith#sine.com',
name: 'John Smith',
since: '7/28/14'
},
rowState: {...}
}
]
};
I originally tried to sort the list via:
ng-repeat="row in $ctrl.list | orderBy:$ctrl.listState.sortBy:$ctrl.listState.sortReverse"
This didn't work, although in tracing with the debugger I found that orderBy was in fact getting the right arguments and returning a properly sorted list. Just to be sure, I changed the code to use orderBy in my controller, like this:
this.listState.list = _this.orderBy(listState.list, 'rowCols.' + listState.sortBy, listState.sortReverse);
This works for the first time (called within the constructor), but then stops working. I'm pretty sure this is some aspect of Angular's scope that I don't fully understand. Any help would be appreciated.
Using a filter in an ng-repeat expression does not update the original model, but rather creates a copy of the array.
You are on the right track with the this.listState.list = _this.orderBy(...) ... and it does make sense that it gets called only once.
If the listState.list model is getting new values after the controller loads and you want to resort with those new values, you would probably want to use something like:
$scope.$watchCollection('listState.list', function listChanged(newList, oldList){
$scope.listState.list = _this.orderBy(...);
});
I can't recall if $watchCollection is going to register a change if the order changes, but if you end up in an infinite loop with that code, you could put a blocker like:
var listSorting = false;
$scope.$watchCollection('listState.list', function listChanged(newList, oldList){
if(!listSorting){
listSorting = true;
$scope.listState.list = _this.orderBy(...);
$timeout(function resetSortBlock(){ // try it without the $timeout to see if it will work
listSorting = false;
});
}
});
Figured it out. The issue was that I used an attribute directive to render the list rows, and the attribute directive and ng-repeat were on the same element. This created a scope conflict between the two directives. I moved my directive to a div within the ng-repeat and it works fine.
Here's an example that uses Backbone with React.
He defines a Model: var _todos = new Backbone.Model();
And then adds two functions to it:
var TodoStore = _.extend(_todos, {
areAllComplete: function() {
return _.every(_todos.keys(), function(id){
return _todos.get(id).complete;
});
},
getAll: function() {
return _todos.toJSON();
}
});
What I don't understand is why areAllComplete is being applied to a Model instead of to a Collection.
Shouldn't this be a function in a Collection that will get all of its models and check that complete attribute.
Similarly, I would expect getAll to belong to a Collection - get all of its models.
This example seems to replace Collection with Model.
Maybe I don't totally understand how models are used.
That example is using Backbone.Model in a fairly wierd way in my opinion.
This is where it's adding new todos to the store:
var id = Date.now();
_todos.set(id, {
id: id,
complete: false,
text: text
});
}
What it's basically doing is setting every todo-item as an attribute of the Model, using the id as the attribute name. It ends up with _todos.attributes looking something like below
{
"1436600629317": {
"id": 1436600629317,
"complete": false,
"text": "foo"
},
"1436600629706": {
"id": 1436600629706,
"complete": false,
"text": "bar"
}
}
That's the same output you get from _todos.toJSON(). I've no idea why they decided to implement it like that, if they were to try using Backbone.Sync they'd end up with a server API that's not exactly RESTful. It seems strange to use Backbone without leveraging any of the things Backbone provides. There's a reference to the change event here but I don't see it being used anywhere. You could easily reimplement that store using any regular JS object.
The only thing that example seem to be actually using from Backbone is Backbone.Events in the dispatcher. You're totally right that using a Collection would make way more sense because then you could actually make it talk to a REST based server API. That example seems to only use Backbone for the sake of using Backbone.
My dilemma is that I would like to pass multiple object properties to an iron:router route in Meteor. The reasoning is that I would like to pass it a property to name my url with and a property to find a collection item with. They are completely independent of each other and I can't use the url property because it is not a value in the collection item. This is what I have:
Template.items.events({
'click': function () {
itemName = this.name.replace(/ /g,'')
Router.go('itemDetails', {itemName: itemName})
}
});
The problem is that although the Router handles this fine and sends me to the correct url, I cannot use itemName to find the collection item object that I am looking for (assume this is impossible).
Router.route('/items/:itemName', {
name: 'itemDetails',
data: function() {return Items.findOne({name: this.params.itemName})}
});
The above Router configuration will not return anything because name != this.params.itemName for any object.
I've tried passing the this object, or creating objects with multiple properties, but iron:router won't have it.
Any help is appreciated, thanks.
Edit #1: To help explain the question further, my problem is the same as routing to a page that uses multiple id's in the URL. For example, how would I go about passing properties to iron:router to fill the :_id and :itemId properties?
Router.route('items/:_id/:_itemId', {
name: 'detailDetails',
data: function() {...}
});
Edit #2: What I would like to do specifically is pass two properties to iron:router and have one of them be appended to the URL, and the other be used in the data property of the route to return a collection item. Example:
....
Router.go('itemDetails', {_id: itemBeingPassedId, itemName: nameToBeAppendedToURL})
....
Router.route('/items/:itemName', {
name: 'itemDetails',
data: function(){return Items.findOne(_id)
});
Whenever I try to do that, it says that _id is undefined. So basically, how can I pass a property to data without having it be a part of the URL and using this.params?
Is the question how to pass multiple parameters to Router.go? Just put all of them in the object for the second parameter:
Router.go('itemDetails', {_id: 'foo', '_itemId': bar});
Edit:
Ok, if you want to pass arbitrary values to the url, you can use query paramters:
Router.go('itemDetails', {itemName: 'foo'}, {query: 'id=bar'});
The id will still be in the url though, it will look like this:
http://example.com/items/foo?id=bar
And you can retrieve it like this:
Router.route('/items/:itemName', {
name: 'itemDetails',
data: function(){
return {
item: Items.findOne(this.params.query.id),
itemName: this.params.itemName
};
}
);
I have a problem with creating Ember objects from a JSON ajax data source. If I create the object the manual way, it works perfectly, and the view gets updated. If the data itself comes from a JSON ajax data call, however, it does not work. If I inspect the resulting objects, the Ember model objects does not get the correct getter and setter properties. Does anyone know why this happens?
App.AlbumView = Ember.View.extend({
templateName:'album',
albums:[],
getAll:function() {
var self = this;
//This works!
self.albums.push(App.Album.create({title: 'test', artist: 'test'}));
$.post('/Rest/list/album',null,function(data) {
$.each(data, function (index, item) {
//This does not work?!?
self.albums.push(App.Album.create(item));
});
}, 'json');
}
});
You should always use embers get('variableName') and set('variableName', newValue) methods when accessing instance variables of a view. Strange things tend to happen if you don't.