Just ran into an issue with some Web API and Angular 2 code I’ve been working on, and since there didn’t seem to be much info in the wild on the error I ran into, I figured I’d blog it, in case it might help someone else.
A Simple Demo of Web API and Angular 2?
Since I had the day off yesterday, I figured it might be a good day to jump in and start doing some work with Angular 2 (Hey, isn’t that what you do with your day off? Don’t judge!). Of course, I’d already run through a number of tutorials that dealt with hard-coded collections of data, so I figured it was time to build something that could retrieve data from an API.
My personal favorite tool for building APIs is ASP.NET Web API, and as a big fan of the OData stack and protocol, I went with building an API using the Nuget packages for OData v4. Since I’m way overdue for a new version of the Community Megaphone site, I figured that with that data, Web API and Angular 2 would be a great match.
If only it had been that simple…still, after a few hours of schema design, and getting all my packages and such lined up, I finally had a working API.
NOTE: If you’re using EF to map your relational data to your model(s), stick with populating any default values inside your code, rather than setting defaults at the database level. I spent way too long debugging an issue where I’d spent way too much time debugging an issue that arose because I’d set the [DatabaseGenerated(DatabaseGeneratedOption.Computed)] attribute on a column, not realizing that it would always use the column default, regardless of what I set in code.
Heroes, Events, Whatever
Next, I set about building the most barebones client I could, using the Angular 2 Tour of Heroes tutorial as a starting point.
One of the things I discovered along the way is that not every part of the Angular 2 API set is equally well documented. The tutorials, I’ve found to be pretty great overall. But once you move off of the main roads, you start running into a fair number of “Not Yet Documented” API methods, so if the code examples you see don’t quite match your scenario, you may find the way harder going.
Several errors I ran into while attempting a simple GET request to my Web API were relatively easy to solve with a quick search (note that you’ll need to view the browser console to see the errors):
- “Unexpected token <” : Needed to add a reference in my .html file to http.js (actually http.dev.js) from the bundles installed by npm.
- “No provider for Http!”: Needed to add an import for HTTP_PROVIDERS, and add that to my bootstrap call in boot.ts.
- “http.get(…).map is not a function”: Needed to add an import for ‘rxjs/add/operator/map’ to my component (where I was attempting to map the data returned from the api).
- “No ‘Access-Control-Allow-Origin’ header is present on the requested resource.”: Needed to set up my ASP.NET Web API to return the Access-Control-Allow-Origin header allowing the Angular site to access the API, since they were on different ports on localhost. This is pretty easy to do using the Microsoft.AspNet.WebApi.Cors Nuget package, which allows you to decorate your API Controller (or OData Controller) with the EnableCors metadata attribute. Just don’t forget to call config.EnableCors() in your WebApiConfig.Register method, too.
But the last error that I ran into was a bit of a head-scratcher.
Error trying to diff?
Yep. That’s a pretty obscure error message. After fixing all the other issues above, the last error message I got was:
“Error trying to diff ‘[object Object]'”
I had no clue what this was telling me, and there was very little information I could find via Bing (seriously, don’t even) for this error message, apart from an issue on the Github repo complaining that the error messages, including this one, weren’t descriptive enough. Well, I’m not going to argue with that.
The key piece is line 5/6, where the exception is thrown. Once I was able to locate this in the browser dev tools, I set a breakpoint, and ran through my code. When running with the hand-coded collection of events, it worked fine. But when working with the return value from the http.get call, it would throw. Thanks to the breakpoint, I could examine the collection in each case.
Turns out that the answer was quite simple. Instead of returning the array of objects I was expecting, the call to my OData API was returning a context object whose “value” property had the array I was looking for, because that’s how an OData controller in Web API works. So I changed my http.get call (which I was executing inside of the ngOnInit function) from:
With that change made, Angular was able to iterate over my data using the *ngFor syntax, and all was right with the world.
At least until the next exception.
The many small errors I ran into (some of which, admittedly, are a product of being new to Angular 2, and a little rusty on my mental JSON parsing) added up to it taking a pretty much full day to go from schema to API to working demo. I initially found this kind of frustrating, since I’d hoped to have it done faster. It’s not that Web API and Angular 2 don’t work well together, it’s more the small things that can trip you up when you jump from working with fake data to real APIs.
Compared to ASP.NET Web Forms (what Community Megaphone currently uses), a day to get a working databinding example seems like an eternity. But given the differences in architectural approach (for example, much of the day was spent on the API, not the client code), I’m fairly pleased with what I was able to get done in a day. As the rough edges are knocked off of Angular 2, and the documentation is further fleshed out, I’m sure it’ll get faster, too. And I hope to contribute some examples with Web API on the back end as I go along.
Once I have my simple demo a little more polished, I’ll do another write up showing the simplest end-to-end Web API and Angular 2 example I can put together.
Let me know if you have suggestions for other topics I should cover.