skip to Main Content

I’m migrating an existing React app over to Angular. Each React component will eventually be rewritten, but in the meantime I’m hosting them in Angular components based on this method: https://medium.com/@joseph5/rendering-react-component-in-angular-apps-9154fa3198d1. I’m using a component instead of a directive, but otherwise it’s the same.

This works fine, and I can render the React components in Angular. What I’d like to do is give them access to services provided in the Angular app. Most importantly, I want them to share the Angular app’s NGRX store. The existing components were using useDispatch and useSelector from the react-redux library, but since they’re now standalone components, there’s no app-level store provided for those to use. I have to provide access to my NGRX store, which can’t be injected into the React component because it’s not in an Angular injectionContext.

What I’ve been doing is injecting the store into the Angular host and passing it through the props:

// Angular
@Component({
  selector: 'app-search-results',
  template: `<ng-react [component]="SearchResults" [props]="props"></ng-react>`,
  encapsulation: ViewEncapsulation.None,
  styleUrl: './search-results.component.scss'
})
export class SearchResultsComponent {
  SearchResults = SearchResults;
  props = {
    store: inject(Store),
  };
}

And then in the React counterpart:

// React
const SearchResults = (props) => {
  const store = props.store;
  const [filterData, setFilterData] = useState(initialState);

  // creates an observable once and subscribes to it with the given callback
  useObservable(() => store.select((state) => state.filterData), setFilterData);
  // ...

This works. The only problem with it is that sometimes these components have children and grandchildren that also need access to NGRX or some other Angular service. I’d prefer not to keep passing it down through the props. I’ve experimented with createContext, but I can’t find a satisfying way to use it for this. Can anyone recommend the best way to share my NGRX store with these React components?

2

Answers


  1. I believe you will have to pass to the input to every "react root" the injector, but at least the rest of things can be a bit better.

    // glue utils
    export const InjectorContext = createContext<Injector | null>(null);
    
    export const useInjected = <T>(token: ProviderToken<T>): T | null => { // null maybe can be omitted for convinience if possible, but need to check
      const injector = useContext(InjectorContext);
      if(!injector) {return null;}
      return injector.get(token);
    }
    export const ReactPiece: FC = ({injector, children}: {injector: Injector, children:...}) => {
      return (<InjectorContext.Provider value={injector}>
        {children}
      </InjectorContext.Provider>)
    }
    

    usage

    // somewhere in angular
    injector = inject(Injector);
    
    render() {
      return (
        <ReactPiece injector={this.injector}>
          <SomeReactComponent/>
        </ReactPiece>
    
      )
    }
    
    // react
    function SomeReactComponent() {
      const service = useInjected(MyAngularService);
      ... // service is your service
    }
    
    Login or Signup to reply.
  2. Use RxJS for Cross-Framework Communication Expose Angular service data as an Observable.
    Subscribe to the Observable from within the React component.

    1. Modify Angular Service to Use RxJS

    src/app/shared/my-angular.service.ts:
    
    
    import { Injectable } from '@angular/core';
    import { BehaviorSubject, Observable } from 'rxjs';
    
    @Injectable({
      providedIn: 'root',
    })
    export class MyAngularService {
      private dataSubject = new BehaviorSubject<string>('Initial Data');
      data$: Observable<string> = this.dataSubject.asObservable();
    
      updateData(newData: string) {
        this.dataSubject.next(newData);
      }
    }
    

    2. Subscribe to the Angular Service Data in React

    src/app/react-wrapper/ReactComponent.tsx:
    
    
    import React, { useEffect, useState } from 'react';
    import { BehaviorSubject } from 'rxjs';
    
    interface Props {
      data$: BehaviorSubject<string>;
    }
    
    export const ReactComponent: React.FC<Props> = ({ data$ }) => {
      const [data, setData] = useState<string>('');
    
      useEffect(() => {
        const subscription = data$.subscribe(setData);
        return () => subscription.unsubscribe(); // Cleanup on unmount
      }, [data$]);
    
      return <div>Data from Angular Service: {data}</div>;
    };
    

    3. Pass the Observable from Angular Service into React

    src/app/react-wrapper/react-wrapper.component.ts:
    
    
    import { Component, ElementRef, OnInit } from '@angular/core';
    import * as ReactDOM from 'react-dom';
    import React from 'react';
    import { MyAngularService } from '../shared/my-angular.service';
    import { ReactComponent } from './ReactComponent';
    
    @Component({
      selector: 'app-react-wrapper',
      template: '<div #reactContainer></div>',
    })
    export class ReactWrapperComponent implements OnInit {
      constructor(
        private hostElement: ElementRef,
        private angularService: MyAngularService
      ) {}
    
      ngOnInit(): void {
        const container = this.hostElement.nativeElement.querySelector(
          '#reactContainer'
        );
    
        // Pass the Angular service's observable to React
        ReactDOM.render(
          <ReactComponent data$={this.angularService.data$} />,
          container
        );
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search