🌜
🌞
ngreact

ngreact

v0.5.2

Use React Components in Angular

npm install ngreact

README

Build Status Pair on this

Note: For a more modern alternative to ngReact, we recommend react2angular, angular2react, and ngimport.

ngReact

The React.js library can be used as a view component in web applications. ngReact is an Angular module that allows React Components to be used in AngularJS applications.

Motivation for this could be any of the following:

  • You need greater performance than Angular can offer (two way data binding, Object.observe, too many scope watchers on the page) and React is typically more performant due to the Virtual DOM and other optimizations it can make

  • React offers an easier way to think about the state of your UI; instead of data flowing both ways between controller and view as in two way data binding, React typically eschews this for a more unidirectional/reactive paradigm

  • Someone in the React community released a component that you would like to try out

  • You're already deep into an Angular application and can't move away, but would like to experiment with React

Installation

Install via Bower:

bower install ngReact

or via npm:

npm install ngreact

Usage

Then, just make sure Angular, React, and ngReact are on the page,

<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/react/react.js"></script>
<script src="bower_components/react/react-dom.js"></script>
<script src="bower_components/ngReact/ngReact.min.js"></script>

and include the 'react' Angular module as a dependency for your new app

<script>
    angular.module('app', ['react']);
</script>

and you're good to go.

Features

Specifically, ngReact is composed of:

  • react-component, an Angular directive that delegates off to a React Component
  • reactDirective, a service for converting React components into the react-component Angular directive

ngReact can be used in existing angular applications, to replace entire or partial views with react components.

The react-component directive

The react-component directive is a generic wrapper for embedding your React components.

With an Angular app and controller declaration like this:

angular
  .module('app', ['react'])
  .controller('helloController', function($scope) {
    $scope.person = { fname: 'Clark', lname: 'Kent' };
  });

And a React Component like this

var HelloComponent = React.createClass({
  propTypes: {
    fname: React.PropTypes.string.isRequired,
    lname: React.PropTypes.string.isRequired
  },
  render: function() {
    return (
      <span>
        Hello {this.props.fname} {this.props.lname}
      </span>
    );
  }
});
app.value('HelloComponent', HelloComponent);

The component can be used in an Angular view using the react-component directive like so:

<body ng-app="app">
  <div ng-controller="helloController">
    <react-component name="HelloComponent" props="person" watch-depth="reference"/>
  </div>
</body>

Here:

  • name attribute checks for an Angular injectable of that name and falls back to a globally exposed variable of the same name
  • props attribute indicates what scope properties should be exposed to the React component
  • watch-depth attribute indicates what watch strategy to use to detect changes on scope properties. The possible values for react-component are reference, collection and value (default)

The reactDirective service

The reactDirective factory, in contrast to the reactComponent directive, is meant to create specific directives corresponding to React components. In the background, this actually creates and sets up directives specifically bound to the specified React component.

If, for example, you wanted to use the same React component in multiple places, you'd have to specify <react-component name="yourComponent" props="props"></react-component> repeatedly, but if you used reactDirective factory, you could create a <your-component></your-component> directive and simply use that everywhere.

The service takes the React component as the argument.

app.directive('helloComponent', function(reactDirective) {
  return reactDirective(HelloComponent);
});

Alternatively you can provide the name of the component

app.directive('helloComponent', function(reactDirective) {
  return reactDirective('HelloComponent');
});

This creates a directive that can be used like this:

<body ng-app="app">
  <div ng-controller="helloController">
    <hello-component fname="person.fname" lname="person.lname" watch-depth="reference"></hello-component>
  </div>
</body>

The reactDirective service will read the React component propTypes and watch attributes with these names. If your react component doesn't have propTypes defined you can pass in an array of attribute names to watch. If you don't pass any array of attribute names, fall back to use directive attributes as a last resort. By default, attributes will be watched by value however you can also choose to watch by reference or collection by supplying the watch-depth attribute. Possible values are reference, collection and value (default).

app.directive('hello', function(reactDirective) {
  return reactDirective(HelloComponent, ['fname', 'lname']);
});

You may also customize the watch depth per prop/attribute by wrapping the name and an options object in an array inside the props array:

app.directive('hello', function(reactDirective) {
  return reactDirective(HelloComponent, [
    'person', // takes on the watch-depth of the entire directive
    ['place', { watchDepth: 'reference' }],
    ['things', { watchDepth: 'collection' }],
    ['ideas', { watchDepth: 'value' }]
  ]);
});

By default, ngReact will wrap any functions you pass as in scope.$apply. You may want to override this behavior, for instance, if you are passing a React component as a prop. You can achieve this by explicitly adding a wrapApply: false in the prop config:

app.directive('hello', function(reactDirective) {
  return reactDirective(HelloComponent, [
    'person',
    ['place', { watchDepth: 'reference' }],
    ['func', { watchDepth: 'reference', wrapApply: false }]
  ]);
});

If you want to change the configuration of the directive created the reactDirective service, e.g. change restrict: 'E' to restrict: 'C', you can do so by passing in an object literal with the desired configuration.

app.directive('hello', function(reactDirective) {
  return reactDirective(HelloComponent, undefined, { restrict: 'C' });
});

Minification

A lot of automatic annotation libraries including ng-annotate skip implicit annotations of directives. Because of that you might get the following error when using directive in minified code:

Unknown provider: eProvider <- e <- helloDirective

To fix it add explicit annotation of dependency

var helloDirective = function(reactDirective) {
  return reactDirective('HelloComponent');
};
helloDirective.$inject = ['reactDirective'];
app.directive('hello', helloDirective);

Reusing Angular Injectables

In an existing Angular application, you'll often have existing services or filters that you wish to access from your React component. These can be retrieved using Angular's dependency injection. The React component will still be render-able as aforementioned, using the react-component directive.

It's also possible to pass Angular injectables and other variables as fourth parameter straight to the reactDirective, which will then attach them to the props

app.directive('helloComponent', function(reactDirective, $ngRedux) {
  return reactDirective(HelloComponent, undefined, {}, { store: $ngRedux });
});

Be aware that you can not inject Angular directives into JSX.

app.filter('hero', function() {
  return function(person) {
    if (person.fname === 'Clark' && person.lname === 'Kent') {
      return 'Superman';
    }
    return person.fname + ' ' + person.lname;
  };
});

/** @jsx React.DOM */
app.factory('HelloComponent', function($filter) {
  return React.createClass({
    propTypes: {
      person: React.PropTypes.object.isRequired
    },
    render: function() {
      return <span>Hello $filter('hero')(this.props.person)</span>;
    }
  });
});
<body ng-app="app">
  <div ng-controller="helloController">
    <react-component name="HelloComponent" props="person" />
  </div>
</body>

Jsx Transformation in the browser

During testing you may want to run the JSXTransformer in the browser. For this to work with angular you need to make sure that the jsx code has been transformed before the angular application is bootstrapped. To do so you can manually bootstrap the angular application. For a working example see the jsx-transformer example.

NOTE: The workaround for this is hacky as the angular bootstap is postponed in with a setTimeout, so consider transforming jsx in a build step.

Usage with webpack and AngularJS < 1.3.14

CommonJS support was added to AngularJS in version 1.3.14. If you use webpack and need to support AngularJS < 1.3.14, you should use webpack's exports-loader so that require('angular') returns the correct value. Your webpack configuration should include the following loader config:

...
module: {
  loaders: [
    {
      test: path.resolve(__dirname, 'node_modules/angular/angular.js'),
      loader: 'exports?window.angular'
    }
  ]
},
...

Developing

Before starting development run

npm install
bower install

Build minified version and run tests with

grunt

Continually run test during development with

grunt karma:background watch

Running the examples

The examples in the examples/ folder use bower_components. To install these first install bower on your machine

npm install --global bower

Then install the bower components

bower install

The examples need to be run on a local webserver like https://www.npmjs.com/package/http-server.

Run the examples by starting a webserver in the project root folder.

Community

Maintainers

  • Kasper Bøgebjerg Pedersen (@kasperp)
  • David Chang (@davidchang)

Contributors

  • Matthieu Prat (matthieuprat)
  • @Shuki-L
  • Fabien Rassinier (@frassinier)
  • Guilherme Hermeto (@ghermeto)
  • @thorsten
  • @katgeorgeek
  • @rosston
  • Tihomir Kit (@pootzko)
  • Alexander Beletsky (@alexanderbeletsky)
  • @matthieu-ravey
  • @ethul
  • Devin Jett (@djett41)
  • Marek Kalnik (@marekkalnik)
  • @oriweingart
  • Basarat Ali Syed (@basarat)
  • Rene Bischoff (@Fjandin)
  • Zach Pratt (@zpratt)
  • Alex Abenoja (@aabenoja)
  • @villesau
  • @bdwain
  • @onumossn

Release Notes

0.5.2
By Kasper Bøgebjerg Pedersen • Published on February 15, 2019

Re-compute prop names on every compile - #222 - thanks @matthieuprat

0.5.1
By Kasper Bøgebjerg Pedersen • Published on October 24, 2017

Updates bower version - #201 - thanks @Shuki-L

0.5.0
By Kasper Bøgebjerg Pedersen • Published on October 2, 2017
  • Pass all props if no propTypes defined #194
0.4.1
By Kasper Bøgebjerg Pedersen • Published on June 9, 2017
  • Don't wrap in $apply if configured + allow capitalized props - #191 - thanks @ghermeto
0.4.0
By Kasper Bøgebjerg Pedersen • Published on March 7, 2017
  • Fix in table example - #163 - thanks @thorsten
  • Remove jsx comments - #182 - thanks @katgeorgeek
  • Enable watchDepth for individual properties - #185 - thanks @rosston
0.3.0
By Kasper Bøgebjerg Pedersen • Published on April 16, 2016
  • Pass injectables and other variables straight to the reactDirective - #149 - thanks @villesau
  • Upgrade to React 14 - #148 - thanks @bdwain
  • Add deferred unmounting - #134 - @onumossn
  • Switched to peerDependencies #118, thanks @aabenoja
  • Fixes to README and examples - #141, #125, #122, #115 - thanks @tiffanywang3, @albatrosary, @hillmanov, @elliottsj
0.2.0
By David Chang • Published on October 25, 2015
  • Upgrade to React 0.14 (Many thanks to @aabenoja) (#109)
  • Bug fix to avoid calling $scope.$apply when you're already in an $apply or $digest (which would cause a bug) (#99)
  • Improvements to docs, tests, and code styles

Thanks to contributions from @aabenoja, @oriweingart, @zpratt, @Fjandin

0.1.7
By David Chang • Published on July 8, 2015
  • Fix/Enhancement: Return the value from Angular functions invoked within a $scope.$apply, if you require that value (#85). There was mixed discussion on this, and a sufficiently compelling use case was not provided, but I am more so of the opinion that returning is trivial and a generally accepted practice. Using it should not be necessary and could be considered an antipattern, but in the case that a valid use case arises down the road, I'd rather ngReact not be in the way.

Contributions from @oriweingart and @basarat

0.1.6
By David Chang • Published on July 1, 2015
  • Fix: Address bug where a changing prop causes a function to no longer be wrapped within scope.apply (#78)
  • Enhancement: Utilize $watchGroup in Angular if it exists to avoid superfluous renders (#69)

Contributions from @psalaets, @kasperp, and @ethul

0.1.5
By David Chang • Published on May 13, 2015

Contributions from @ethul, @djett41, and @marekkalnik

General

License
MIT
Typescript Types
Tree-shakeable
No

Popularity

GitHub Stargazers
2,628
Community Interest
3,025
Number of Forks
264

Maintenance

Commits
10/219/2201
Last Commit
Feb 15, 2019
Open Issues
0
Closed Issues
145
Open Pull Requests
0
Closed Pull Requests
21

Versions

Versions Released
10/219/2201
Latest Version Released
Feb 15, 2019
Current Tags
latest0.5.2

Contributors

kasperp
kasperp
Commits: 53
hasdavidc
hasdavidc
Commits: 47
djett
djett
Commits: 9
alexbeletsky
alexbeletsky
Commits: 7
aabenoja
aabenoja
Commits: 5
ethul
ethul
Commits: 4
zpratt
zpratt
Commits: 4
davidchang
davidchang
Commits: 3
ghermeto
ghermeto
Commits: 3
Fjandin
Fjandin
Commits: 3
matthieuprat
matthieuprat
Commits: 3
marekkalnik
marekkalnik
Commits: 2
basarat
basarat
Commits: 2
onumossn
onumossn
Commits: 2
oriweingart
oriweingart
Commits: 2