This article is for developers who are familiar with Angular 1.x and would like to learn more about React. We’ll look at the different approaches they take to building rich web applications, the overlapping functionality and the gaps that React doesn’t attempt to fill.
After reading, you’ll have an understanding of the problems that React sets out to solve and how you can use the knowledge you have already to get started using React in your own projects.
Frameworks vs Libraries
Angular is a framework, whereas React is a library focused only on the view layer. There are costs and benefits associated with both using frameworks and a collection of loosely coupled libraries.
Frameworks try to offer a complete solution, and they may help organize code through patterns and conventions if you’re part of a large team. However, having a large API adds cognitive load when you’re writing, and you’ll spend a lot more time reading documentation and remembering patterns — especially in the early days when you’re still learning.
Using a collection of loosely coupled libraries with small APIs is easier to learn and master, but it means that when you run into problems you’ll need to solve them with more code or pull in external libraries as required. This usually results in you having to write your own framework to reduce boilerplate.
Recommended Courses
Todd Motto
The ultimate resource to learn Angular and its ecosystem. Use coupon code 'SITEPOINT' at checkout to get 25% off.
Out Of The Box
Angular gives you a rich feature set for building web applications. Among it’s features are:
- HTML templates with dynamic expressions in double curlies
{{ }}
- built-in directives like
ng-model
,ng-repeat
andng-class
for extending the capability of HTML - controllers for grouping logic and passing data to the view
- two-way binding as a simple way to keep your view and controller in sync
- a large collection of modules like
$http
for communicating with the server andngRoute
for routing - custom directives for creating your own HTML syntax
- dependency injection for limiting exposure of objects to specific parts of the application
- services for shared business logic
- filters for view formatting helpers.
React, on the other hand gives you:
- JSX syntax for templates with JavaScript expressions in single curlies
{ }
- components, which are most like Angular’s element directives.
React is unopinionated when it comes to the rest of your application structure and it encourages the use of standard JavaScript APIs over framework abstractions. Rather than providing a wrapper like $http for server communication, you can use fetch()instead. You’re free to use constructs like services and filters, but React won’t provide an abstraction for them. You can put them in JavaScript modules and require them as needed in your components.
So, while Angular gives you a lot more abstractions for common tasks, React deliberately avoids this to keep you writing standard JavaScript more often and to use external dependencies for everything else.
Recommended Courses
Wes Bos
A step-by-step training course to get you building real world React.js + Firebase apps and website components in a couple of afternoons. Use coupon code 'SITEPOINT' at checkout to get 25% off.
Bootstrapping
Initializing Angular apps requires a module, a list of dependencies and a root element.
let app = angular.module('app', [])
let root = document.querySelector('#root');
angular.element(root).ready(function() {
angular.bootstrap(root, ['app']);
});
The entry point for React is rendering a component into a root node. It’s possible to have multiple root components, too:
let root = document.querySelector('#root');
ReactDOM.render(<App />, root)
Templates
The anatomy of an Angular view is complex and has many responsibilities. Your HTML templates contain a mix of directives and expressions, which tie the view and the associated controllers together. Data flows throughout multiple contexts via
$scope
.
In React, it’s components all the way down, data flows in one direction from the top of the component tree down to the leaf nodes. JSX is the most common syntax for writing components, transforming a familiar XML structure into JavaScript. Whilst this does resemble a template syntax, it compiles into nested function calls.
const App = React.createClass({
render: function() {
return (
<Component>
<div>{ 2 + 1 }</div>
<Component prop="value" />
<Component time={ new Date().getTime() }>
<Component />
</Component>
</Component>
)
}
})
The compiled code below should help clarify how the JSX expressions above map to
createElement(component, props, children)
function calls:var App = React.createClass({
render: function render() {
return React.createElement(
Component,
null,
React.createElement("div", null, 2 + 1),
React.createElement(Component, { prop: "value" }),
React.createElement(
Component,
{ time: new Date().getTime() },
React.createElement(Component, null)
)
);
}
});
Template Directives
Let’s look at how some of Angular’s most used template directives would be written in React components. Now, React doesn’t have templates, so these examples are JSX code that would sit inside a component’s
render
function. e.g.class MyComponent extends React.Component {
render() {
return (
// JSX lives here
)
}
}
ng-repeat
<ul>
<li ng-repeat="word in words">{ word }</li>
</ul>
We can use standard JavaScript looping mechanisms such as
map
to get an array of elements in JSX.<ul>
{ words.map((word)=> <li>{ word }</li> )}
</ul>
ng-class
<form ng-class="{ active: active, error: error }">
</form>
In React, we’re left to our own devices to create our space-separated list of classes for the
className
property. It’s common to use an existing function such as Jed Watson’s classNames for this purpose.<form className={ classNames({active: active, error: error}) }>
</form>
The way to think about these attributes in JSX is as if you’re setting properties on those nodes directly. That’s why it’s
className
rather than the class
attribute name.formNode.className = "active error";
ng-if
<div>
<p ng-if="enabled">Yep</p>
</div>
if … else
statements don’t work inside JSX, because JSX is just syntactic sugar for function calls and object construction. It’s typical to use ternary operators for this or to move conditional logic to the top of the render method, outside of the JSX.// ternary
<div>
{ enabled ? <p>Enabled</p> : null }
</div>
// if/else outside of JSX
let node = null;
if (enabled) {
node = <p>Enabled</p>;
}
<div>{ node }</div>
ng-show / ng-hide
<p ng-show="alive">Living</p>
<p ng-hide="alive">Ghost</p>
In React you can set style properties directly or add a utility class such as
.hidden {display: none }
to your CSS for the purpose of hiding your elements (which is how Angular handles it).<p style={ display: alive ? 'block' : 'none' }>Living</p>
<p style={ display: alive ? 'none' : 'block' }>Ghost</p>
<p className={ classNames({ hidden: !alive }) }>Living</p>
<p className={ classNames({ hidden: alive }) }>Ghost</p>
You’ve got the hang of it now. Instead of a special template syntax and attributes, you’ll need to use JavaScript to achieve what you want instead.
An Example Component
React’s Components are most like Angular’s Directives. They’re used primarily to abstract complex DOM structures and behavior into reusable pieces. Below is an example of a slideshow component that accepts an array of slides, renders a list of images with navigational elements and keeps track of its own
activeIndex
state to highlight the active slide.<div ng-controller="SlideShowController">
<slide-show slides="slides"></slide-show>
</div>
app.controller("SlideShowController", function($scope) {
$scope.slides = [{
imageUrl: "allan-beaver.jpg",
caption: "Allan Allan Al Al Allan"
}, {
imageUrl: "steve-beaver.jpg",
caption: "Steve Steve Steve"
}];
});
app.directive("slideShow", function() {
return {
restrict: 'E',
scope: {
slides: '='
},
template: `
<div class="slideshow">
<ul class="slideshow-slides">
<li ng-repeat="slide in slides" ng-class="{ active: $index == activeIndex }">
<figure>
<img ng-src="{{ slide.imageUrl }}" />
<figcaption ng-show="slide.caption">{{ slide.caption }}</figcaption>
</figure>
</li>
</ul>
<ul class="slideshow-dots">
<li ng-repeat="slide in slides" ng-class="{ active: $index == activeIndex }">
<a ng-click="jumpToSlide($index)">{{ $index + 1 }}</a>
</li>
</ul>
</div>
`,
link: function($scope, element, attrs) {
$scope.activeIndex = 0;
$scope.jumpToSlide = function(index) {
$scope.activeIndex = index;
};
}
};
});
The Slideshow Component in Angular
This component in React would be rendered inside another component and passed the slides data via props.
let _slides = [{
imageUrl: "allan-beaver.jpg",
caption: "Allan Allan Al Al Allan"
}, {
imageUrl: "steve-beaver.jpg",
caption: "Steve Steve Steve"
}];
class App extends React.Component {
render() {
return <SlideShow slides={ _slides } />
}
}
React components have a local scope in
this.state
, which you can modify by calling this.setState({ key: value })
. Any changes to state causes the component to re-render itself.class SlideShow extends React.Component {
constructor() {
super()
this.state = { activeIndex: 0 };
}
jumpToSlide(index) {
this.setState({ activeIndex: index });
}
render() {
return (
<div className="slideshow">
<ul className="slideshow-slides">
{
this.props.slides.map((slide, index) => (
<li className={ classNames({ active: index == this.state.activeIndex }) }>
<figure>
<img src={ slide.imageUrl } />
{ slide.caption ? <figcaption>{ slide.caption }</figcaption> : null }
</figure>
</li>
))
}
</ul>
<ul className="slideshow-dots">
{
this.props.slides.map((slide, index) => (
<li className={ (index == this.state.activeIndex) ? 'active': '' }>
<a onClick={ (event)=> this.jumpToSlide(index) }>{ index + 1 }</a>
</li>
))
}
</ul>
</div>
);
}
}
Events in React look like old-school inline event handlers such as
onClick
. Don’t feel bad, though: under the hood it does the right thing and creates highly performant delegated event listeners.The Slideshow Component in React
Two-Way Binding
Angular’s trusty
ng-model
and $scope
form a link where the data flows back and forth between a form element and properties on a JavaScript object in a controller.app.controller("TwoWayController", function($scope) {
$scope.person = {
name: 'Bruce'
};
});
<div ng-controller="TwoWayController">
<input ng-model="person.name" />
<p>Hello {{ person.name }}!</p>
</div>
React eschews this pattern in favor of a one-way data flow instead. The same types of views can be built with both patterns though.
class OneWayComponent extends React.Component {
constructor() {
super()
this.state = { name: 'Bruce' }
}
change(event) {
this.setState({ name: event.target.value });
}
render() {
return (
<div>
<input value={ this.state.name } onChange={ (event)=> this.change(event) } />
<p>Hello { this.state.name }!</p>
</div>
);
}
}
The
<input>
here is called a “controlled input”. This means its value is only ever changed when the `render` function is called (on every key stroke in the example above). The component itself is called “stateful” because it manages its own data. This isn’t recommended for the majority of components. The ideal is to keep components “stateless” and have data passed to them via props
instead.
Typically, a stateful Container Component or Controller View sits at the top of the tree with many stateless child components underneath. For more information on this, read What Components Should Have State? from the docs.
Call Your Parents
Whilst data flows down in one direction, it’s possible to call methods on the parent through callbacks. This is usually done in response to some user input. This flexibility gives you a lot of control when refactoring components to their simplest presentational forms. If the refactored components have no state at all, they can be written as pure functions.
// A presentational component written as a pure function
const OneWayComponent = (props)=> (
<div>
<input value={ props.name } onChange={ (event)=> props.onChange(event.target.value) } />
<p>Hello { props.name }!</p>
</div>
);
class ParentComponent extends React.Component {
constructor() {
super()
this.state = { name: 'Bruce' };
}
change(value) {
this.setState({name: value});
}
render() {
return (
<div>
<OneWayComponent name={ this.state.name } onChange={ this.change.bind(this) } />
<p>Hello { this.state.name }!</p>
</div>
)
}
}
This might seem like a round-about pattern at first if you’re familiar with two-way data binding. The benefit of having a lot of small presentational “dumb” components that just accept data as
props
and render them is that they are simpler by default, and simple components have far fewer bugs. This also prevents the UI from being in an inconsistent state, which often occurs if data is in multiple places and needs to be maintained separately.Dependency Injection, Services, Filters
JavaScript Modules are a much better way to handle dependencies. You can use them today with a tool like Webpack, SystemJS or Browserify.
// An Angular directive with dependencies
app.directive('myComponent', ['Notifier', '$filter', function(Notifier, $filter) {
const formatName = $filter('formatName');
// use Notifier / formatName
}]
// ES6 Modules used by a React component
import Notifier from "services/notifier";
import { formatName } from "filters";
class MyComponent extends React.Component {
// use Notifier / formatName
}
Sounds Great. Can I Use Both!?
Yes! It’s possible to render React components inside an existing Angular application. Ben Nadel has put together a good post with screencast on how to render React components inside an Angular directive. There’s also ngReact, which provides a
react-component
directive for acting as the glue between React and Angular.
If you’ve run into rendering performance problems in certain parts of your Angular application, it’s possible you’ll get a performance boost by delegating some of that rendering to React. That being said, it’s not ideal to include two large JavaScript libraries that solve a lot of the same problems. Even though React is just the view layer, it’s roughly the same size as Angular, so that weight may be prohibitive based on your use case.
While React and Angular solve some of the same problems, they go about it in very different ways. React favors a functional, declarative approach, where components are pure functions free of side effects. This functional style of programming leads to fewer bugs and is simpler to reason about.
How About Angular 2?
Components in Angular 2 resemble React components in a lot of ways. The example components in the docs have a class and template in close proximity. Events look similar. It explains how to build views using a Component Hierarchy, just as you would if you were building it in React, and it embraces ES6 modules for dependency injection.
// Angular 2
@Component({
selector: 'hello-component',
template: `
<h4>Give me some keys!</h4>
<input (keyup)="onKeyUp($event)" />
<div>{{ values }}</div>
`
})
class HelloComponent {
values='';
onKeyUp(event) {
this.values += event.target.value + ' | ';
}
}
// React
class HelloComponent extends React.Component {
constructor(props) {
super()
this.state = { values: '' };
}
onKeyUp(event) {
const values = `${this.state.values + event.target.value} | `;
this.setState({ values: values });
}
render() {
return (
<div>
<h4>Give me some keys!</h4>
<div><input onKeyUp={ this.onKeyUp.bind(this) } /></div>
<div>{ this.state.values }</div>
</div>
);
}
}
A lot of the work on Angular 2 has been making it perform DOM updates a lot more efficiently. The previous template syntax and complexities around scopes led to a lot of performance problems in large apps.
A Complete Application
In this article I’ve focused on templates, directives and forms, but if you’re building a complete application, you’re going to require other things to help you manage your data model, server communication and routing at a minimum. When I first learned Angular and React, I created an example Gmail application to understand how they worked and to see what the developer experience was like before I started using them in real applications.
You might find it interesting to look through these example apps to compare the differences in React and Angular. The React example is written in CoffeeScript with CJSX, although the React community has since gathered around ES6 with Babel and Webpack, so that’s the tooling I would suggest adopting if you’re starting today.
There’s also the TodoMVC applications you could look at to compare:
Learning Resources
Learning React has been enjoyable, it’s taught me more about functional programming and has a vibrant community around it contributing their own interesting pieces to the React ecosystem. Andrew Ray has written a few great introductory posts on React and Flux, the official React tutorial is the go-to place to get started. Enjoy!
- React for stupid people – Andrew Ray
- Flux for stupid people – Andrew Ray
- React Tutorial – Facebook
- React Router – Ryan Florence
- Redux – Video series by Dan Abramov
No comments:
Post a Comment