Code Monkey home page Code Monkey logo

react-angular-chimera's Introduction

React Angular Chimera · version

"A thing of immortal make, not human, lion-fronted and snake behind, a goat in the middle."

-Homer, the Iliad

This is a proof of concept and example of what I'm calling the "React Angular Chimera", AKA running React components inside an Angular controller/wrapper inside the Umbraco backoffice.

This is a very simple example of a property editor with a textbox.

Seeing the Example In Action

To see the example property editor in action, copy the /build/dist/ReactAngularChimera/ folder into /App_Plugins/ of the Umbraco install of your choice, restart your Umbraco install, add a new DataType with the "React in Angular Chimera" property editor, and then include that on a doctype you wish to see the editor in.

The Source Files

The JavaScript file bundle.js inside the distributable example is built with Webpack. The source files for its JS is inside /build/assets/js. You can modify and build these files by first running npm install in the /build/ directory and then running npm run webpack after you've made any desired code changes.

The JS files and their role are as follows:

app.js

This is the "launching point" of the JS code. It imports the AngularWrapper() function and instantiates it as a controller mapped to "ReactAngularChimera.AngularWrapper", which is used by the ng-controller data-type for the Angular view for the property editor in /build/dist/ReactAngularChimera/views/container.html.

controllers/AngularWrapper.js

This is the Angular controller that binds to the view for the property editor. It makes use of two Angular dependencies: $scope and $element, we are injected via $inject at the end of the file. In this example we're using $scope instead of var vm = this to reduce confusion with scope binding for the React component.

The controller file is heavily commented, but I'll walk you through how the interaction between the Angular and React works.

We need to perform two important tasks to make the React work properly: we need to initialize a render of a component inside the markup, and we need to keep a two-way communication between the Angular controller's $scope and the React component's props.

/build/dist/ReactAngularChimera/views/container.html looks like this:

<div class="chimera-container" ng-controller="ReactAngularChimera.AngularWrapper">
    <div><strong>React in Angular Chimera Example</strong></div>
    <div class="react-mount-node"></div>
    <div>
        <strong>Angular-controlled span: </strong><span>{{model.value}}</span>
    </div>
</div>

So we need to bind the React component to the <div class="react-mount-node" /> element. This is why we've injected $element into the AngularWrapper controller. Inside $scope.setVariables() we have the following:

$scope.reactNode = $element[0].querySelector('.react-mount-node');

Whenever AngularWrapper's $scope.model.value updates, we want to be able to pass that into our React component, ReactLogic, which doesn't benefit from Angular's native binding. So in $scope.init() we set a watch on $scope.model.value as follows:

$scope.$watch('model.value', function(newValue) {
    $scope.updateReact(newValue);
});

Inside the $scope.updateReact() function we make sure to get the new value (which we assign to the variable value) and then we insert it into our ReactLogic component with the following:

ReactDOM.render(<ReactLogic 
    onValueChange={$scope.updateAngular} 
    value={value} 
/>, $scope.reactNode);

We reference the element we bound earlier to $scope.reactNode, and we pass in our value as well as a function called $scope.updateAngular() which we've mapped to onValueChange (we'll visit this momentarily).

Off-hand, one might think that calling ReactDOM.render() each time we want to update our component would be slow. Thankfully, this isn't the case, as described by Dan Abramov:

Misconception: calling ReactDOM.render() second time is very slow. Reality: it has the same performance as setState() on root component.

-Dan Abramov (@dan_abramov) on Twitter, 24 Jan 2016

By doing this, every time Angular detects an update to our value, we pass that change into our React component's props.

The final important part of this puzzle is how to perform the reverse: capturing changes occurring to our value in the React component and passing them back to our Angular controller. We keep this all inside the AngularWrapper's sphere of authority by creating a $scope.updateAngular() function that looks like this:

$scope.updateAngular = function(value) {
    $scope.model.value = value;
    $scope.$apply();
}      

Which we pass into our ReactLogic component as onValueChange. Inside the component we can then call this function and have it pass the changes back up to the Angular.

components/ReactLogic/ReactLogic.js

This is our React component that we're injecting into the Angular view using the ReactDOM.render() inside AngularWrapper, and that we're passing $scope.model.value to as props.value. It is a vanilla React component, with no need for additional changes to behave properly with our Angular wrapper. We can use it in any normal fashion we desire.

There will be some issues if we try to utilize Angular dependencies like $http, but we could inject those with ngimport, and we also need to pass in any Umbraco services or resources into our component via AngularWrapper as props if we wish to utilize those, as we can't use ngimport to bring in non built-in Angular dependencies. More on how to do this will exist in a future version of this repo.

react-angular-chimera's People

Contributors

cssquirrel avatar

Stargazers

 avatar

Watchers

 avatar  avatar

Forkers

f-engstrom

react-angular-chimera's Issues

Don't use a watcher on model.value

Angular watchers are notoriously inefficient so should be avoided where possible. With that in mind, there are a few "special" Umbraco features you can make use of to achieve the same without the watcher overhead. A bare bones example would be

// here we declare a special method which will be called 
// whenever the value has changed from the server
$scope.model.onValueChanged = function (newVal, oldVal) {
  // Model value changed so update UI
  updateUi()
};

var unsubscribe = $scope.$on("formSubmitting", function () {
  // Write data back to the model.value
  $scope.model.value = ...
});

$scope.$on('$destroy', function () {
  unsubscribe();
});

Ultimately Umbraco checks for the special method onValueChanged and uses that to notify you if the model on the server changes, and the formSubmitting event gets fired when the form is about to send to the server, so you can hold off writing to $scope.model.value until the very last minute. The $destroy event handler just does some cleaning up to prevent memory leaks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.