Hacker News new | comments | ask | show | jobs | submit login
Mistakes AngularJS Developers Make (airpair.com)
152 points by davidkellis on Oct 3, 2014 | hide | past | web | favorite | 73 comments

This is possibly better called a list of framework design mistakes made by the AngularJS team.

#3: The fact that the default dependency injection mechanism breaks when minified means it should never have been included. If this mechanism sneaks in anywhere in your app (and there are plenty of third party libs that use it), it wont start breaking until you bundle and minify the code, at which point you may already be in production.

#5: The service/factory distinction adds a conceptual complexity that is completely unnecessary, as demonstrated in this blog post.

#7: Having too many watchers will slow your app down to a crawl on a desktop machine, and be even worse on a phone. This happens more easily than you'd think and it's fundamentally caused by Angulars reliance on its digest loop. This loop is the essence of how Angular works, and it means that there are tons of applications that cannot ever run efficiently if they're built on Angular.

8#: Because of the way their $scope system works, it is actually literally impossible to tell what the the meaning of ng-model="foo" is by reading the program. It may in fact depend on the order in which the user interacts with elements of the web page. Consider this example: http://jsfiddle.net/7kkxLkxh/ What foo binds to is dependent on which input you type into first. Yes, there are ways to avoid this, but it should have been avoided by either disallowing this construct, or requiring foo to be declared before it is used. (If you miss a var in JS, you get a global - that turned out to be bad language design. Removing the var keyword and effectively deciding on variable scope at runtime is certainly a much worse language design.)

After working on a large Angular project, it is clear to me that there are a lot of ideas in there that are frankly not good. It's sad that there seems to be very little discussion anywhere about the downsides of the various frameworks that are out there.

>#3: The fact that the default dependency injection mechanism breaks when minified means it should never have been included. If this mechanism sneaks in anywhere in your app (and there are plenty of third party libs that use it), it wont start breaking until you bundle and minify the code, at which point you may already be in production.

I think the real mistake in that scenario is not minifying the code until "in production". You should minify your code from the day one.

Although I agree that the "not safe for minifying" mechanism shouldn't have been included in AngularJS.

On the other (other) hand, it's pretty abhorrent that minifying is something that developers need to actually place any thought into..

> I think the real mistake in that scenario is not minifying the code until "in production". You should minify your code from the day one.

How are you debugging minified code?

That's a big problem. Firefox and Chrome these days support debugging source mapped files. However, I'm running through two source map steps: TypeScript to JavaScript and JavaScript to minified JavaScript. For whatever reason the browser debuggers don't work that well with this setup. They're buggy and make the browser very slow. I often do have to resort to "console.log" debugging. On the plus side I'm always running code like it's run in "production", which will make it easier to catch any bugs resulting from minifying.

I use Webpack with a similar setup (CoffeeScript->JavaScript->Minified) and I didn't notice any issues. Source maps stop working when using Webpack's hot code reloading, but it's a price I'm willing to pay.


I concur: this is a good list of issues. One additional thing I found was composition/inheritance of controllers with $injector.invoke.. breaks in minified code because of issues similar to #3.

I'm fairly certain that the majority of Angular developers would tell you that controller inheritance is an anti-pattern. This is what services are for. The furthest I go is using services as controller mixins, eg. $scope.commonTools = CommonToolsMixinService.call($scope);

Wow, bang on. I completely agree with all of this, and it has been my experience working on a large-ish Angular project as well.

As for #3, that's what "testing" (or "acceptance", if automated tests are run not in "development" phase, but at a separate "testing" stage) in development→testing→production sequence is for. It's virtually the same setup, running the code intended for production, but not visible to general audience.

There are angular specific minifiers dealing just fine with #3. It's more of a toolchain problem.

For #5 the real deal is the provider and everything else is syntactic sugar on top of it. Once you have a very large application you start to appreciate the distinction. The naming of concepts is awful though; could have been much more clearer.

>There are angular specific minifiers dealing just fine with #3. It's more of a toolchain problem.

I'm sorry to say it's pretty "retarded" to add in framework-specific minifiers to the build process. I don't want to tweak 10 different minifiers if I'm using 10 different libraries in my application.

Sorry, I was wrong at the first time it's not actually the minifier that deals with injection. It's a preprocessor called ng-min. You can use any minifier after that. It's been a while since I touched my grunt file. I stand corrected.

Grouping files by feature has made navigating and reasoning about a codebase so much easier. I've even found myself grouping stylesheets & test files alongside templates and app code, it really helps enforce the idea of composing smaller apps together.

Google has released a recommended app structure similar to this as well [0], though I haven't actually seen it being used too often in the projects I've come across.

[0] - https://docs.google.com/document/d/1XXMvReO8-Awi1EZXAXS4PzDz...

It doesn't necessarily make sense when you have a team working on different things, front-end devs working on the templates and back-end devs working on services, etc.

Furthermore most likely you will be sharing many partial templates or widgets between different views and sections in your app. So those have to go in some sort of common views folder, losing much of your grouping.

I wouldn't say either way is right or wrong, it just depends on your workflow, size of the app and size of the team working on it.

Why doesn't the widget go in its own folder?

or in their own module, pulled into the project with bower

I understand the grouping of the JS files by feature. But I have a hard time including the partials/HTMLs with that. Because when building (concat, minify etc) the project, they will then need to be copied around and all template-references updated.

It's easier instead to just dump all of them into one place and copy that folder, no need to update references etc. then.

Any ideas on how to solve this?

I use browserify and the brfs transform [0], but I know a lot of folks don't like mixing up other module systems with angular. Your approach is probably the easiest tbh.

There might be some way to load them into $templateCache on build, but that would kill lazy loading

[0] - https://github.com/substack/brfs

I use this Yeoman generator that uses a very good structure like recommended above https://github.com/cgross/generator-cg-angular

We're using the same generator for a large government app. "Widget" partials are stashed in their respective folders under /widget while /services, /directives, and so on are contained in the root folder. HTML/CSS/JS stored in each folder. It's all super organized and modular IMO. Of course it helps to be working with the creator of said generator.

Packaging up the templates? (e.g. https://www.npmjs.org/package/grunt-angular-templates)

Yeah I think that's a fair point. It's easy enough to write a grunt or gulp task that does the copy. Not too hard to copy all the *.html files from the app directory. The time savings for development are worth it to me

These types of articles are great, but too frequently I see phrases such as, "It makes testing much simpler" sprinkled throughout them without any justification. These claims are generally not untrue, but it seems like it would be appropriate to demonstrate scenarios of the unit and e2e tests actually being affected by decisions in the application code.

Apologies if this comes across as unsolicited advice; but if you really struggle with this, your software dev fundamentals are weak. Before learning frameworks, you should focus on concepts like decoupling, single responsibility, etc...

upvoted because software developers who avoid heavily testing their code, and the use of code analysis tools, are often surprised once they start testing and analyzing their code.

It's a good article, but a good portion of these aren't "mistakes" but rather "opinions".

#1 isn't a "mistake" it's a preference, really. Also, I'm really not sure about putting the template files in the directory with the other files. Then I guess I'd have to add something to my build script to copy them to a hosted directory?

#3.1 IMO, Is actually more of a mistake. Imagine you have 3-4 modules, which one do you define that underscore service in? Oops, better make a 5th "common" module, I guess. Just to include something that you could have injected with $window. Notably, the underscore service is also missing that it should be injecting $window. How is he going to test that? Scrap the whole underscore service, and just inject $window where you need something from the global scope.

#9.1 Protractor - Protractor is great. But it's a little hefty for true "unit" tests. Jasmine with `angular-mocks` works a little better for unit testing Controllers and Services, where Protractor is better at testing directives and full end-to-end tests.

and one thing I'd add to this list:

#11 - Overly "DRY" Jasmine tests.

A lot of Angular developers I know are way too fond of drying up their Jasmine tests with nested describe() blocks and lots of beforeEach() clauses. Tests should really be "DAMP" not "DRY".

#1 -- Separating by concerns is much preferable to me, organizing by controllers/directives/services/... is a separation of technology, not the high level concept. There's a great React video on this concept, as well as some other good stuff! [1]

[1] https://www.youtube.com/watch?v=x7cQ3mrcKaY

> #11 - Overly "DRY" Jasmine tests.

Agreed. I've gone down that rabbit-hole myself more times than I'd like to admit. It's very easy to waste time DRYing up your test suite with beforeEach calls; there is a kind of nagging temptation to reduce every spec down to the minimum number of lines required, and it's a pretty cathartic way to avoid doing real work (especially if you're taking a TDD approach), but ultimately, you just end up constricting your test suite in a way that wastes even more time when the day comes that you have to adjust a few specs in a manner which is incompatible with the beforeEach templates you've established for the suite.

Nowadays, as a rule of thumb, if the tests are already passing, I forbid myself from refactoring any specs that test an unshipped feature. Maybe I should be a full time test engineer because it's really not a very healthy behavior. :)

Been there as well. I call it being clever with your test suites. Figuring out a way to hack your test suites so you can write as little code as you can. Except your test suite ends up unreadable because of all the magic you made for... DRY.

Yup, per Sandi Metz:


I've also heard it expressed as "the pain caused by code duplication is nothing compared to the pain caused by wrong abstractions".

do you mind explaining the acronyms? DRY and DAMP sound more like a degree of wetness unless a definition is provided.


as an industry, we really love those backronyms

In this context, how about something like this?

"Don't repeat yourself (As Much as Possible)"

I disagree with #10 (Using jQuery). Why would I rewrite perfectly good widgets (jQuery plugins) in Angular (or any other library) when I can create a thin Angular wrapper around them? Because the philosophy is different? That's hogwash. At the end of the day, you're still manipulating HTML no matter what library you use or don't. This is pure nonsense, not to mention impractical, and bad advice.

I think a better way to phrase #10 is "Don't do DOM manipulation anywhere other than a directive."

If there's a complex jQuery widget (say, a date selector) that works well for you, then wrap it in a simple directive and be done.

I've done this a number of times with no problem.

This is the way to go. Sometimes you just have to build your own from scratch.

The rule I've found works best is to avoid jQuery until absolutely necessary, and definitely not until you have fully learned Angular. At first I would find myself reaching for jQuery for all kinds of things that ended up just being a couple of lines in Angular. It has actually been a while now since I've actually touched it, and thinking about it, I don't think I've even included it in my latest project. No doubt I will end up needed to pull in some third party control at some point which will necessitate I include it, but really I haven't missed it.

Yep, I agree with this one completely. I'd go even further and say the opposite is true. I see a lot of angular projects needlessly using $scope for handling pure UI rendering issues which do not need to be hooked into the $digest cycle, which can really have an impact on performance as it adds unnecessary watchers. Need to adjust the layout of a UI element? Creating a scope property and binding to ng-style is not the way to go.

Point 3 about "Dependency injection" is often not needed. Just use ng-annotate before minify/uglify, and it will convert the code to the array syntax for you. It handles most of the cases, leaving your code without the ugly array stuff.

It doesn't work for everything, though, so suddenly it blows up. So if you want to be 100% sure your code works as intended do it manually.

It sounds like you talked yourself into doing it the way the author in the article suggests halfway through your post :)

Well, yeah, deployed a minified angular app yesterday, and ng-annotate handled almost everything except a controller that's initialized a custom way. So it broke. :(

That occasionally happens when you do things in non-standard ways, although ng-annotate has become quite smart lately and can now follow references and more. Did you use the latest version (also feel free to open an issue)? ng-strict-di in AngularJS 1.3 will greatly improve the error messages for these situations.

I haven't found anything that doesn't work either automagically or with explicit annotations[0].

[0]: https://github.com/olov/ng-annotate#explicit-annotations

ng-annotate works just fine with regular angular controllers/filters/providers/services/etc. definitions, but I would not trust it for special injections found in such stuff such as $state definitions with resolves and maybe $get with the factory returned by a provider.

it can certainly not support everything, partly because of limitations imposed by static analysis, buy you may be surprised with what it can handle.

Also: For situations where you don't know whether ng-annotate will detect a form or not (assuming you already use ng-annotate for your project), you can use explicitly use /* @ngInject */ or ngInject() to avoid stuttering the array yourself.

This is all really great advice. The only thing I would add is that the number of watches is important, but the type of watch (reference vs value) is also important.

I have seen people make Angular charts, and they use deep value type watches that kill performance. It is fine for angular to watch a massive array with time series data, but use a reference watch, and swap out the array when the data changes.

People talk about the scalability of Angular's dirty checking, but in my experience the number of watches should be roughly the number of interactive elements in the user interface. Since there are limits to how much interface a human brain can process, the theoretical problems of dirty checking are not real problems in practice. If you have more than 2000 interactive elements on a page, then you probably have a different problem, not an angular problem.

Hey - #8 regarding "consulting the prototype chain if the value is an object" is something of an oversimplification, and slightly incorrect. The prototype chain is more or less always consulted because that is the only way to evaluate the property on a child scope.

What is happening in the first example is that the ng-bind directive is creating a new entry on the child scope, called "user", which overwrites the reference to the property defined in the parent scope. When you ng-bind an attribute of an object, your child scope is not changing the reference to the original object, so both scopes will show updates.

Example fiddle: http://jsfiddle.net/02f4o7u9/1/

Yeah I agree it's an oversimplification. It's tough when writing a piece like this where a deep level of language knowledge is required to make a clear explanation. I tried to lead people toward the cause without getting bogged down in too many details.

Thanks for the example!

The modules one isn't necessarily true. I'm seeing a lot of Angular devs keep things under one module (esp for libraries). I don't think the value of multiple modules has really been realized. Exs: we do this for Angular Material and Ionic.

Hey I'm the author of the article. I created a library of modules that were reused across single page apps. For individual libraries I agree it doesn't make sense, but not enough people take advantage of the module system

At the moment breaking a monolithic app into modules has little benefit, but is theoretically a beneficial practice to take advantage of future Angular features. I'm speculating but my guess would be some sort of lazy-loading system will be implemented.

I can't find the exact article I read where the authors discussed this, but I did find a presentation by Brian Ford[1] that touches on the subject.


On point 1: "AngularJS is, for lack of a better term, an MVC framework."

It is not an MVC framework. The angular team even describes it as an MVW framework (the W stands for Whatever).

Angular on its own does not provide enough structure to work on large projects. It requires a better structure than using directories called 'controllers', 'services' and 'directives'. These are just angular concepts that do not translate well into understanding of what functionality should go in each directory. If you've worked on a larger project, you may have already seen this problem.

Angular documentation is not very helpful in pushing this point to the developer, however. It recommends[1] the use of 'services' as the place to do all business logic, which causes many projects to put most of the code in the 'services' directory.

To work on larger projects, you should create the MVC structure to fill in the gaps that angular does not provide. Creating top level folders such as: Models, View Controllers, Controllers, Endpoints (server communication). And using services, not as a top level folder, but as a way to instantiate components.

[1] https://docs.angularjs.org/guide/concepts

I've never understood the pattern of separating out services, controllers and directives into different modules. Your post controller is probably going to need your post service, so why put them in separate modules? Makes more sense to have a post module with your WYSIWYG directive, post service, post controller, etc.

Every time I see it I just wonder aloud, what problem did you think this solved?

I think it was such a ubiquitous pattern in tutorials and apps early on that it became a norm people didn't question. As the author mentioned, it isn't really something that matters too much until you really start to build a complex app.

In my own experience, I've found taking such a modular approach too early on can actually hinder productivity as I find myself spending too much time on boilerplate and figuring out if, for example, this service REALLY belongs with feature X or feature Y.

I think modules become also really powerful to build and/or extract the framework that your app needs. In other words, distilling your framework out of your app.

I look at them as a way to extract those building blocks that support the business logic in your app such as a data store or driving app state through routes (whether you roll out your own or use third party libraries). Even if you rely on third party libraries to fill those gaps, I like having my controllers/services/directives not to depend directly on them. For example, in the last year I've transitioned from ng-routes, to ui-router and finally settled with dotJEM/angular-routing [1]. Something similar happened when using $resource, then switched to Restangular, to finally use a thin data store on top of $http [2].

Having that code in a separate layer helped identifying service boundaries within the app and made it easier to replace them when needed.

[1] https://github.com/dotJEM/angular-routing

[2] Something similar to the following implementation: https://github.com/vicentereig/hola-apps/blob/master/app/ass...

9: There are no excuses for not testing an AngularJS app, I have never actually written unit or integration tests.

The author mentions nothing of protractor's use of selenium webdriver and how it does blackbox testing of your app. There is no mention of Karma interacting with your app's code for integration tests, as well. They talk about unit tests under the "Protractor" header, but integration tests can be run both by Karma and Protractor.

9.2: "Once integration tests have been written using Protractor?" Karma and Protractor are both test runners that support tests written for Jasmine (among other various JS testing frameworks). Perhaps the author meant to say "Once integration tests have been written using Jasmine."

Also, I fail to see how, "Waiting for tests to run, especially integration tests, can be frustrating for developers" has anything to do with Karma.

The rest of the stuff was useful, but the author clearly has never even coherently used these testing tools.

That first (and, to the extent that the author relates the two, second) point feels pretty dubious. I imagine most adherents of the "directory by feature" camp come from the Django-like world, while "directory by type" campers have Rails-like backgrounds. I haven't found a concrete, objective reason to use one over the other. I tend to find a hybrid, "directory by feature" organization mirrored in each of the MVC roles to be the best of both: easy to find all views or models at once and therefore easy to spot and combine functionality across similar models or views, but also easy to find all files related to a feature via grep/fuzzy searches/navigating through the same directory names from different starting points.

I think what you describe is a great approach.

The reason I claimed it was a mistake for angularJS devs was that angular seed and yeoman lead to doing directory by type, which I think becomes a burden as an app scales.

I tend to do it by application domain, not necessarily feature or type. Domain usually encapsulates the things that need to work together and provides clear lines for SOC.

I think this the first Angular article I read that actually showed a functional difference between Factory and Service: factory can return a constructor function, service can't. Personally, I have my factory be an actual factory by having it posses a method that returns a new instance of an object.

> Whether using Sublime, Visual Studio, or Vim with Nerd Tree, a lot of time is spent scrolling through the directory tree.

There might be a lot of reasons to use dir-by-feature or dir-by-function but if the above is a problem you should probably get a better editor.

EDIT: or use yours better.

I actually close my file browser "side bar" in Sublime and just open files with the "Goto" command (⌘ P). With strict naming conventions I find this to be quicker than navigating a file browser.

These are great suggestions!

I really enjoy 1, 9 and 10. Learn something new everyday.

Best of advices and guides are here https://github.com/johnpapa/angularjs-styleguide

Not mentioned: Coding things so that your site content is invisible to the Google search engine. Which is ironic considering Google's support of Angular

Except that the crawler now executes a lot of Javascript: http://googlewebmastercentral.blogspot.com/2014/05/understan...

A lot of things built in Angular aren't needed be SEO. When you do want it I recommend checking out https://prerender.io/

great advice here, I would have liked to see some code examples for #4, b/c I am currently struggling with a ng codebase where the controller are doing way too much in my opinion. using ui-router helps some, but, I would love to see more ways to keep the controllers slim.

The biggest mistake is using AngularJS. Been there, done that.

Mistake #1: Thinking it is a good idea to use AngularJS or any other JS framework that reimplements loading a page.

Applications are open for YC Summer 2019

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact