Code walk-through of podrad.io
I listen to a lot of podcasts, and have been disappointed that there wasn’t something in the Chrome web store for this already. While I know that there are a couple of web-app clients out there, I haven’t found one so far that I’ve really liked. So, I decided to build my own.
After about 24 hours of work, I got to a point where I thought that I could push it up to my server without getting hate-mail. I deployed it and it’s going ok so far. I’ve been able to use it a little bit to listen to a couple of episodes, and it seems to be working for me.
You’ll need to have a few things installed before getting started:
Follow the instructions on the Yeoman project site for getting the prerequisites installed.
Generating the project
Again, check out the Yeoman project site, but basically, all you need to do is
yo angular:app inside a directory to have it generate a new project for you. However, for the purposes of this post, just go ahead and clone the podrad.io github repo. (At the time of this writing, the current commit is 10f18e9.)
Onto the code!
At a very high level, there will be a couple of files that lend the most structure to the project, those are the index.html and app.js files.
The index.html file is just what you’d expect it to be, it’s the starting point of the app. Here’s the index.html file from podrad.io with all of the auto-generated code stripped out, and just the important changes left in:
The first thing that you’ll notice is that I added a CDN source for Bootstrap and Font Awesome. I did this for a couple of reasons. First, you sort of get Bootstrap by default when you generate an AngularJS project with Yeoman and accept all the defaults. It’s also quite useful for providing some basic styling, especially if you’re like me and are allergic to CSS (not that I dislike it, I’m just bad at it). Font Awesome is used because it provides vector images, and I have a really high res screen. CDNs are used for a couple reasons, first, it’s better for client caching. Second, because I couldn’t figure out how to properly compile my scss files, and just wanted to get things moving along.
Next in the index file are a couple of AngularJS directives, ng-app and ng-view. These tell AngularJS about the scope, and what it’s supposed to control, ng-view is controlled by the routing in app.js.
You can also see that I added a few extra libraries to help me along. One for angular-bootstrap, which provides the accordion effect on the single pod view. Another called X2JS, which parses XML into JSON format. And, finally, an MD5 library, so that I can easily make hashes of things. That’s basically it for the index.
Moving on to app.js, mine is very simple, there’s very little routing that needs to happen here (as of now). Basically, we just want to use the main view for everything, and while it’s defining a controller, that controller doesn’t actually get used. That’s an error, and I’ll probably refactor that out.
The main.html file simply pulls in additional views and defines controllers for those views. It also declares that the second view, onepod.html is not visible if there is not a podId in the scope.
In pods.html, we can see a couple of input fields being defined and bound to models in the scope. When the button is pressed, the addPod function in the controller is called, defined by
ng-submit. Then, in the next chunk, we display all of the podcasts that the user has subscribed to, and provide a couple of simple actions that the user can take (either select or delete).
To generate the list of podcasts, use
ng-repeat="item in Array". Filtering the list is easy too, I’m filtering based on the contents of the search box, and also sorting the list on the fly.
In the controller, all of the work is done to populate those models with content, or handle the content coming in from the view when the user wants to add a new podcast. We also provide a few default podcasts for casual viewers, just so that they have something to look at if they find the site and are utterly confused.
One more thing to note here is that I’m using the LocalStorage HTML5 API to persist data. I reviewed and implemented about a half dozen different solutions before choosing this one. I found issues with nearly all of the other solutions that I had tried, most of them being either lack of documentation, lack of development or poor browser support. While it may not scale well, LocalStorage was what I needed to get me up and running.
When the user selects a podcast in the pods view, the onepod view comes alive and displays some basic information about the podcast, along with a handful of the most recent episodes. Users can change the number of episodes they see by entering a number in the input box and clicking the reload button.
I mentioned that I was using the accordion element from angular-bootstrap, and you can see that here. What’s also done here is the parsing of the podcast feed data. Since I’m able to iterate over an array, it is easy to simply pull out the bits that need to be displayed from the JSON provided. This means that I don’t need to write a separate parser, I can do that straight in the view.
When I started out trying to parse the feeds, I was making the request straight to the podcast feed url, and parsing it from there. This worked fine for the first feed that I tried, since the HTTP response headers were set properly, allowing cross-site requests. Another provider, however, had failed to set that header, and my requests were failing. After searching for a bit, I landed on a service that Google offers to make this sort of request easy, and AngularJS provides some hooks to make it even easier. The strategy is that you call the Google service with your feed location as an encoded uri argument, and also provide a standard callback to Google. In AngularJS, you can use the JSONP HTTP request, and everything works well.
One cool feature about the Google service is that it can translate the XML response into JSON for you. However, podcasts contain certain elements that the Google service didn’t handle, namely their media. So, I had to get the results as XML and then do the conversion with X2JS.
I was having an issue trying to compile this app for deployment, something about
.jshintrc getting an end of file error. The problem was that Yeoman does not to auto-generate a .jshintrc, even though Grunt requires that file for compilation. I eventually got a working .jshintrc built, however it doesn’t seem to be correct, or it might be ok, and my Grunt config file is screwed up. The issue that I’m seeing is that my scss stylesheets are not being compiled into CSS for deployment.
I think that about covers it, at least for the code aspect. I’m planning to continue building out this project, hopefully as I discover issues as I use it. Overall, I really enjoyed building this, and while there were some pain points along the way, I’ll be using AngularJS again in the future.