A exercise to compare the use of Angular and React in creating stateful components with common behaviour implemented once. Demo
Given an array of objects (presumably coming from the server), render each as a UI component with state and behaviour. Each component will have common features that must be implemented DRYly. In the current exercise, the common behaviour is to bind the visibility of the component to the visible
property of the object. We do this by implementing a base or wrapper component. The goal is the outer and inner components share enough state that a property mutation originating in the inner component triggers a binding declared in the outer component. In a large application, there would be many different type of inner components so pulling common behaviour into an outer component avoids duplication.
{ name: 'control1', visible: true, message:'One' }
We nest directives using transclusion, passing the name of the object into the inner directive where the object is retrieved and stored on $scope
.
<outer><inner name="control1"></inner></outer>
The object is also passed to the parent $scope
by requiring (require: '?^outer'
) the parent and receiving the parent controller in the postList
function. The <outer>
directive wraps the <inner>
in markup, using ng-show
to bind to visible
. It also display's the object's name. The <inner>
directive binds a checkbox to visible
. Since <outer>
and <inner>
share the same model instance, these bindings succeed even though the directives are isolated.
We again nest outer and inner classes, this time passing the full object as a prop
to the outer control which converts it to state via getInitialState
. The outer class receives the object instead of the inner because data only flows from parent to child in React. The outer class binds its visibility to the visible
property. The object is passed to the child as a prop
along with two-way binding helper object which the child assigns to the checkedLink
property of the checkbox.
In our implementation, the app
class assembles the application by mapping over the data set and outputting nested <outer key={i} model={item} ><inner /></outer>
instances. This means that outer
does not create inner
itself and misses out on setting its properties. Instead, it accesses the child via this.props.children
and decorates it with properties through a call to React.addons.cloneWithProps
. It is here that the state object and the two-way binding helper are made available to inner
.
-
In the Angular implementation, the outer and inner directives share the same model object instance. This allows for events originating in the inner to mutate the outer without manual hook up other than simple declarative bindings. (The manner in which shared scope is achieved is open to debate, but I have this pattern running in a large production application to good effect.)
-
In the React implementation there is no shared state. The parent must pass to the child callbacks for each type of signal it wishes to receive from the child. In the current implemenation we are concerned with visibility. In a more complex application, we may wish to handle events for validity, enable/disable, debugging and more. We use the
ReactLink
add-on to avoid having to to created change event handlers in theinner
that call aprop
function passed in byouter
. -
Angular requires that the directives be declared in markup, requiring that we render the tags server-side. Undoubtedly we could iterate over the array and insert the tags into DOM, but this would require us to prevent automatic initialization and manually bootstrap the Angular application.
-
Since React allows us to build the entire UI in Javascript, we can build our class instances by mapping over the object array. But imagine we want to choose the type of the inner control based on the value of a property of
item
. It is not clear how this could be accomplished in JSX. This is invalid:<{item.innertype} />
. We will attempt this usingReact.DOM
.
var models = this.props.data.map(function(item, i){
return <outer key={i} model={item} ><inner /></outer>
});
-
Angular's factories provide a nice way to encapsulate and share the data source, making it easy to replace the hard-coded data source with a more realistic fetch from the server.
-
React may also provide a solution too. I am much more familiar with Angular than with React and it is possible that my solution does not use React in an idiomatic way.
-
Note that in the current exercise we only have one component type. In a more realistic scenario, there would be many types each inheriting the common behaviour.