Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

angular2react method can be simplified #29

Open
cozmy opened this issue Apr 17, 2018 · 3 comments
Open

angular2react method can be simplified #29

cozmy opened this issue Apr 17, 2018 · 3 comments

Comments

@cozmy
Copy link

cozmy commented Apr 17, 2018

I want to start by saying that I really like what you've accomplished here. It's a really useful tool for those which want to gradually migrate an AngularJS project to the newer world.

I found that the angular2react can be a bit hard to use and thus I've created a wrapper function which simplifies the API so my team members would use it more comfortably. As you'll see, we don't need to pass the $injector and register the angular component ourselves. It's all done automatically

Here it is:

import { IComponentOptions, module } from 'angular';
import { angular2react } from 'angular2react';

// All converted components have to be registered, thus we create our own module in which we'll register the original ng components.
const ngModule = module('converted-ng-components', []);

let $injector;
ngModule.run(['$injector', $i => $injector = $i]);

export function myAngular2React<Props extends {}>(name: string, options: IComponentOptions) {
  ngModule.component(name, options);

  return angular2react<Props>(name, options, {
    // angular2react requires the $injector but doesn't actually use it until a component needs to be rendered.
    get get() {
      return $injector.get;
    }
  } as any);
}

export const convertedNgComponentsModuleName = ngModule.name;

If you think this approach is fine, I can make a pull request.

@cozmy cozmy changed the title angular2react can extended in order to be simplified angular2react can be extended in order to be simplified Apr 17, 2018
@cozmy cozmy changed the title angular2react can be extended in order to be simplified angular2react can be simplified Apr 17, 2018
@cozmy cozmy changed the title angular2react can be simplified angular2react method can be simplified Apr 17, 2018
@Downchuck
Copy link

@cozmy Thanks for this one! Have y'all hit anything with hot reloading? I've successfully used this with storybook, which is a big help for dealing with some of our older components.

@Irev-Dev
Copy link

Irev-Dev commented Oct 17, 2019

I'm using a similar approach, so we don't need to pass in the injector each time, though we've defined ours as a custom hook where it wraps angular2react in a useMemo so that it can be called at the start of functional components without making excessive calls to angular2react

it's not a big abstraction so I'm not 100% sure a PR is needed.

import { ComponentClass, useMemo } from 'react';
import { angular2react } from 'angular2react';
import * as angular from 'angular';

let $injector;
export function setInjector(_$injector) {
	$injector = _$injector;
}

export function useAngularComponent(
	angularComponentName: string,
	angularComponentDefinition: angular.IComponentOptions
): ComponentClass {
	if (!$injector) {
		throw new Error('Missing injector, you might have run this before angular was bootstrapped');
	}
	return useMemo(() => angular2react(angularComponentName, angularComponentDefinition, $injector), []);
}

@LuisAverhoff
Copy link

LuisAverhoff commented May 25, 2020

@cozmy How exactly do you use your myAngular2React function? From my understanding and after countless hours trying to render a very simple component template, a call to the module's component function i.e ngModule.component(name, options); should not be called in your myAngular2React function. All your angular components need to be registered before you bootstrap angular just like @bcherny is doing here https://github.com/bcherny/angular2react-demos/blob/master/multi-file/src/index.jsx#L24 else the component won't render its template. Unless I'm missing something?

I made a simple angular helper module for registering angular components, converting them to react components, services, routes. I liked the idea that @Irev-Dev had with using useMemo to reduce the number of calls to angular2react so that is what I went with.

import { angular2react } from 'angular2react';
import { module, auto, ILocationProvider } from 'angular';
import { useMemo } from 'react';
import { AngularComponent } from './components';
import { AngularService } from './services';

export const root = module('root', ['']);
let $injector: auto.IInjectorService;

export const useAngularComponent = <Props extends {}>(component: AngularComponent) => {
    if (!$injector) {
        throw new Error('Missing injector, you might have run this before angular was bootstrapped');
    }

    return useMemo(() => angular2react<Props>(component.selector, component, $injector), []);
};

export const registerAngularComponents = (components: AngularComponent[]) => {
    for (const component of components) {
        root.component(component.selector, component);
    }
};

export const registerAngularServices = (services: AngularService[]) => {
    for (const service of services) {
        root.service(service.name, service.injectable);
    }
};

export const registerAngularConstants = (constants: AngularService[]) => {
    for (const constant of constants) {
        root.constant(constant.name, constant.injectable);
    }
};

export const registerAngularRoutes = (routes: Function[]) => {
    root.config(($locationProvider: ILocationProvider) => {
        $locationProvider.html5Mode({ enabled: true, requireBase: false });
    });

    for (const route of routes) {
        root.config(route);
    }
};

export const setInjector = (_$injector: auto.IInjectorService) => {
    $injector = _$injector;
};

I have a single bootstrapping function.

import { bootstrap, auto } from 'angular';
import {
    root,
    setInjector,
    registerAngularComponents,
    registerAngularServices,
    registerAngularConstants,
    registerAngularRoutes,
} from './helpers';
import constants from './constants';
import routes from './routes';
import components from './components';
import services from './services';

const bootstrapAngular = (callback: () => void) => {
    registerAngularConstants(constants);
    registerAngularRoutes(routes);
    registerAngularComponents(components);
    registerAngularServices(services);

    root.run([
        '$injector',
        (_$injector: auto.IInjectorService) => {
            setInjector(_$injector);
            callback();
        },
    ]);

    bootstrap(document.createElement('div'), [root.name]);
};

export default bootstrapAngular;

This is how my my main index.tsx looks.

import React from 'react';
import { render } from 'react-dom';
import './index.css';
import App from './App';
import bootstrapAngular from './-angular';
import * as serviceWorker from './serviceWorker';
import { makeServer } from './tests/Mocks/Server';

if (process.env.NODE_ENV === 'development') {
    console.log('calling makeServer');
    makeServer();
}

const bootstrapReact = () => {
    render(<App />, document.getElementById('app'));
};

bootstrapAngular(bootstrapReact);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants