Вам может понадобиться другой вариант, поэтому я пишу его в 2 параграфе:
- Angular-ReactJS без связи
- Angular-ReactJS с двунаправленной связью
Весь код ниже минимален, чтобы показать проблему на представленном шаге.На GitHub у вас есть полный код для решения проблемы, не всегда 1: 1 с примером ниже, потому что этот код расширен.
Angular-ReactJS без связи
Чтобы добавить приложение ReactJS в существующий AngularВ приложении вам нужно установить 5 npm-зависимостей: react
, react-dom
:
npm install --save react
npm install --save react-dom
npm install --save-dev @types/react
npm install --save-dev @types/react-dom
npm install --save-dev @types/react-select
Следующий шаг - мы должны разрешить использование шаблона jsx
в .tsx
файлах, поэтому мы должны отредактировать tsconfig.json
и добавьте:
{
...
"compilerOptions": {
…
"jsx": "react"
}
Если вы используете WebStorm, вам следует перезапустить ваш проект, потому что tslint покажет ошибку до перезапуска.
Чтобы сохранить четкую структуру, я создаю структуру каталогов:
angular /
ng-hero.component.ts // Component in Angular
react-renderer.component.ts // ReactJS renderer without communication
react /
react-application.tsx // React init application
react-hero.tsx // React hero component
app.component.html
app.component.ts
Теперь вам нужно создать специальный компонент в Angular, который будет отвечать за встраивание приложения ReactJS.Этот компонент я назову ReactRendererComponent
.Этот компонент очень прост и имеет только одну строку шаблона, конструктор с import Injector
и одну строку в ngOnInit
:
@Component({
selector: 'app-react-renderer',
template: `<div class="react-container" id="react-renderer"></div>`
})
export class ReactRendererComponent implements OnInit {
constructor(public injector: Injector) { }
ngOnInit() {
ReactApplication.initialize('react-renderer', this.injector);
}
}
Теперь нам нужен компонент ReactApplication
, где мы инициализируем приложение ReactJS:
interface IReactApplication {
injector: Injector;
}
class ReactApp extends React.Component<IReactApplication, any> {
constructor(props) {
super(props);
}
render() {
return (
<div className={'renderer'}>
<h2>ReactJS component: </h2>
<br/>
<ReactHero/>
</div>
);
}
}
export class ReactApplication {
static initialize(
containerId: string,
injector: Injector
) {
ReactDOM.render(
<ReactApp injector={injector}/>,
document.getElementById(containerId)
);
}
}
И нам нужен ReactHero
компонент, который использовался в примере ниже:
class ReactHero extends React.Component<any, any> {
constructor(props) {
super(props);
}
render() {
return (
<span>
<span>react-hero works!</span><br/>
<span>Don't have any data</span>
</span>
);
}
}
export default ReactHero;
В Angular App мы должны использовать ReactRenderer
компонент, поэтому мы используем:
App.component data:
<hr>
<h2>This is Angular</h2>
<img width="100" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
<hr>
<!-- Without data binding -->
<app-react-renderer></app-react-renderer>
На данный момент у нас есть приложение Angular со встроенным приложением ReactJS, но без какой-либо связи.Вам этого достаточно?Если да, то это все.Если вам нужен какой-либо тип связи между обоими приложениями, я представлю вам опцию RxJS ниже.
Angular-ReactJS с двунаправленной связью
В этом примере у вас есть двунаправленная привязка данных, поддерживаемая RxJS.Вы можете получить эти данные и использовать их в своем приложении ReactJS и Angular, чтобы увидеть все изменения.Этого достаточно для многих проектов, но вы можете использовать другую опцию для получения этой двунаправленной связи, например, вы можете использовать Redux для них.
Чтобы было ясно, ниже я представляю полную структуру каталогов для этой части:
angular /
hero.service.ts
ng-hero.component.ts // Component in Angular
react-bidirectional-renderer.component.ts // ReactJS renderer with bidirectional communication
model /
hero.ts // interface for Hero object
react-bidirectional
react-bidirectional-application.tsx // React init application with bidirectional communication
react-bidirectional-hero.tsx // React hero component with RxJS support
app.component.html
app.component.ts
Прежде всего мы создаем IHero
интерфейс с данными: /model/hero.ts
export interface IHero {
name: string;
age: number;
}
На следующем шаге мы создаем сервис angular/hero.service.ts
, чтобы использовать его в угловой частиприложения:
@Injectable({
providedIn: 'root'
})
export class HeroService {
private heroes$: BehaviorSubject<IHero[]> = new BehaviorSubject([]);
constructor() {
}
addHeroes(hero: IHero) { // To add new hero
const actualHero = this.heroes$.value;
actualHero.push(hero);
this.heroes$.next(actualHero);
}
updateHeroAge(heroId: number, age: number) { // To update age of selected hero
const actualHero = this.heroes$.value;
actualHero[heroId].age = age;
this.heroes$.next(actualHero);
}
getHeroes$(): BehaviorSubject<IHero[]> { // To get BehaviorSubject and pass it into ReactJS
return this.heroes$;
}
}
И в app.component.ts
мы инициализируем данными (Зевс и Посейдон):
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
public heroesObj$: BehaviorSubject<IHero[]>;
public heroes: IHero[];
constructor(private heroService: HeroService) {}
ngOnInit(): void {
this.heroService.getHeroes$().subscribe((res: IHero[]) => {
this.heroes = res;
});
this.heroesObj$ = this.heroService.getHeroes$();
this.initHeroes();
}
initHeroes() {
this.heroService.addHeroes({name: 'Zeus', age: 88});
this.heroService.addHeroes({name: 'Poseidon', age: 46});
}
}
На следующем шаге мы должны подготовить часть приложения ReacJS, поэтому мы создаемreact-bidirectional/react-bidirectional-application.tsx
file:
interface IReactBidirectionalApp {
injector: Injector;
heroes$: BehaviorSubject<IHero[]>; // We use this interface to grab RxJS object
}
class ReactBidirectionalApp extends React.Component<IReactBidirectionalApp, any> {
constructor(props) {
super(props);
this.state = {
heroes$: this.props.heroes$ // and we pass this data into ReactBidirectionalHero component
};
}
render() {
return (
<div className={'renderer'}>
<h2>ReactJS component (bidirectional data binding): </h2>
<ReactBidirectionalHero heroes$={this.state.heroes$}/>
</div>
);
}
}
export class ReactBidirectionalApplication {
static initialize(
containerId: string,
injector: Injector,
heroes$: BehaviorSubject<IHero[]>, // This is necessary to get RxJS object
) {
ReactDOM.render(
<ReactBidirectionalApp injector={injector} heroes$={heroes$}/>,
document.getElementById(containerId)
);
}
}
На следующем шаге нам понадобится компонент ReactBidirectionalHero
, поэтому мы создадим его:
interface IReactBidirectionalHero {
heroes$: BehaviorSubject<IHero[]>;
}
class ReactBidirectionalHero extends React.Component<IReactBidirectionalHero, any> {
constructor(props) {
super(props);
this.state = {
heroes: []
};
this.addAge = this.addAge.bind(this); // Register function to bump age
this.addHero = this.addHero.bind(this); // Register function to add new Hero
}
componentDidMount(): void {
// In componentDidMount we subscribe heroes$ object
this.props.heroes$.subscribe((res: IHero[]) => {
// and we pass this data into React State object
this.setState({heroes: res});
});
}
addAge(i: number) {
const temp = this.state.heroes;
temp[i].age = temp[i].age + 1;
// In this way we update RxJS object
this.props.heroes$.next( temp);
}
addHero() {
const temp = this.state.heroes;
temp.push({name: 'Atena', age: 31});
// In this way we update RxJS object
this.props.heroes$.next(temp);
}
render() {
// Hire we render RxJS part of application with addAge button and ADD ATENA button below
const heroes = this.state.heroes.map((hero: IHero, i) => {
return <span key={i}>{hero.name} - {hero.age} <button onClick={() => this.addAge(i)}>Add {hero.name} age</button><br/></span>;
});
return (
<span>
<span>react-hero works!</span><br/>
{heroes}
<br/>
<button onClick={this.addHero}>ADD ATENA</button>
</span>
);
}
}
export default ReactBidirectionalHero;
Теперь нам нужно инициализировать приложение ReactJS в приложении Angular, поэтомумы создаем angular/react-bidirectional-renderer.component.ts
- это очень просто, с одним изменением по сравнению с версией без связи:
@Component({
selector: 'app-react-owc-renderer',
template: `<div class="react-container" id="react-owc-renderer"></div>`
})
export class ReactBidirectionalRendererComponent implements OnInit {
// Hire we get data from parent component, but of course we can also subscribe this data directly form HeroService if we prefer this way
@Input() heroes$: BehaviorSubject<IHero[]>;
constructor(public injector: Injector) { }
ngOnInit() {
// We add only one parameter into initialize function
ReactBidirectionalApplication.initialize('react-owc-renderer', this.injector, this.heroes$);
}
}
А теперь нам нужно немного изменить ng-hero.component.ts
, чтобы увидеть все эффекты:
@Component({
selector: 'app-ng-hero',
template: `
<div>
<span>ng-hero works!</span><br/>
<span *ngFor="let hero of heroes; let i = index;">{{hero.name}} - {{hero.age}} - <button (click)="addAge(i)">Add {{hero.name}} age</button><br/></span>
<br/>
<button (click)="addHero()">ADD AFRODITA</button>
</div>
`
})
export class NgHeroComponent implements OnInit {
public heroes: IHero[];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.heroService.getHeroes$().subscribe((res: IHero[]) => {
this.heroes = res;
});
}
addAge(heroId: number) {
this.heroService.updateHeroAge(heroId, this.heroes[heroId].age + 1);
}
addHero() {
this.heroService.addHeroes({name: 'Afrodita', age: 23});
}
}
Наконец мы меняем app.component.html
:
App.component data:
<hr>
<h2>This is Angular component: </h2>
<app-ng-hero></app-ng-hero>
<hr>
<!-- With bidirectional data binding-->
<app-react-owc-renderer [heroes$]="heroesObj$"></app-react-owc-renderer>
<hr>
И все должно работать.Если у вас есть какие-либо проблемы, не стесняйтесь спрашивать.
Полный репозиторий с этим решением, которое вы можете найти на GitHub .
Если вы ищете демо, нажмите прокат .