Friday, September 22, 2017

AngularJS to React/Redux and back again

I've found AngularJS to have many analogues (corresponding pieces) to React/Redux.  Let me show you.

A Redux state almost exactly corresponds to an AngularJS $scope variable.

In a Redux reducer file, you have an initial state that looks like this:
const initialState = {
  inited: false,
  loading: true,
  user: {
    first: '',
    last: '',
    email: ''
  },
  error: ''
}

This initial state almost directly corresponds to an AngularJS $scope initial state.  If it doesn't, you can easily move the AngularJS variables that are outside the $scope variable into the $scope variable to match up exactly.

Not every AngularJS controller has an "inited" variable but many do.  In Redux, an "inited" variable is essential to signal that the Redux state has not been populated by data from the backend yet.  An easy way to do this is to check the "inited" prop in the componentDidMount() method in the React component that requires it (or any React component that requires it) and, if it is false, load up the data and set the "inited" value (well, call an action to call a reducer to do it) to true.  Even if you don't have an "inited" variable in your AngularJS $scope variables, it is easy to add one.  The AngularJS controller function might be converted like this:
componentDidMount() {
  if (!this.props.inited) {
    // do this only once
    this.props.model('inited', true)
    // in AngularJS, this is the controller function
    getUser(this.props)
  }
}
Speaking of Redux reducers and so on (and since I brought it up in the code directly above), it is very convenient and useful to make a "model" action along the lines of an AngularJS ng-model.  The "model" action will take a string indicating the value to change, e.g. props.model('user.first', 'John'), and ultimately create something that looks like this:
<input type="text" value={ props.user.first } 
  onChange={e => props.model('user.first', e.target.value)}/>
Often, I've found it easy to copy the HTML from my AngularJS templates (HTML) into the React render() method and fix them up.  There are a three tricks that you have to remember:
  1. Do a global search and replace on "class=" to replace with "className=".  Before you make this change, your React component may render but will not apply the CSS classes.
  2. Search for each "style=" (e.g. style="background: blue") and convert each instance (e.g. style={ { background: 'blue' } }).  (Don't forget to make it a JavaScript object by having the double curly braces.) . Really, if your AngularJS still contains "style" attributes, it's just better to convert them into CSS classes and "class" attributes rather than go through the hassle of converting them to React.  Before you make this change, your React component will often render a blank screen; this is a big hint that you have unconverted "style" attributes.
  3. Change each AngularJS template to have a single root element, not multiple root elements.  Multiple root elements are allowed in AngularJS but there is no reason to use them and it is easy enough to just wrap it all in a div tag so it is easy to convert to React.  React components (specifically, JSX and the render() method) require a single root element.
react-router-redux is a pretty convenient analogue to AngularJS router modules (either the built-in or ui-router).

Factories and services can be stripped of their AngularJS specifics and placed in your React application's modules folder.  Usually, I place them in a subfolder of modules.  Often, factories and services use the AngularJS $http service; you can convert these calls to use fetch (from the isomorphic-fetch npm module) or Axios (or superagent or request or whatever).

AngularJS filters are essentially just functions; I just strip out the code and drop them into the modules folder itself.  A call to a filter is easy to convert.  In AngularJS, a filter looks like this:
{{ 'mytitle' | translate }}
In React, it becomes:
{ translate('mytitle') }
In AngularJS, we have the "ng-if" and "ng-repeat" attributes which are used a lot in almost every AngularJS application to do control flow.

The AngularJS "ng-if" attribute is used like this:
<div ng-if="showing">
  My title
</div>
In React, it becomes a conditional, like this:
{ props.showing? (
<div>
  My title
</div>
) : '' }
An AngularJS "ng-repeat" attribute is a little more complicated.  From this:
<table>
<thead>
<tr>
<th ng-repeat="column in columns">
  {{ column }}
</th>
</tr>
</thead>
</table>
To this in React:
<table>
<thead>
<tr>
{ props.columns.length? props.columns
  .map((column, index) => (
<th key={ index }>
  { column }
</th>)
  ).reduce((p, c) => [p, '', c]) : ''}
</tr>
</thead>
</table>
Admittedly, this post is a little slapdash and not really enough to convert an entire AngularJS to React/Redux. But I think that you will agree that there are a lot of concepts that easily port from AngularJS to Redux with a little tweaks, here and there.

Wednesday, September 20, 2017

What's wrong with Ember.js?

Once you have React and Angular in your toolkit, should you concern yourself with Ember.js?

Executive summary: No.

React comes from Facebook; Angular comes from Google.  If you live in the Silicon Valley like I do, Facebook and Google cast a long shadow.  Many companies, large and small, "cargo cult" Facebook and Google; that is, they adopt technology blindly based on what Facebook and Google do, hoping that that imitating success will yield real success.  Many startups adopt "hot" technologies to appeal to venture capitalists ("hey, we're just like Google!") and to garner attention from developers and the press.

Right now (9/20/2017), React is in the cat bird seat.  React developers command the extravagant salaries and most of the pursuit by recruiters.  In contrast, AngularJS (1.x) is a workhorse: valued by many companies but also some willing to leave positions open rather than paying a premium and others paying a limited premium in developer salaries.  Angular (2/4) is rarely seen: companies who adopted Angular (2/4) seem oddly out of touch, like they didn't get the memo, and seem to have little interest in paying salary premiums.

Where is Ember.js?

Ember.js is at LinkedIn, a respectable name to be sure, but hardly the influencer that Google and Facebook are.  Ember.js is also seen at the occasional AI or hardware startup.  No salary premium for Ember.js developers: those companies think that being an Ember.js developer shows that you are a free-thinking hippie that cares more about concepts than money.

Ember.js gets an honorable mention by those job postings that read, "must know at least one of React, AngularJS, Ember.js or other JavaScript framework".  Hey, that's something: Vue.js doesn't get this kind of respect.  But we all know that, when a job posting reads like that, it really means, "Hell, we're don't know jack about any of these JavaScript frameworks so, if you've done any of it, it's all the same to us."

Recently, I did a multi-day investigation and technical review on Ember.js and, while Ember.js seems technically competitive with React and Angular in some respects, there was no indication that they were ever going to emerge from their rather distant third place, open source, hippie-dippie "alternative" framework status.  Having recently reviewed Backbone.js, I see a lot of intellectual inheritance from Backbone.js in Ember.js and, believe me, that's not a compliment.  That smell of Backbone.js in Ember.js makes it feel ... old.

I have to give the Ember.js team respect for hanging it there all this time.  But, sorry, I will not be staking my future on Ember.js, either now or anytime soon.  Nor will I be considering any Ember.js positions.  And I'd advise others not to, either.

But, hey, Ember.js, if, someday, you break through, give me a call.  I'm in the book.

Tuesday, September 19, 2017

What was Backbone.js and why did it die?

As AngularJS (Angular 1.x), Angular (Angular 4) and React rose, Backbone.js declined. What was Backbone.js? Why did it decline?

Let's just dive in and look at some Backbone.js design and features.

Backbone.View.extend(), Backbone.Model.extend(), Backbone.Router.extend(), etc.

Backbone.js is based on a Java-style class structure with classes, objects and inheritance. It makes a conscious choice to embrace this approach, instead of JavaScript prototypical inheritance. The extend() method creates a subclass of the "class" which it is invoked upon. While other frameworks adopt this approach as well (usually by upgrading to TypeScript or ES6) for their view classes, Backbone.js is rather dogmatic about it and, as we will soon see, it requires a lot more classes and a lot more objects whereas other frameworks were more limited and less, well, so darned adamant about creating all those things.

_.bindAll() call in a Backbone.View constructor

When a new Backbone.View class is created, its constructor often contains multiple calls to _.bind() or a single call to _.bindAll(). I have written about the function of _.bind() before so I won't repeat that here. OK, OK, I'll repeat it a little. _.bind() converts a method into a standalone function call. In Backbone.js, this is often required because event handlers only work with functions, not with methods. Backbone.js uses events extensively so the _.bind() calls are necessary. Calls to _.bind() and _.bindAll() confuse many people and cause odd errors when not used so Backbone.js got a reputation as being confusing.

React requires a bind() call in some but not all circumstances but it is not designed around events in the same way that Backbone.js is. The use of _.bind() and _.bindAll() isn't a deal killer but certainly doesn't help Backbone.js' reputation.

render() method of a Backbone.View

Every Backbone.View class has a render() method, similar to React. The render() method in Backbone.js is usually a string concatenator, something like an old-fashioned version of React, before JSX was widely adopted. In Backbone.js, it is additionally encouraged to call the render() methods of child Backbone.View objects, take the returned strings and insert them in the correct position in the parent Backbone.View string. All this string concatenation feels very awkward and old-fashioned.

events property of a Backbone.View

The events property looks something like this:

events: {
 'click button#add': 'addItem'
}

It is a map with magic strings that makes nobody happy.  The “click” refers to the onclick event; the “on” prefix is dropped.  "button#add” is a jQuery selector.  “addItem” is the method to invoke on the current object.

Backbone.js is the one framework that separated event handlers from the HTML. This approach provides no benefit to the developer and is somewhat inconvenient and buggy.

Backbone.Model

Backbone.js separates views and data; Backbone.Model was the base class for data. Instead of working with plain old JSON objects like many other frameworks, Backbone.js expects JSON objects (from the server) to be deserialized into full blown Backbone.Model objects.

Backbone.js data objects support events and event handlers (as do view objects). The idea seems to be that, once JSON data is converted into full-blown Backbone.js objects, the view objects and data objects are stitched together with the right events.

The Backbone.js data objects is an alternative to AngularJS' $scope variable or React's props variable (argument). Backbone.js event mechanisms require the developer to orchestrate how data changes propagate to the view. In contrast, AngularJS' digest cycle and React's virtual DOM and diffing algorithms take care of this orchestration for the developer.

Backbone.Collection

Backbone.Collection is the Backbone.Model version of an array. Instead of using ordinary JavaScript arrays, Backbone.js requires the developer to deserialize arrays into collections so that events and event handlers can be used to monitor data changes.

Backbone.sync()

Backbone.sync is an Backbone.js specific implementation of something like GraphQL but is incompatible with GraphQL. Its API is reminiscent of server side persistence libraries like Hibernate with methods like fetch() and save(). Backbone.sync uses HTTP operations like GET, POST, PUT and DELETE to perform server CRUD operations using pre-determined URLs. A server needs to confirm to Backbone.sync's conventions in order to work with Backbone.sync.

GraphQL has attained some level of popularity due to its support from Facebook; Backbone.sync never has.

Backbone.Router

Backbone.Router supports partial views. It seems OK.

Underscore.js templates

Backbone.js supports HTML templates via Underscore.js.

An Underscore.js HTML template looks like:

<h1><%= title %></h1>
<% if (title === 'first') { %>
This is the first title <% } %>

Backbone.js also supports other libraries like Mustache but these must be downloaded and integrated separately. This presents a problem: you cannot re-use templates that were written in one library with a different library so templates were mostly not reusable.

Backbone.js does not provide any support for dynamic data in the template. In AngularJS, Angular or React, if the data changes, the template (or JSX) is automatically re-rendered whereas, with Backbone.js templates, the developer must detect the change, invoke the template directly and re-render the updated template in the right place.

Also, Backbone.js does not provide any support for (reusable) components. In AngularJS, directives allow the developer to insert a component with HTML and behavior. Similarly, JSX in React allows a developer to insert a component with HTML and behavior.

Backbone's weakness in templates and lack of component functionality is one of its biggest weakness and an important reason why it is not popular.

Overall

Backbone.js is a very awkward framework, requiring much of the developer trying to use it.

It is also a very ineffective framework, lacking popular features while providing other features that were not that useful.

Backbone.js is very imperative, manually connecting everything with events and event listeners, instead of declarative, changing JSX (in React) or changing HTML templates (in AngularJS) to invoke the event handler.