AngularJS

Angular vs. React: Which Is Better for Web Development?

There are countless articles out there debating whether React or Angular is the better choice for web development. Do we need yet another one?

The reason I wrote this article is because none of the articles published already—although they contain great insights—go in-depth enough for a practical front-end developer to decide which one may suit their needs.

Angular vs. React: Choose the framework way, or tinker with libraries?

In this article, you will learn how Angular and React both aim to solve similar front-end problems though with very different philosophies, and whether choosing one or the other is merely a matter of personal preference. To compare them, we will build the same application twice, once with Angular and then again with React.

Angular’s Untimely Announcement

Two years ago, I wrote an article about the React Ecosystem. Among other points, the article argued that Angular had become the victim of “death by pre-announcement.” Back then, the choice between Angular and almost anything else was an easy one for anyone who didn’t want their project to run on an obsolete framework. Angular 1 was obsolete, and Angular 2 was not even available in alpha version.

On hindsight, the fears were more-or-less justified. Angular 2 changed dramatically and even went through major rewrite just before the final release.

Two years later, we have Angular 4 with a promise of relative stability from here on.

Now what?

Angular vs. React: Comparing Apples and Oranges

Some people say that comparing React and Angular is like comparing apples to oranges. While one is a library that deals with views, the other is a full-fledged framework.

Of course, most React developers will add a few libraries to React to turn it into a complete framework. Then again, the resulting workflow of this stack is often still very different from Angular, so the comparability is still limited.

The biggest difference lies in state management. Angular comes with data-binding bundled in, whereas React today is usually augmented by Redux to provide unidirectional data flow and work with immutable data. Those are opposing approaches in their own right, and countless discussions are now going on whether mutable/data binding is better or worse than immutable/unidirectional.

A Level Playing Field

As React is famously easier to hack on, I’ve decided, for the purpose of this comparison, to build a React setup that mirrors Angular reasonably closely to allow side-by-side comparison of code snippets.

Certain Angular features that stand out but are not in React by default are:

Feature Angular package React library
Data binding, dependency injection (DI) @angular/core MobX
Computed properties rxjs MobX
Component-based routing @angular/router React Router v4
Material design components @angular/material React Toolbox
CSS scoped to components @angular/core CSS modules
Form validations @angular/forms FormState
Project generator @angular/cli React Scripts TS

Data Binding

Data binding is arguably easier to start with than the unidirectional approach. Of course, it would be possible to go in completely opposite direction, and use Redux or mobx-state-tree with React, and ngrx with Angular. But that would be a topic for another post.

Computed Properties

While performance is concerned, plain getters in Angular are simply out of question as they get called on each render. It’s possible to use BehaviorSubject from RsJS, which does the job.

With React, it’s possible to use @computed from MobX, which achieves the same objective, with arguably a bit nicer API.

Dependency Injection

Dependency injection is kind of controversial because it goes against current React paradigm of functional programming and immutability. As it turns out, some kind of dependency injection is almost indispensable in data-binding environments, as it helps with decoupling (and thus mocking and testing) where there is no separate data-layer architecture.

One more advantage of DI (supported in Angular) is the ability to have different lifecycles of different stores. Most current React paradigms use some kind of global app state which maps to different components, but from my experience, it’s all too easy to introduce bugs when cleaning the global state on component unmount.

Having a store that gets created on component mount (and being seamlessly available to this component’s children) seems to be really useful, and often overlooked concept.

Out of the box in Angular, but quite easily reproducible with MobX as well.

Routing

Component-based routing allows components to manage their own sub-routes instead of having one big global router configuration. This approach has finally made it to react-router in version 4.

Material Design

It’s always nice to start with some higher-level components, and material design has become something like an universally-accepted default choice, even in non-Google projects.

I have deliberately chosen React Toolbox over the usually recommended Material UI, as Material UI has serious self-confessed performance problems with their inline-CSS approach, which they plan to solve in the next version.

Besides, PostCSS/cssnext used in React Toolbox is starting to replace Sass/LESS anyway.

Scoped CSS

CSS classes are something like global variables. There are numerous approaches to organizing CSS to prevent conflicts (including BEM), but there’s a clear current trend in using libraries that help process CSS to prevent those conflict without the need for a front-end developer to devise elaborate CSS naming systems.

Form Validation

Form validations are a non-trivial and very widely used feature. Good to have those covered by a library to prevent code repetition and bugs.

Project Generator

Having a CLI generator for a project is just a bit more convenient than having to clone boilerplates from GitHub.

Same Application, Built Twice

So we are going to create the same application in React and Angular. Nothing spectacular, just a Shoutboard that allows anyone to post messages to a common page.

You can try the applications out here:

Shoutboard app in Angular vs. in React

If you want to have the whole source code, you can get it from GitHub:

You will notice we have used TypeScript for the React app as well. The advantages of type checking in TypeScript are obvious. And now, as better handling of imports, async/await and rest spread have finally arrived in TypeScript 2, it leaves Babel/ES7/Flow in the dust.

Also, let’s add Apollo Client to both because we want to use GraphQL. I mean, REST is great, but after a decade or so, it gets old.

Bootstrap and Routing

First, let’s take a look at both applications’ entry points.

Angular

const appRoutes: Routes = [
  { path: 'home', component: HomeComponent },
  { path: 'posts', component: PostsComponent },
  { path: 'form', component: FormComponent },
  { path: '', redirectTo: '/home', pathMatch: 'full' }
]
 
@NgModule({
  declarations: [
    AppComponent,
    PostsComponent,
    HomeComponent,
    FormComponent,
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot(appRoutes),
    ApolloModule.forRoot(provideClient),
    FormsModule,
    ReactiveFormsModule,
    HttpModule,
    BrowserAnimationsModule,
    MdInputModule, MdSelectModule, MdButtonModule, MdCardModule, MdIconModule
  ],
  providers: [
    AppService
  ],
  bootstrap: [AppComponent]
})
@Injectable()
export class AppService {
  username = 'Mr. User'
}

Basically, all components we want to use in the application need to go to declarations. All third-party libraries to imports, and all global stores to providers. Children components have access to all this, with an opportunity to add more local stuff.

React

const appStore = AppStore.getInstance()
const routerStore = RouterStore.getInstance()
 
const rootStores = {
  appStore,
  routerStore
}
 
ReactDOM.render(
  <Provider {...rootStores} >
    <Router history={routerStore.history} >
      <App>
        <Switch>
          <Route exact path='/home' component={Home as any} />
          <Route exact path='/posts' component={Posts as any} />
          <Route exact path='/form' component={Form as any} />
          <Redirect from='/' to='/home' />
        </Switch>
      </App>
    </Router>
  </Provider >,
  document.getElementById('root')
)

The <Provider/> component is used for dependency injection in MobX. It saves stores to context so that React components can inject them later. Yes, React context can (arguably) be used safely.

The React version is a bit shorter because there are no module declarations – usually, you just import and it’s ready to use. Sometimes this kind of hard dependency is unwanted (testing), so for global singleton stores, I had to use this decades-old GoF pattern:

export class AppStore {
  static instance: AppStore
  static getInstance() {
    return AppStore.instance || (AppStore.instance = new AppStore())
  }
  @observable username = 'Mr. User'
}

Angular’s Router is injectable, so it can be used from anywhere, not only components. To achieve the same in react, we use the mobx-react-router package and inject the routerStore.

Summary: Bootstrapping both applications is quite straightforward. React has an edge being more simple, using just imports instead of modules, but, as we’ll see later, those modules can be quite handy. Making singletons manually is a bit of a nuisance. As for routing declaration syntax, JSON vs. JSX is just a matter of preference.

Post originally appeared on Toptal Developers blog

Leave a Reply

Your email address will not be published. Required fields are marked *