This is yet another Angular vs React comparison blog article but I think I have a bit of a unique perspective because I used to be an Angular developer for about 6 years starting from AngularJS all the way up to Angular 7. In recent years though, I've found myself working with React more often.
Some background on me, I'm a full stack developer that's been building web applications since the jQuery days before eventually finding AngularJS. Since then, I've worked on quite a few Angular projects and developed a pretty advanced understanding of Angular. In recent years, I had one medium sized React project that was a browser extension. Not a full blown SPA application but something very similar.
I'm also the lead Web Development Instructor and Curriculum Designer at a local bootcamp called DevLeague. At DevLeague, we teach both Angular 2+ and React and at the end of our bootcamp, we have a "final" project where the students are given two weeks to build a web application using either Angular or React. Our bootcamp is full time, 6 days a week, 11 hours a day. So that comes about to about 66 hours a week or a total of 132 "work" hours to build a web application. 9 times out of 10, the students choose React because it's simple to understand and easy to use.
I've recently run into a bit of free time between jobs so I decided to give myself the same challenge I give to the students. Having built web application with Angular many times over, I wanted to see what it would be like to build something of the same size with React.
The result of this experiment is contracting.rocks. A job listing site where employers can post contract work and where contractors can pick up work that interests them. They can opt to choose full time work or maybe just pick up a remote side job to help pad their bank account a bit. The inspiration for this idea came out of a few offers for contract work while I was also looking for new employment over at remoteok.io.
In the following paragraphs, I've chronicled the bits and pieces that really stuck out while jumping from Angular to React. TLDR: with one real exception, I would be fine using either one.
Form Validation
The most glaring point that sticks out in my experience, was working with forms. Angular was originally built to make working with forms easier and it obviously shows in both their documentation and their two approaches to building forms, Template Driven Forms and Reactive Forms. Template Driven Form is a holdover from AngularJS where forms are more static and validations are attached the HTML elements directly, while Reactive Forms are more dynamic and are backed by a data model. The form validation for both are also pretty extensive and something I missed quite a lot while building forms in React. Basically, each HTML input is extended with a few booleans that tell whether an input was manipulated by the user, never touched by the user, and a host of other useful input "states."
React, on the other hand, because of how close it tries to stay to the HTML5 standards has almost no support for validation above whatever the browser already provides. This meant that for a complicated form where some fields were required, others were dependant on a previous field, etc; I had to re-create a lot of magic of Angular within React.
One interesting tidbit, as much as people don't like the "two-way data binding" that Angular offers, I think it would have cut down replaced many of the functions that needed to be declared with React. In the end, I created a one-stop handler function for all inputs to be attached to. This handler function would take in the new value and a "name" prop that would allow the handler function to update the formData state properly based on the "name."
As far as validation was concerned, I used the same pattern to create a one-stop validation function that used a slightly different signature to validate any input according to the requirements of the input itself: required, dependant, toggle, etc.
Another interesting note, I built the forms to save all data to localStorage so that if the user happened to navigate away by accident or just decided to come back to the form at a later date, the content would still be there and would auto populate the correct fields. This was actually a lot easier to do in React than compared to Angular. Given the one-stop handler function, it was simple to save any input change to localStorage while I was updating state.
The problem I ran into was trying to figure out how to fire the validation functions across all the inputs on page load to show the user any inputs that weren't properly filled out. Initially, I thought that the onChange from loading the saved from data into the input elements would fire the validation function attached to each input element. But this didn't happen so I need to think of another way. I then tried to focus all the inputs and blur them immediately using useRef but this would cause all of the inputs to fire in the same tick thus only saving the last error to the error data model. I wasn't aware that you could call the setter function from useState with a method that provided the previous state in the function parameters but I still have my doubts about this since all setter functions are still firing in the same tick.
I finally settled on a separate revalidate function that would check each input for errors, collect the errors into one object, then finally update the error data model with the collected errors. But I ran into an issue with the function firing infinitely since useEffect function surrounding the revalidate function originally had the revalidate function as a dependant. I could use a useCallback here but with the error data changing at the end of the revalidate function, this would cause a new copy of the useCallback function, which would trigger the useEffect to fire again. The useEffect was meant to only trigger on page reload.
I think this was more an issue with eslint, the exhaustive-deps warning, and my inability to recognize that this would be the one situation where just disabling the warning as the answer. In the end, having the useEffect only dependant on a reload variable that is set to true if and only if there was data in localStorage kept the revalidate function from firing infinitely.
All in all, with React, I built a form that was much more inline with how Reactive forms are currently built with Angular. One data model to hold all the input data and a separate data model to how all the errors for each input. I think with Angular I would have only needed the former data model. The rest would be provided by Angular's built in libraries.
Application State
While working through the application I thought I would need Redux for a global application store. In the end, it turned out that I didn't really need it for a simple SPA that took in some information and displayed it on the home page in a ordered format. There were only three pieces of data that I stored globally, the user, a redirect URL, and prices. The user is obvious enough, keeping user state at a global level allows for components to keep track of whether the user is signed in, and changes the components appearance if they are. The redirect URL is used when a user tries to navigate to a page when they aren't authenticated. This could probably be pulled out of the global store now that I think about it more. Finally, prices are actually pulled down from the server whenever a user visits the job posting form itself. Since this is the only place the prices are being used, I could probably pull this out of the global store as well.
In the end, the only global storage I really needed was the user and I could have probably accomplished this with React Context alone. I'm sure with a more complicated application, the global store would come in handy but for what I created and for most of my experience, context is usually enough.
The biggest downside (in my opinion) to using something like context and/or redux, is the complicated boilerplate that comes with it in terms of making sure that the Provider/Consumer is in the right place for the store to be passed down to the component itself. In comparison to Angular where state is stored in a dedicated file, in a dedicated directory with Dependency Injection used to ensure that the data is there before the component is created, I always makes me ask myself if this data is really worthy of all that boilerplate code. Redux and useContext definitely cuts down on some of the boilerplate but I can easily see this ballooning into Higher Order Component on top of HOC on top of HOC as the application gets more complicated. That being said, the latest version of Redux now includes the useDispatch and useSelector hooks that simplifies some of the boilerplate code but the context boilerplate is still necessary. I also wanted to give the Redux hooks a try since they were new.
What I did fine enjoyable is that with React, if two components needed to share data and were close enough on the page, I could just hoist the state one component higher and share the data between them. This came in really handy when I could take the data from the form, and plug it directly in a 'preview' component right next to the form itself. This can also be accomplished with Angular but I run into the same issue that I had with React. Is all that boilerplate to pass data between components really necessary? Which leads to my next topic…
Component Communication
Angular components can communicate with each other in two ways. The old-school AngularJS approach was to put any data that needed to be shared between two components into a 'Service'. From Angular 2+, angular components can pass data from one component to another component. This is called Component Interaction but it does involve quite a bit of boilerplate code in the HTML itself and the component.ts file.
For a quick rundown, both components need to declare what exactly is going to be passed up or down using @Input and @Output where the input is a Javascript data type and the output is an event emitter that emits out a Javascript data type as well. On top of this, the input and outputs need to be managed within the component itself in terms of when to emit data or how to use the incoming data. And finally, the html on both components need to line up with the name arguments provided to but the input and output. That's a lot of work for what is essentially props in React.
That being said, there are two key differences. With React and props, you can create "inputs" on the fly with having to declare each one within the incoming component. That's hot. And secondly, thanks to Angular's output, data can actually flow in both directions where with React, data only flows downward. If you haven't figure it out by now, this is actually how Angular's custom "two-way data binding" works in a nutshell. There's a few things I skipped over like how the HTML syntax is using [] for inputs, and () for events but I'm sure the docs can clear that up.
So while I could have built the same side by side form and preview with Angular, I know from experience that it would have been much more difficult than the React version. Chances are, I would just create a service because it's much simpler but that technically goes against what a service should be, a global store.
Routing and Guards
One of the nice things about Angular's approach of providing all the tools necessary for building a web application is that I don't have to think about (or do research for) what router to bring in, and if it can handle all the cases that I may run into. For React, I decided to stick with the most popular option, React Router. After reading though the documentation, I was able to build a simple router for the pages that I needed, but the part that gave me the most frustration was building "guards" for pages I wanted to protected from users that weren't authenticated in yet.
In React Router, this is mainly accomplished by wrapping a Route component with a custom component then using RenderProps to decide whether to return the wrapped Route, or if they aren't authenticated, redirect to the login page. Given that routing is not built into React proper, this seems a bit of a roundabout way of building a guard but it gets the job done. I also put all the routes into their own component so that I could easily keep all the routes in one file. This helped with the organization of the routes themselves.
Looking back, I built the router and routes to fit my idea of what routing should look like and that's largely influenced based on how Angular's routing works. But I do remember that when I was first learning how to build guards with Angular, I did run into similar issues with Guards and Observables. All in all, there's actually a lot to be desired on both React and Angular's side when it comes to developer experience with routing.
HTTP and Error Handling
Another tool that angular provides is an HTTP client to make requests back to the server. With this project, I just stuck with using fetch mainly because it requires no extra dependencies. Fetch is now part of most web browsers so there's plenty of documentation on it and it's not too difficult to use. The only issue I have with it is, the extra call to response.json() to parse out a JSON body from the response but that's understandable. Angular's http client does this automatically now, but again it's not a big deal.
What I did end up doing was creating a function that helped with the JSON handling mentioned above, along with some error handling as well. But my biggest issue was really dealing with errors. Angular has a nice way of handling 401 or 403 errors via interceptors which when used with a Component placed at the top of the application and a Service to hold errors data, 'toast' like alert messages can be easily created. With React, it's a bit more roundabout since since I will sometimes make a direct HTTP request without using dispatch because the resulting response body does not need to be stored globally. But if a 401 is encountered, I now need to use a dispatch method because the error itself will need to be placed in the global store.
For my application, I decided to forego 'toast' like messages for now and just displayed the errors closer to the component that generated the HTTP request. This might better overall UX but it is something to keep in mind if 'toast' like messages are required in the future.
Scoped CSS
By default, any css/scss files referenced by a component in Angular is automatically scoped. I find this to be one of the best features of angular. It keeps the css modular and avoids potential bugs where a change in one class name could drastically change the outward appearance of the site.
React has a way to do this as well with CSS Modules, but I find that the way they implemented it to be much more clunky in regards to developer experience than compared to Angular. In React, to use the CSS scoping feature, the stylesheet needs to be imported into the component (this is similar to Angular) and set to a variable (not needed in angular), and for each HTML element the className needs to be set using the predefined variable. This can often become wordy if there are multiple classes being applied to a single element or if scoped classes are being mixed with classes defined at a global level.
Often times a function needs to be created just to handle composing class names for HTML elements or the developer will just have to live with a className property that can be quite long or difficult to read. Whereas with Angular, you just need to place the class in the appropriate HTML element and nothing more.
File Structure
One of the nice things about having a more opinionated framework or a widely adopted style guide is file naming and file structure. As a developer, spending time trying to find a file, is time wasted, so Angular's approach of having set folders where Services, Components, Guards, and others live, makes this a breeze. While many of the React projects I've jumped on will have varying file structures based on how well the Senior developer is versed with large scale SPAs.
My current preferred model is shown above. Where actions and reducers have their own folders, although as I look at this now, I think it would be better to have both of those folder in a higher store folder so that they are closer together. A folder for components that are shared between pages. A pages folder with a folder for each page. Any components that are strictly for that page can live in that folder as well. And finally a folder for routing and styles.
Nice to haves
One thing I would like to see as both frameworks start to evolve would be better support for new features that are coming to browsers. I've often wanted to use Background Sync but I'm hesitant to spend hours trying to figure out how to put all my frontend requests into a serviceworker.js file where it would be completely isolated from the components that might actually be using them. And then rewriting all frontend requests to basically dump into IndexedDB where it's now completely disconnected from the application itself. Although having played around with Background Sync during my time at DevLeague, I can imagine a way to get it to work. But I foresee it being a place for bugs to nest, or for junior developers to run into a lot of frustration. Having a tighter integration into the either fetch itself or possibly into Angular's HttpClient would make this a lot easier for everyone to use and understand.
I could also see Service Worker's Precaching capability as a boon for eager loading any lazy loaded pages in a separate thread. This could definitely help both TTFB (Time To First Byte) and TTFR (Time To First Render). Although I'm not quite sure how the frameworks could have a tighter integration with this. I could see this being something for WebPack to tackle for production builds. There probably is a plugin for it, I just haven't looked yet.
Luckily, both frameworks do have a pre-populatedserviceworker.js file and a manifest.json file that helps to turn any standard web application into a PWA (Progressive Web App). This is a great start but I'd like to see more work thrown into this direction.
Conclusion
In the end, I find the experience of building a web application in either framework to be nearly equivalent. Each framework has places that could be improved upon but for the most part, anything that I'm familiar with in Angular, I was easily able to duplicate in React. I also like how frameworks are influencing each other as well, Angular is now much more component based and simpler overall. I believe Vue.js is also taking the same approach too with regard to component like structures. React on the other hand, is starting to pull more tools into the main library with "suspense."
The one circumstance where I prefer Angular over React, would be if the application has many forms that needed validation. Angular really has this built into its DNA and it really shows. But I have to agree that building components is much quicker in React since it's largely just a single file. Also, with the addition of "Hooks", I haven't written a single Class based component in a while. Hooks just makes the entire lifecycle much easier to understand and to work with.
In my opinion, it really comes down to the application being built and what framework the development team is familiar with. Angular does have a higher learning curve in comparison to React but that's mainly because there's so much more to unpack. But it's a richer environment for an enterprise level team where people could be jumping on and off the project rapidly. React on the other hand is simpler but requires a lot more discipline from the lead architect in making sure that a style guide is enforced on all fronts. With Angular's new Ivy Renderer, file sizes and rendering speed for Angular continue to get smaller and better. With the new tools in the pipeline for React, it's becoming more and more of a delight to use. I honestly would be fine using either framework, just take them at their face value.
But I've been reading a lot of svelte lately too…
If you've made it this far, thank you for listening to me ramble on. I'm also looking to get hired so if you enjoyed this article and liked the site I built please email me at hire@contracting.rock.
I originally wrote this on medium.com but for some reason my posts and profile is now 404 on there. The original post had links to a lot of things and properly wrapped code with backticks but after spending two days on this and being properly disappointed, I just don't have the energy to update this copy. I'll come back around to this soon but just not today.
Top comments (3)
Cool write up, thanks. Forms are definitely a big difference between the two frameworks. React seems to have two major ways of doing it-- controlled and uncontrolled form fields. Coming from an angular perspective, this was pretty foreign to me.
Best of luck on your job search!
I don't think you have to call .json() anymore.
If Mozilla's documentation is up to date, it looks like you still have to call it: developer.mozilla.org/en-US/docs/W...