I'm setting up a SPA using Angular and Breeze. I've been following John Papa's hot-towel tutorial from plural site. I'm having a weird issue I think might be spawning from my metadata? But in the end, I'm not really sure....
First things first, my API is running off a LAMP stack, so I'm not using EF. I have created a Metadata endpoint that I think is giving me the correct structure I need. I'm using the breeze.angular.q.js to help my mappings from Q to $q
resource: api/v1/Metadata
{
"metadataVersion": "1.0.5",
"dataServices": [
{
"serviceName": "api/v1/",
"hasServerMetadata": true,
"jsonResultsAdapter": "webApi_default",
"useJsonp": false
}
],
"structuralTypes": [
{
"shortName": "tracks",
"namespace": "MyNamespace",
"dataProperties": [
{
"name": "id",
"nameOnServer": "id",
"maxLength": 36,
"validators": [],
"dataType": "Guid",
"isPartOfKey": true
},
{
"name": "title",
"nameOnServer": "title",
"maxLength": 255,
"validators": [],
"dataType": "String"
},
{
"name": "description",
"nameOnServer": "description",
"maxLength": 0,
"validators": [],
"dataType": "String"
}
]
}
]
}
example API return data looks like this:
resource: api/v1/tracks
{
"data": [
{
"id": "495f21d6-adfc-40b6-a41c-fc93d9275e24",
"title": "harum",
"description": "Error doloribus ipsam et sunt fugiat."
},
{
"id": "d7b141d2-6523-4777-8b5a-3d47cc23a0fe",
"title": "necessitatibus",
"description": "Voluptatem odit nulla maiores minima eius et."
}
],
"embeds": [
"courses"
]
}
Now w/ all my code, I'm actually returning correct data from my api. I've poured over examples from the breeze site as a few good tidbits I found here on SO (like this question and great answer from ward). Alas, no luck. Essentially whats happening is when I try to loop over my results, in my view model, that are returned from my breeze query, I get an angular error Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: t in vm.tracks, Duplicate key: object:00I
The call is happening in a function inside my datacontext. The data returned in my querySucceeded promise callback doesn't appear to be correctly bound.
datacontext.js
...
function getTrackPartials() {
...
return EntityQuery.from(entityNames.track)
.toType(entityNames.track)
.using(manager).execute()
.then(querySucceeded, _queryFailed);
function querySucceeded(data) {
console.log(data); // <-- Log out to see what is returned
tracks = data.results;
_areTracksLoaded(true)
log('Retrieved [Track Partials] from remote data source', tracks.length, true);
return tracks;
}
}
If I were to log this data out to the console, I get this (all the $$hashKey's are the same, and the id, title, description are all NULL. But I do get the correct number of results, and this isn't a coincidence - if I adjust the number of results I'm supposed to receive, it correctly matches every time).

Now, since my data comes back a little bit different - I used the Edmonds example and created a custom JsonResultsAdapter so I could "massage" the data. Its very rudimentary at the moment, as I'm just trying to get this working. Whats really throwing me off, is if I log out the node parameter from the visitNode function in the JsonResultsAdapter, it has the correct data....????
entityManagerFactory.js
(function () {
'use strict';
var serviceId = 'entityManagerFactory';
angular.module('app').factory(serviceId, ['config', emFactory]);
function emFactory(config) {
breeze.config.initializeAdapterInstance('modelLibrary', 'backingStore', true);
breeze.NamingConvention.camelCase.setAsDefault();
var serviceName = config.remoteServiceName;
var metadataStore = new breeze.MetadataStore();
var provider = {
metadataStore: metadataStore,
newManager: newManager
};
var jsonResultsAdapter = new breeze.JsonResultsAdapter({
name: "Tracks",
extractResults: function(json) {
console.log(json.results.data); // <-- Log out to see what is returned
return json.results.data;
},
visitNode: function(node, mappingContext, nodeContext) {
console.log(node); // <-- Log out to see what is returned
return {
entityType: 'tracks',
nodeId: node.id
};
}
});
var dataService = new breeze.DataService({
serviceName: serviceName,
jsonResultsAdapter: jsonResultsAdapter
});
return provider;
function newManager() {
var mgr = new breeze.EntityManager({
dataService: dataService,
metadataStore: metadataStore
});
return mgr
}
}
})();
Here is my return value from my JsonResultsAdapter::extractResults function

Here is a node from my JsonResultsAdapter::visitNode function

Any help would be appreciated. Like I said, I'm not really sure where the error is happening at? But if i had to guess, I would say there is some disconnect between my EntityQuery using my manager and the JsonResultsAdapter, that might be caused by bad metadata I've generated.
** UPDATE **
So I walked through the breeze code to figure out where I'm loosing my data and was able to figure out whats going on and a way to fix it. However, I'm not sure if thats the best way to actually handle this.
I should mention, I used bower to install breeze - and by doing such I went the bower-breeze-angular git://github.com/eggers/bower-breeze-angular.git package and not the default breeze breeze git://github.com/IdeaBlade/Breeze.git which is bloated with examples and other data I wasn't keen on packing into my repo.
In breeze, after my JsonResultsAdapter::visitnode callback has returned, it needs to "merge" my data, the problem I have is the entityKey that is returned from my node is not matching up. This is because the rawValueFn from my mappingContext is looking for nameOnServer, which I thought I set in my metadata from my server - but somehow when I log out my dataproperty it has changed from what I set.
This is one dp logged out, if you look back up at the top in my Metadata resource call, I specifically set this to "id". How did this change to Id? Thats whats causing my headache!

I can get around this by updating my rawValueFn function on my mappingContext in my JsonResultsAdapter and everything will work - but this feels like a "hack". I've also tried playing w/ the "NamingConvention" but that doesn't seem to work either.
Here is my updated JsonFactory that makes it work
var jsonResultsAdapter = new breeze.JsonResultsAdapter({
name: "Tracks",
extractResults: function(json) {
return json.results.data;
},
visitNode: function(node, mappingContext, nodeContext) {
// Had to adjust this so it would lowercase and correctly match
mappingContext.rawValueFn = function(rawEntity, dp) {
name = dp.name;
name.substring(0, 1).toLowerCase() + name.substring(1);
return rawEntity[name];
}
return {
entityType: 'tracks'
};
}
});
extractResultsfunction. Make sure thatjson.results.datais what you think it is. TheextractResultsfunction should return an array. - Steve SchmittextractResultsfunction. Its down near the bottom of the post. I do in fact return an array of two objects, and right below that image you can see when IvisitNodeI'm getting passed the correct object with the correct data...not sure why its not merging correctly when returned from that function? - veilig