Skip to content

React Roomba To Angular

Posted on:March 16, 2023

The next framework I’ve taken to learning is Angular. It’s cool having these small apps I’ve built that I can implement in different frameworks for a learning experience.

In this case I went about porting my roobma-react project to angular.

Overall the code translated over fairly well with some obvious differences.

Component syntax

The below is nothing too crazy. The main difference is the element syntax where one is lowercase and with a dash. <app-roomba> vs <Roomba />.

Angular also has separate .html templates while React has the concept of JSX where the markup is right in the JavaScript.

Angular

// app.component.html

<div class="wrapper">
  <app-roomba></app-roomba>
</div>

React

// App.jsx

import React from 'react';
import Roomba from './components/Roomba';

function App() {
    return (
        <div className="wrapper">
            <Roomba />
        </div>
    )
}

export default App;

With React you don’t have to do any special importing while in Angular you need to bind your elements in a module like the following:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { RoombaComponent } from './roomba/roomba.component';
import { RoombaGridComponent } from './roomba-grid/roomba-grid.component';

@NgModule({
  declarations: [
    AppComponent,
    RoombaComponent,
    RoombaGridComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Prop binding

The next difference you will see below is how props are used. Angular has special syntax where () are used for events such as click, and [] for binding attributes to properties on your class.

Angular

// roomba/roomba.component.html

<div>
    <h1>Roomba</h1>
    <p>
        <button (click)="togglePointer()">Change Direction</button>
    </p>
    <app-roomba-grid [coord]="coord" [pointer]="pointer"></app-roomba-grid>
</div>

React

import React from 'react';
import Grid from './Grid';

export default function Roomba() {
    // business logic here

    return (
        <div>
            <h1>Roomba</h1>
            <p>
                <button onClick={togglePointer}>Change Direction</button>
            </p>
            <Grid coord={coord} pointer={pointer} />
        </div>
    )
}

Control Flow

So here is where React makes its case as just being JavaScript. In Angular you don’t use map and ternaries but instead *ngIf and *ngFor in your templates.

Angular

// roomba-grid/roomba-grid.component.html

<div *ngFor="let row of grid; let rowIndex = index" class="row">
    <div *ngFor="let column of row; let columnIndex = index" class="cell">
        <span *ngIf="coord[0] === rowIndex && coord[1] == columnIndex">{{pointer}}</span>
    </div>
</div>

React

import React from 'react';
import { range } from "../utils";

function Grid({ coord, pointer }) {
    const grid = range(5).map(_ => range(5).map(_ => ''));
    const [x, y] = coord;

    return (
        grid.map((row, rowIndex) => (
            <div key={rowIndex} className="row">
                {row.map((_, columnIndex) => (
                    <div key={columnIndex} className="cell">
                        {x === rowIndex && y == columnIndex ? pointer : null}
                    </div>
                ))}
            </div>
        ))
    )
}

export default Grid;

Functions vs. Classes

You’ll notice quite a few differences here:

• React uses functions where Angular has leaned into classes
• Instead of useState we just have instance variables on the class that we modify directly using this.
• We use ngOnInit and ngOnDestroy lifecycle hooks vs a useEffect hook to synchronize the timer.

Angular

// roomba/roomba.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-roomba',
  templateUrl: './roomba.component.html',
  styleUrls: ['./roomba.component.scss']
})
export class RoombaComponent {
  coord: [number, number] = [0, 0];
  pointer = '👉';
  timer: number | undefined;

  ngOnInit() {
    this.timer = window.setInterval(() => this.move(), 1000);
  }

  ngOnDestroy() {
    clearTimeout(this.timer);
  }

  togglePointer() {
    if (this.pointer === '👉') {
      this.pointer = '👇';
    } else if (this.pointer === '👇') {
      this.pointer = '👈';
    } else if (this.pointer === '👈') {
      this.pointer = '👆';
    } else if (this.pointer === '👆') {
      this.pointer = '👉';
    }
  }

  move() {
    // move logic
  }
}

React

import React from 'react';
import { useEffect } from 'react';

export default function Roomba() {

     const move = React.useCallback(() => {
        // move logic here
    }, [coord, pointer, togglePointer]);

    useEffect(() => {
        const timer = setInterval(move, 1000);

        return () => {
            clearTimeout(timer);
        }
    }, [move])

    // returned jsx here
}

Props

The one last thing to quickly note below is that you use the Input() decorator to show that these props are going to be passed in from the parent.

Angular

// roomba-gird/roomba-grid.component.ts

import { Component, Input } from '@angular/core';

function range(size: number, startAt = 0) {
  return [...Array(size).keys()].map(i => i + startAt);
}

@Component({
  selector: 'app-roomba-grid',
  templateUrl: './roomba-grid.component.html',
  styleUrls: ['./roomba-grid.component.scss']
})
export class RoombaGridComponent {
  @Input() coord!: [number, number];
  @Input() pointer!: string;

  grid = range(5).map(_ => range(5).map(_ => ''));
}

This was my journey into Angular. I hope you enjoyed it!