All Projects โ†’ GoTeamEpsilon โ†’ Angular To React Redux

GoTeamEpsilon / Angular To React Redux

Licence: mit
Angular to React/Redux, a Guide for Angular v1 Experts Looking to Learn React/Redux

Projects that are alternatives of or similar to Angular To React Redux

Ng6 Starter
๐Ÿ†– An AngularJS Starter repo for AngularJS + ES6 + Webpack
Stars: โœญ 1,933 (+1673.39%)
Mutual labels:  webpack, angularjs
Faqguru
๐ŸŽ’ ๐Ÿš€ ๐ŸŽ‰ A list of interview questions. This repository is everything you need to prepare for your technical interview.
Stars: โœญ 4,653 (+4168.81%)
Mutual labels:  webpack, angularjs
Angularjs Webpack Starter
๐Ÿš€ A modern frontend setup for AngularJS projects using NPM, TypeScript and Webpack.
Stars: โœญ 173 (+58.72%)
Mutual labels:  webpack, angularjs
Resourcedoc
Share the swift resource for iOS dev
Stars: โœญ 251 (+130.28%)
Mutual labels:  guide, resource
React Redux Quotlify
A quote browser and manager that allows one to search famous quotes and save them to a data store
Stars: โœญ 47 (-56.88%)
Mutual labels:  webpack, redux-thunk
Angularjs Typescript Webpack
AngularJS 1.7, typescript 3 and webpack 4 starter project based on angular tutorial
Stars: โœญ 150 (+37.61%)
Mutual labels:  webpack, angularjs
Easyformgenerator
create amazing forms without coding : form editor based on angular formly
Stars: โœญ 242 (+122.02%)
Mutual labels:  webpack, angularjs
Apple Basket Redux
๐ŸŽ ่‹นๆžœ็ฏฎๅญ๏ผŒไธ€ไธชๅพฎๅž‹็š„redux/mobxๆผ”็คบ(้™„ๅคš็‰ˆๆœฌ)
Stars: โœญ 125 (+14.68%)
Mutual labels:  webpack, redux-thunk
React Antd Admin Template
ไธ€ไธชๅŸบไบŽReact+Antd็š„ๅŽๅฐ็ฎก็†ๆจก็‰ˆ๏ผŒๅœจ็บฟ้ข„่งˆhttps://nlrx-wjc.github.io/react-antd-admin-template/
Stars: โœญ 1,022 (+837.61%)
Mutual labels:  webpack, redux-thunk
Tested
Angular Material in MEAN Stack Website Source
Stars: โœญ 35 (-67.89%)
Mutual labels:  webpack, angularjs
Style Resources Loader
CSS processor resources loader for webpack
Stars: โœญ 214 (+96.33%)
Mutual labels:  webpack, resource
Angular2 Demo
A simple demo for Angular 2
Stars: โœญ 77 (-29.36%)
Mutual labels:  webpack, angularjs
Reactjs Portfolio Mern Website
My Portfolio | Full Stack MERN Application
Stars: โœญ 25 (-77.06%)
Mutual labels:  webpack, redux-thunk
Component Pattern For Angular Js 1 X
Example of implementation of Component pattern for Angular JS 1.X using ES6 & Webpack
Stars: โœญ 75 (-31.19%)
Mutual labels:  webpack, angularjs
Ng Boot Oauth
oauth2 demo with angularjs and springboot
Stars: โœญ 99 (-9.17%)
Mutual labels:  webpack, angularjs
Lecture Frontend Dev Env
"[์ธํ”„๋Ÿฐ] ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœํ™˜๊ฒฝ์˜ ์ดํ•ด์™€ ์‹ค์Šต" ๊ฐ•์˜ ์‹ค์Šต ์ž๋ฃŒ์ž…๋‹ˆ๋‹ค.
Stars: โœญ 108 (-0.92%)
Mutual labels:  webpack
Cnblogs Theme Silence
๐Ÿ“– ไธ€ๆฌพไธ“ๆณจไบŽ้˜…่ฏป็š„ๅšๅฎขๅ›ญไธป้ข˜
Stars: โœญ 1,668 (+1430.28%)
Mutual labels:  webpack
Vue2 Elm
ๅŸบไบŽ vue2 + vuex ๆž„ๅปบไธ€ไธชๅ…ทๆœ‰ 45 ไธช้กต้ข็š„ๅคงๅž‹ๅ•้กต้ขๅบ”็”จ
Stars: โœญ 37,325 (+34143.12%)
Mutual labels:  webpack
Html Webpack Plugin
Simplifies creation of HTML files to serve your webpack bundles
Stars: โœญ 9,942 (+9021.1%)
Mutual labels:  webpack
Csp Html Webpack Plugin
A plugin which, when combined with HTMLWebpackPlugin, adds CSP tags to the HTML output.
Stars: โœญ 109 (+0%)
Mutual labels:  webpack

Build Status

img

This repository is an educational resource for Angular v1 experts that are looking to learn React/Redux. A contrived sample application for managing basic patient information is provided using both technologies' best practices (look under samples/ to see both versions). This application (demonstrated below) will be referred to below as we explore the key philosophical differences between Angular v1 and React/Redux so that you can get coding!

gif

Getting Started

๐Ÿ”จ Scaffolding & Tooling

Once the technologies for a project are chosen, the next step is to figure out how to scaffold and build the application using production-ready practices. Angular v1 applications are typically wired together using a mixture of NPM and Bower (dependency management) and Grunt or Gulp (build tooling). In the React/Redux world, NPM and Webpack are the way to go (this repo uses and recommends the react-redux-starter-kit which incorporates these technologies).

Scaffolding a project in React/Redux isn't very different from what is typically done in Angular v1 (with some exceptions to the John Papa styleguide). Here's the tree output of both samples in this repository:

                 Angular v1                                           React/Redux
                ------------                                         -------------
โ”œโ”€โ”€ app.module.js                                      โ”œโ”€โ”€ common
โ”œโ”€โ”€ common                                             โ”‚   โ”œโ”€โ”€ CustomValidators.js
โ”‚   โ”œโ”€โ”€ ssn.filter.js                                  โ”‚   โ”œโ”€โ”€ Formatters.js
โ”‚   โ””โ”€โ”€ tel.filter.js                                  โ”‚   โ”œโ”€โ”€ FormsyDatePicker.js
โ”œโ”€โ”€ patient                                            โ”‚   โ”œโ”€โ”€ FormsyInput.js
โ”‚   โ”œโ”€โ”€ demographics                                   โ”‚   โ””โ”€โ”€ FormsyMaskedInput.js
โ”‚   โ”‚   โ”œโ”€โ”€ basic                                      โ”œโ”€โ”€ containers
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ basic.info.controller.js               โ”‚   โ””โ”€โ”€ AppContainer.js
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ basic.info.directive.js                โ”œโ”€โ”€ index.html
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ basic.info.view.html                   โ”œโ”€โ”€ layouts
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ basic.module.js                        โ”‚   โ””โ”€โ”€ CoreLayout
โ”‚   โ”‚   โ”œโ”€โ”€ contact                                    โ”‚       โ”œโ”€โ”€ CoreLayout.js
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ contact.info.controller.js             โ”‚       โ”œโ”€โ”€ CoreLayout.scss
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ contact.info.directive.js              โ”‚       โ””โ”€โ”€ index.js
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ contact.info.view.html                 โ”œโ”€โ”€ main.js
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ contact.module.js                      โ”œโ”€โ”€ routes
โ”‚   โ”‚   โ”œโ”€โ”€ demographics.controller.js                 โ”‚   โ”œโ”€โ”€ index.js
โ”‚   โ”‚   โ”œโ”€โ”€ demographics.directive.js                  โ”‚   โ””โ”€โ”€ Patient
โ”‚   โ”‚   โ”œโ”€โ”€ demographics.module.js                     โ”‚       โ”œโ”€โ”€ Demographics
โ”‚   โ”‚   โ””โ”€โ”€ demographics.view.html                     โ”‚       โ”‚   โ”œโ”€โ”€ Basic
โ”‚   โ”œโ”€โ”€ patient.controller.js                          โ”‚       โ”‚   โ”‚   โ””โ”€โ”€ BasicComponent.js
โ”‚   โ”œโ”€โ”€ patient.html                                   โ”‚       โ”‚   โ”œโ”€โ”€ Contact
โ”‚   โ”œโ”€โ”€ patient.module.js                              โ”‚       โ”‚   โ”‚   โ””โ”€โ”€ ContactComponent.js
โ”‚   โ””โ”€โ”€ patient.service.js                             โ”‚       โ”‚   โ”‚
โ””โ”€โ”€ index.html                                         โ”‚       โ”‚   โ”œโ”€โ”€ PatientDemographicsComponent.js
                                                       โ”‚       โ”‚   โ””โ”€โ”€ PatientDemographicsContainer.js
                                                       โ”‚       โ”œโ”€โ”€ index.js
                                                       โ”‚       โ””โ”€โ”€ PatientModule.js
                                                       โ”œโ”€โ”€ store
                                                       โ”‚   โ”œโ”€โ”€ createStore.js
                                                       โ”‚   โ”œโ”€โ”€ location.js
                                                       โ”‚   โ””โ”€โ”€ reducers.js
                                                       โ””โ”€โ”€ styles
                                                           โ”œโ”€โ”€ _base.scss
                                                           โ””โ”€โ”€ core.scss

Notice how everything is organized in modules as opposed to a flat directory approach. This is a best practice that helps one organize a complex user interface while still sharing generic pieces.

Now that the file structure (hopefully) makes sense, one can go back a directory and run the build tool (you won't find major differences between gulp/grunt and webpack). In our case, it's grunt serve and npm start for Angular v1 and React/Redux samples, respectively.

๐ŸŽ› Directives vs Components

The heart of Angular v1 is with directives. These discrete interfaces take in 1 and 2-way data parameters and inject services that really power up your view. Fortunately, directives are not that different from Redux-aware React components. Moreover, the stuff inside of React components can be easily translated from Angular v1 concepts (this repo won't go into those details, as they are easily "Googleable"... for instance Google "React equivalent for ng-repeat" to see for yourself).

In Angular v1, directives are typically introduced in views that are controlled by route-level controllers. In React/Redux, components are introduced in the same way, however, a container must be placed in the middle so that the component (also known as a smart component, in this case) can get application-wide state. The container will also bring in functions from upper level services that child components will use. These topics (application-wide state and upper level services) will be explained later in the guide.

As mentioned before, Redux has to bind its store to child React components in a container. In our application, the routes/Patient/Demographics/PatientDemographicsContainer.js puts references to the Redux state like so (code simplified for sake of demonstration):

const mapStateToProps = (state) => ({
  patientInContext: state.patient.patientInContext,
  basic: state.patient[state.patient.patientInContext].basic
  contacts: state.patient[state.patient.patientInContext].contact
})

One other important thing that is done in this container is the binding of Redux-aware service functions (formally known as "action creators") like so:

const mapDispatchToProps = {
  setPatientInContext,
  updatePatientData,
  updateContactData,
  deleteContact,
  startAddingNewContact
}

With these two mappings out of the way, child components can access the state and functions from above. Here are some highlighted examples of this in routes/Patient/Demographics/Basic/BasicComponent.js:

Displaying data from the store in a table:

<tr>
  <td><strong>SSN:</strong> {socialSecurityFormat(this.props.info.ssn)}</td>
  <td><strong>Martial Status:</strong> {this.props.info.martialStatus}</td>
</tr>

Form edit reference to the SSN input (uses local component state instead of Redux store state - note that Formsy is a popular form validation library):

<td>
  <FormsyMaskedInput mask={[/\d/,/\d/,/\d/,'-',/\d/,/\d/,'-',/\d/,/\d/,/\d/,/\d/]}
                     value={this.state.ssn}
                     onChange={this.handleInputChange}
                     validations={{
                       isLength: 9
                     }}
                     sanitizationFunction={this.sanitizeToJustNumbers}
                     validationErrors={{
                       isDefaultRequiredValue: 'Valid SSN is required',
                       isLength: 'Valid SSN is required'
                     }}
                     name='ssn'
                     label='SSN'
                     required />
</td>

Save function for the form that takes the local component state of the form and sends it to the Redux store for updating. Note that updatePatientData is passed from the parent container:

handleSubmit(formValues) {
  // Convert dob back to date string
  formValues.dob = formValues.dob.format('YYYY-MM-DD')
  this.props.updatePatientData(formValues)
  this.setState({ showForm: false })
}

At this point, you may be thinking "wait, why are you copying the data from Redux into the local state/form rather than using it directly? Isn't the point of Redux to encapsulate all application state?". Good question. As with most things in software engineering, it is always best to be able to break the rules when it's justified. Should state such as { showForm: true/false } (determines whether to render the form or not) and { cachedForm: this.props.info } (holds a cache of the form state if the user hits cancel) be put into the Redux store or just be local to the component? It depends. In our case, this state doesn't need to be application wide so Redux is only storing things that are domain-centric rather than domain-centric and UI-centric. These things will often come down to requirements and what the opinions are of your resident seasoned Redux enthusiast(s).

Service Layer

๐ŸŒฟ Store

In Angular v1, application-wide state is put into services so that directive controllers can CRUD it. In React/Redux, all application-wide state is put into the store, an object tree. As shown in the above section, components access the store via containers and parent components passing it to them. Components can alter said state by invoking module functions (formally known as "action creators") that containers and parent components pass down.

One key difference between Angular v1 application-wide state in services and Redux store is that state mutation is not allowed. While this sounds weird and scary at first, you can change this state but it must be done in a very specific way. The easiest way to think about this is whenever you update the store, you simply clone the object, mutate the clone to your heart's content, and send that to the store.

Think back to your Angular v1 directives that display information from a service that holds the state. When that service state changes, the directive will change the view to reflect said change. Similarly, Redux-aware React components will change when the store changes.

โœจ Actions & Pure Reducers

A key difference between the updating of the state in an Angular v1 service and in the Redux store is that you don't "talk" to the store directly. In order to get the store to respond to data changes, you must issue an action. Actions simply send data from your application to your store and then your app "reacts" (pardon the pun).

Recall that the routes/Patient/Demographics/Basic/BasicComponent.js calls this.props.updatePatientData(formValues) when it wishes to update basic patient information in the store. The updatePatientData function is defined in the module routes/Patient/PatientModule.js (modules will be covered in the next section) that looks like this:

export const updatePatientData = (data) => {
  return (dispatch, getState) => {
    return new Promise((resolve, reject) => {
      console.debug(`updating basic patient data for ${getState().patient.patientInContext}`)
      dispatch({
        type    : 'UPDATE_PATIENT_DATA',
        payload : data
      })
      resolve()
    })
  }
}

The important piece to focus on for now is the dispatch function. This function takes in something called an action. In our case, our action is of type UPDATE_PATIENT_DATA and the payload is the new basic data.

When the action has been dispatched, something needs to handle it so that the store is updated. This is the job of the reducer. Reducers look at an inbound action and figure out how to update the store with the new information. For example routes/Patient/PatientModule.js exposes the following reducer:

const initialState = testData
export default function patientReducer (state = initialState, action) {
  let result
  let copy
  switch (action.type) {
    case 'UPDATE_PATIENT_IN_CONTEXT':
      copy = clone(state)
      copy.patientInContext = action.payload
      result = copy
      break
    case 'UPDATE_PATIENT_DATA':
      copy = clone(state)
      copy[copy.patientInContext].basic = action.payload
      result = copy
      break
    case 'UPDATE_CONTACT_DATA':
      copy = clone(state)
      const contactIndexForUpdation = _.findIndex(copy[copy.patientInContext].contacts, (c) => {
        if (c && c.hasOwnProperty('id')) {
          return c.id === action.payload.id
        }
      })
      copy[copy.patientInContext].contacts[contactIndexForUpdation] = action.payload
      result = copy
      break
    case 'INSERT_CONTACT':
      copy = clone(state)
      const lastContact = _.last(copy[copy.patientInContext].contacts)
      let newContactId = 0
      if (lastContact != null && lastContact.hasOwnProperty('id')) {
        newContactId = lastContact.id + 1
      }
      copy[copy.patientInContext].contacts.push({ isNewContact: true, id: newContactId })
      result = copy
      break
    case 'DELETE_CONTACT':
      copy = clone(state)
      const contactIndexForDeletion = _.findIndex(copy[copy.patientInContext].contacts, (c) => {
        if (c && c.hasOwnProperty('id')) {
          return c.id === action.payload
        }
      })
      delete copy[copy.patientInContext].contacts[contactIndexForDeletion]
      result = copy
      break
    default:
      result = state
  }

  return result
}

There is a good amount going on here, but the most important thing to focus on is that state variable. This is the application store. Because we are switching on action types, the reducer will know to run the code in the UPDATE_PATIENT_DATA section. This code is simply making a copy of the store and editing it with the new basic information. At the end of the function return result is called and the Redux store is updated.

What's interesting is that the reducer is pure in that no mutations were made to the original store because clone (a nice NPM module) copied the store to a new object.

๐Ÿญ Modules

In the last section, we learned that routes/Patient/Demographics/Basic/BasicComponent.js calls this.props.updatePatientData(formValues) which dispatches an action to the reducer so that the store can be updated. You may be thinking "the module is just a place where actions are created and reducers run based on those actions". This is correct, but there is an additional piece worth pointing out. Modules can serve as a centralized place for logic to run before the store updates.

routes/Patient/Demographics/Contact/ContactComponent.js allows users to add a new contact. This data payload will eventually make it up to the module and the module may wish to perform an HTTP POST to a server (note that our sample application doesn't do this) before saving the new contact information to the store. This logic is totally appropriate for the module function and would look something like this:

export const startAddingNewContact = (data) => {
  return (dispatch, getState) => {
    return HttpPost(endpoint, data)
      .then((response) => {
        data.id = response.data.id
        dispatch({
          type    : 'INSERT_CONTACT',
          payload : data
        })
      })
    })
  }
}

You may be thinking "I see there's a mutation here (function is not pure)... data is getting an id added on, is that allowable?". The answer is "yes". Module functions can be asynchronous and have side effects. The important thing is that the reducer that will receive the action will be pure and synchronous.

Unit Testing

๐Ÿ”ฌ Frameworks & Philosophy

Unit testing is not too much different than the approaches found in Angular v1. Karma, Mocha, Chai, and Sinon are used as the test runner, framework, assertion library, and mocks/spies tools respectively.

The only philosophical difference that is notable is that tests assert items on the component view in React/Redux. This is typically not done in Angular v1 unit tests.

NOTE: v1.0.0 Didn't include unit tests for the Redux/React sample application (embarrasing, right?). The Angular v1 sample tests are in place, but we plan on doing the Redux/React tests in v1.0.1.

Additional Resources

License & Credits

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].