Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/bubblydoo/angular-react

Use React in Angular and Angular in React, easily
https://github.com/bubblydoo/angular-react

angular microfrontend react

Last synced: 1 day ago
JSON representation

Use React in Angular and Angular in React, easily

Awesome Lists containing this project

README

        


NPM


Storybook

# React in Angular and Angular in React

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

```html

```

```tsx
function ReactComponent({ text }) {
return
}
```

## 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: ``
})
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 (
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 }) => {children}
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>()

```

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

{props.message}

})

@Component({
template: ``,
})
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 (

setNumber(number + 1)}>increment


)
}
```

### Using templates

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

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

@Component({
selector: 'message',
template: `




`,
})
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
}
```

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
}

@Component({
selector: "outer",
template: `
{{ message }}




`,
})
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: ``
})
class AppComponent {
Button = Button

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

You can fix it as follows:

```ts
@Component({
template: ``
})
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)