<a href="https://www.npmjs.com/package/@bubblydoo/angular-react" alt="NPM">
  <img src="https://img.shields.io/npm/v/@bubblydoo/angular-react" alt="NPM"/>
</a>
<a href="https://angular-react-bubblydoo.vercel.app/" alt="Storybook">
  <img src="https://img.shields.io/badge/Storybook-e1618c.svg?colorA=26077C&logo=data:image/png;base64,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" alt="Storybook"/>
</a>

# React in Angular and Angular in React

This is a small Angular library that lets you use React components inside Angular projects.

```html
<react-wrapper [component]="Button" [props]="{ children: 'Hello world!' }">
```

```tsx
function ReactComponent({ text }) {
  return <AngularWrapper component={TextComponent} inputs={{ text }}>
}
```

## Installation

```bash
npm i @bubblydoo/angular-react
```

```ts
import { AngularReactModule } from '@bubblydoo/angular-react'

@NgModule({
  ...,
  imports: [
    ...,
    AngularReactModule
  ]
})
```

## Features

### `ReactWrapperComponent`

Use this component when you want to use React in Angular.

It takes two inputs:
- `component`: A React component
- `props?`: The props you want to pass to the React component

The React component will be first rendered on `ngAfterViewInit` and rerendered on every `ngOnChanges` call.

```ts
import Button from './button'

@Component({
  template: `<react-wrapper [component]="Button" [props]="{ children: 'Hello world!' }">`
})
class AppComponent {
  Button = Button
}
```

### `AngularWrapper`

Use this component when you want to use Angular in React.

It takes a few inputs:
- `component`: An Angular component
- `inputs?`: The inputs you want to pass to the Angular component, in an object
- `outputs?`: The outputs you want to pass to the Angular component, in an object
- `events?`: The events from the Angular component to listen to, using `addEventListener`. Event handlers are wrapped in `NgZone.run`
- `ref?`: The ref to the rendered DOM element (uses `React.forwardRef`)

```tsx
import { TextComponent } from './text/text.component'

function Text(props) {
  return (
    <AngularWrapper
      component={TextComponent}
      inputs={{ text: props.text }}
      events={{ click: () => console.log('clicked') }}/>
  )
}
```

### `useInjected`

The Angular Injector is provided on each React component by default using React Context. You can use Angular services and other injectables with it:

```tsx
import { useInjected } from '@bubblydoo/angular-react'

const authService = useInjected(AuthService)
```

### `useObservable`

Because consuming observables is so common, we added a helper hook for it:

```tsx
import { useObservable, useInjected } from '@bubblydoo/angular-react'

function LoginStatus() {
  const authService = useInjected(AuthService)

  const [value, error, completed] = useObservable(authService.isAuthenticated$)

  if (error) return <>Something went wrong!<>

  return <>{value ? "Logged in!" : "Not logged in"}</>
}
```

### Global React Context

If you want to have a global React Context, you can register it as follows:

```ts
// app.component.ts

constructor(angularReact: AngularReactService) {
  const client = new ApolloClient()
  // equivalent to ({ children }) => <ApolloProvider client={client}>{children}</ApolloProvider>
  angularReact.wrappers.push(({ children }) => React.createElement(ApolloProvider, { client, children }))
}
```

In this example, we use `ApolloProvider` to provide a client to each React element. We can then use `useQuery` in all React components.

This is only needed when your host app is an Angular app. If you're using Angular-in-React, the context will be bridged.

### Refs

You can get a ref to the Angular component instance as follows:

```tsx
import { ComponentRef } from '@angular/core'

const ref = useRef<ComponentRef<any>>()

<AngularWrapper ref={ref} />
```

To get the component instance, use `ref.instance`. To get a reference to the Angular component's HTML element, use `ref.location.nativeElement`.

To forward a ref to a React component, you can simply use the props:

```tsx
const Message = forwardRef((props, ref) => {
  return <div ref={ref}>{props.message}</div>
})

@Component({
  template: `<react-wrapper [component]="Message" [props]="{ ref, message }">`,
})
export class MessageComponent {
  Message = Message

  message = 'hi!'

  ref(div: HTMLElement) {
    div.innerHTML = 'hi from the callback ref!'
  }
}
```

### Reading React contexts in Angular

```tsx
@Component({
  selector: "inner",
  template: `number: {{ number$ | async }}`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
class InnerComponent {
  number$ = this.contexts.read(NumberContext)

  constructor(@Inject(InjectableReactContextToken) public contexts: InjectableReactContext) {}
}

function App() {
  const [number, setNumber] = useState(42)
  return (
    <NumberContext.Provider value={number}>
      <button onClick={() => setNumber(number + 1)}>increment</button>
      <AngularWrapper component={InnerComponent} />
    </NumberContext.Provider>
  )
}
```

### Using templates

#### `useToAngularTemplateRef`: to convert a React component into a `TemplateRef`

```tsx
import { useToAngularTemplateRef } from "@bubblydoo/angular-react"

@Component({
  selector: 'message',
  template: `
    <div>
      <ng-container
        [ngTemplateOutlet]="tmpl"
        [ngTemplateOutletContext]="{ message }"
        [ngTemplateOutletInjector]="injector"
      ></ng-container>
    </div>
  `,
})
class MessageComponent {
  @Input() tmpl: TemplateRef<{ message: string }>
  @Input() message: string

  constructor(public injector: Injector) {}
}

function Text(props: { message: string }) {
  return <>{props.message}</>
}

function Message(props: { message: string }) {
  const tmpl = useToAngularTemplateRef(Text)

  const inputs = useMemo(() => ({
    message: props.message,
    tmpl,
  }), [props.message, tmpl])

  return <AngularWrapper component={MessageComponent} inputs={inputs} />
}
```

Note: `useToAngularTemplateRef` is meant for usage with `[ngTemplateOutletInjector]="injector"`. If you can't use that, use `useToAngularTemplateRefBoundToContextAndPortals` instead.

#### `useFromAngularTemplateRef`: to convert a `TemplateRef` into a React component

```tsx
function Message(props: {
  message: string
  tmpl: TemplateRef<{ message: string }>
}) {
  const Template = useFromAngularTemplateRef(props.tmpl)

  return <Template message={props.message.toUpperCase()} />
}

@Component({
  selector: "outer",
  template: `
    <ng-template #tmpl let-message="message">{{ message }}</ng-template>
    <div>
      <react-wrapper
        [component]="Message"
        [props]="{ tmpl, message }"
      ></react-wrapper>
    </div>
  `,
})
class MessageComponent {
  Message = Message

  @Input() message!: string
}
```

## Developing

You can test the functionality of the components inside a local Storybook:

```bash
yarn storybook
```

If you want to use your local build in an Angular project, you'll need to build it:

```bash
yarn build
```

Then, use `yarn link`:

```bash
cd dist/angular-react
yarn link # this will link @bubblydoo/angular-react to dist/angular-react
# or `npm link`
```

In your Angular project:

```bash
yarn link @bubblydoo/angular-react
# or `npm link @bubblydoo/angular-react`
```

`node_modules/@bubblydoo/angular-react` will then be symlinked to `dist/angular-react`.

You might want to use resolutions or overrides if you run into NG0203 errors.

```json
"resolutions": {
  "@bubblydoo/angular-react": "file:../angular-react/dist/angular-react"
}
```

## Usage notes

### `this` is undefined when passing an Angular component method as a React prop

Angular component methods are always called with the component instance as `this`. When you pass an Angular method as a prop to a React component, `this` will be `undefined`.

```ts
@Component({
  template: `<react-wrapper [component]="Button" [props]="{ onClick }">`
})
class AppComponent {
  Button = Button

  onClick() {
    console.log(this) // undefined
  }
}
```

You can fix it as follows:

```ts
@Component({
  template: `<react-wrapper [component]="Button" [props]="{ onClick }">`
})
class AppComponent {
  Button = Button

  onClick = () => {
    console.log(this) // AppComponent instance
  }
}
```

## Further reading

See this blog post for the motivation and more details: [Transitioning from Angular to React, without starting from scratch](https://dev.to/bubblydoo/transitioning-from-angular-to-react-without-starting-from-scratch-j66)
