A continuing series about how the language you love is, in fact, terrible.
Enough with softball topics like politics, education reform and unionism. Lets get to something really controversial: JavaScript Frameworks. (And yes, I realize that Angular is a framework not a language, but I am covering it under the "Why I Hate Your Favorite Programming Language" section because bite me you pedantic reader, thats why.)
AngularJS has a lot going for it, but also a lot working against it. First, and this is entirely subjective and therefore is more impressive and true than anything else I will have to say on the matter of AngularJS, it feels very ad-hoc, with lots of different ways to do things. Now, I am a fan of different ways to do things. But there I also think that, when looking at a framework, the framework should have an opinion. It should make you think about why the opinion exists and thus make you think about why you are rejecting that opinion. Angular doesn't really do this.
You can structure your code in almost any way want. There are suggestions, but nothing that requires you to think about why you aren't using the preferred means. There is a great deal of overlap between the purpose of controllers and directives. When to use which is largely an art, with very little provided in the way of guidance. One project may handle certain tasks in one type of code; another may handle that same task in a different type of code. One project may make extensive use of directives with their own controllers; another may lump all of the controlling code into one set of controllers. You can indicate directives in at least tow ways, with not real explanation for when to use which. Even small things have a great deal of overlap: the differences between services, factories and providers are extremely small and not well defined. There may be -- likely are -- reasons for those differences, but they aren't entirely clear. It can feel like differences exist for no tother reason than to support one style of coding or another. That way leads to code that can be very hard to maintain. A new person, even one with Angular experience, has to learn about the particular flavor of Angular practiced in your shop.
The $scope variable is another flavor of this problem. $scope can be global cross the entire application, or $scope can be limited at a directive level. Inside the directive, $scope can be completed isolated or only partially isolated. There is no 100% clear way to know how isolated the $scope in a given directive is without inspecting the code. This can lead to situations where the values in an application change in unclear or contradictory ways. That, needless to say, is insane.
Angular also works against modularity. Angular provides no means of invoking a module directly. There are two primary means of invoking directives: triggering a change when a data value in another module changes or reacting to an event triggered by another module. Both of those methods mean tying the module to the scope of a another directive and/or the overarching application. In the first case, there must be a two way binding between the parent scope and the directive scope in the form of a watched variable. The directive instructs the angular application to watch for changes in the value of that variable and to call a function defined by the directive on said change. The directive is essentially told to invoke itself, once it notices that a variable in another directive or the parent application has changed. This, of course, tightly coupes that module to the directive or application that holds the variable it needs. You could use events, but that doesn't help in all cases.
When the directives are siblings -- two directives at the same level of an application -- they must use the parent scope in order to trigger the events. So in order to communicate between sibling modules, the parent scope must be invoked, and both module must agree at the time of writing what the name of the event will be and what data will be passed. Again, this essentially destroys module independence in a way that, for example, allowing modules to be invoked via direct calls, would not.
And Angular has performance problems. AngularJS uses what they refer to as a digest loop for maintaining the two way binding in applications. Essentially, this is simply a loop that checks the sate of every single item that has a data binding associated with it. If it finds a change, it then calls the code listening for such changes. This system has significant problems for performance. First, the digest loop processes inspect the actual DOM, a process that is in and of itself relatively expensive. The results of that inspection can trigger hundreds or even thousands of functions, depending on how much data is bound in the application. These in turn result in even more changes to the DOM, again a relatively expensive operation. As a result, AngularJS has known performance issues. In fact, the general; consensus seems to be not to have more than 2000 data bound elements in an application. Given the broad nature of a SPA, given that the constraint is about data bindings in an application, not just in the display, given that every piece of data constitutes a data binding, and given that in app expression (i.e. a + b) can represent multiple bindings, it is easy to see why AngularJS applications can become very slow very quickly.
Now, all of these problems and more are going to be fixed, but that creates its on issue. AngularJs 2.0 is likely going to be a complete rewrite, making the framework more performant, getting rid of two way bindings, getting rid of scope and getting rid of controllers, leaving just directives in that space. But there is as of now no expectation that a clean upgrade path will exists between current Angular and Angular 2.0. So you can write a ton of code today and find out tomorrow that none of works with Angular 2.0, leaving stuck with either sub-standard code or a large job ahead of you to upgrade your code base. Either prospect is not comforting.