Development Central

Bill Sorensen's software development blog

AngularJS - is it worth it?

Over the last year and a half, I've been doing a lot of work on a single-page app (SPA) built with AngularJS (a.k.a. Angular 1). I didn't write the initial version, but I was part of a team developing new features for it.

Caveat: I don't consider myself an AngularJS expert.

That said, I plan to steer clear of AngularJS on my next project.

Why not AngularJS?

1. Testing.

There aren't a lot of options for doing end-to-end testing with AngularJS. The official recommendation is Protractor, which is built on Selenium. Protractor has its own learning curve, particularly in the way it hides asynchronous calls with "magic." The documentation recommends against using it with PhantomJS, so it's relatively slow.

We disabled all of our Protractor tests. Several team members (I was one) spent multiple days over the course of weeks trying to get the tests to run reliably on our build server. We gave up in frustration.

We switched to unit tests (Jasmine + Karma). These work reliably with PhantomJS. They aren't easy to write, though. Every component requires a different type of test. Want to test a controller? Look up how to do it. A directive? That's different. A filter? Different still. A service? Different. A directive with a template? Different - install and configure a library to handle the template cache.

http://angulartestingquickstart.com/ is helpful for untangling the complexity.

Recently Nightwatch.js appeared on the scene; I have heard it is possible to test AngularJS with this powerful tool.

2. Framework.

AngularJS is a framework, not a library. It attempts to provide solutions to nearly every aspect of building a web application. One cost of this approach is complexity. Tutorials may give developers the feeling that AngularJS is simple; it's not. Even after using it daily for months, I learned important facts on a daily basis.

Some aspects are needlessly complex. For example, nearly every time I use ngOptions I have to look up the syntax. One would think that creating a drop-down list from an array of objects and binding it to an identifier would be a common use case.

I distrust frameworks in general; they tend to be less flexible than building an application using focused libraries. If a God Class violates the Single Responsibility Principle, doesn't a framework suffer from the same issues?

3. Documentation.

I referenced the AngularJS documentation frequently during development. Then I'd go out on Stack Overflow or Google and try to find a clear explanation. The official documentation appears to have been written as a technical reference, and I personally find it difficult to follow.

4. Fragile.

I lost count of the number of times I forgot that myName becomes my-name in AngularJS. Except with filters.

Moving markup that worked perfectly into a template on a directive caused display issues that we never did resolve.

There are a number of other "gotchas" in the AngularJS world. Mistakes (such as typos) tend to fail silently. Part of this is the nature of JavaScript and dynamic languages in general, but it doesn't make things any less painful.

Here's one that took some time to track down: Angular $http calling success on 404

5. Short-lived.

Angular 2 is here. Much of what I've learned with Angular 1 will be obsolete eventually. How much study time do I want to spend on this? How long will it be around?

If you want to use AngularJS...

Follow the AngularJS style guide by John Papa. The guide is endorsed by the Angular team. If we had started with this, development would have been much less painful.

Avoid $rootScope whenever possible. Think of it like using global variables. Leverage services instead.

Use UI-Router. Don't even start with the AngularJS router. It will paint you into a corner of workarounds and hacks. This article opened my eyes.

What should I use instead?

I don't know. I like React's philosophy, but I'm still a beginner with that. I haven't tried other SPA frameworks (including Angular 2). Consider if you really need a SPA; would ASP.NET MVC plus a bit of Knockout do the job?

Whatever you choose, look for simplicity, testability, and clear documentation. Don't be sucked in by "look how fast you can build a to-do list!" samples.

Knockout.js learning tips

It's been awhile. I'm doing web development at my new job, so this and future posts may focus on that.

I used Knockout.js recently, and I'll share a few tips that I learned the hard way.

1. Watch the parentheses.

Remember that ko.observable objects are functions. If you're binding to the property and nothing else, you can omit the parentheses. If an expression is involved, you'll generally need them. If in doubt, include them.

The easiest way to avoid the need for parentheses is to put as much logic as possible in the view model. This minimizes expressions in the markup.

2. Be careful mixing server-side and client-side code.

This was on an ASP.NET MVC site, and we had both view engine markup and Knockout bindings originally. This proved difficult to reason about. While it's definitely possible, remember that Knockout is only going to see the page once it's rendered client-side.

3. Avoid comment (containerless) Knockout bindings.

I found that these did not seem to play well with templates, and they may not work with IE8.

4. Don't mix if bindings with other bindings.

I combined an if and a text binding in the same element. This resulted in an error of "You cannot use these bindings together on the same element." One solution is to use another span or div.

5. Don't mix if bindings with CSS classes or other markup.

In general, use if bindings with a div that has no classes, etc. The issue is when the binding is falsy, the element will still render - it'll just be empty. Styles can cause undesired visual artifacts.

6. Be cautious if mixing jQuery and Knockout.

We were using jQuery to wire up form submission to a class that was in a Knockout foreach. It wasn't working. The fix was to switch to a Knockout submit binding. Knockout can work fine with jQuery in most cases, though.

7. Remember what binding context you're in.

Especially with foreach, it's easy to forget and bind to the current item when you meant to bind to $parent or $root. The Knockoutjs context debugger (search the Chrome store) can help.

8. Don't try to do progressive enhancement.

If the client doesn't have JavaScript enabled, skip the whole Knockout section. See http://stackoverflow.com/questions/8961073/progressive-enhancement-with-knockoutjs.

9. Encode where appropriate.

It appears that attr bindings don't encode anything (although the text binding does). This is particularly relevant when binding to the href attribute of an anchor.

10. Use foreach on the parent element.

The documentation is clear on this, but it's easy to misread. The result of binding to a child is typically missing closing tags.

I like Knockout, and the learning curve isn't very steep. Keep it simple and it seems to work well.

That's not TDD

I'm currently reading Pro ASP.NET MVC 4 as part of learning web development. So far it's been a good tutorial, but I take issue with the author's coverage of Test-Driven Development (TDD).

I applaud the coverage of unit testing, dependency injection, mocking, separation of concerns, and other best practices. It's the details that bother me.

In chapter 6, the author says he will follow the TDD approach in a unit test example. He starts out by writing the system under test and having it throw a NotImplementedException. Bzzt. Write the test first. There is a reason for this: the tests guide your design. You write the API you want to consume.

Next, he writes the unit tests. All of them. In TDD, you write one test, see it fail, write code to get it to pass, refactor, and then repeat. While there's no law that unit tests must be written in that fashion, changing the process means you're not doing TDD. If you don't want to do TDD, fine, but don't call it that.

Several of the tests violate the guideline of one logical assert per test. The only justification I can see is brevity. This comes at a cost: when the first assert fails, subsequent asserts are not run. In addition, messages have to be added to the asserts for clarity; it's much better to include this information in the test method name.

// These should be three separate test methods
Assert.AreEqual(5, TenDollarDiscount, "$10 discount is wrong");
Assert.AreEqual(95, HundredDollarDiscount, "$100 discount is wrong");
Assert.AreEqual(45, FiftyDollarDiscount, "$50 discount is wrong");

The author encourages use of the ExpectedExceptionAttribute. This is far too coarse-grained, and is one reason for avoiding MSTest (which the book also promotes).

I encourage everyone to go to the experts to learn TDD and unit testing. Two good sources are Test Driven Development: By Example and The Art of Unit Testing.

The book's coverage of dependency injection is incomplete as well; the authoritative work is Dependency Injection in .NET.

I'm being harsh for a book on ASP.NET MVC. If you're going to cover these topics in detail, though, please get the details right.