Подключить приложение Angular к приложению ReactJS? - PullRequest
0 голосов
/ 28 января 2019

У меня есть приложение Angular и часть приложения, которую я хочу написать в ReactJS.

Как я могу внедрить приложение ReactJS в существующее приложение Angular?Мне также нужна двунаправленная связь для компонентов.

1 Ответ

0 голосов
/ 28 января 2019

Вам может понадобиться другой вариант, поэтому я пишу его в 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 .

Если вы ищете демо, нажмите прокат .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...