DEV Community

Cover image for Angular in React Terms: Components & Data Flow
Gleb Irovich
Gleb Irovich

Posted on • Updated on

Angular in React Terms: Components & Data Flow

Angular in React Terms Part 1

An attempt to create an Angular cookbook for React developers and vice versa.

Although it might be opinionated to compare two different frontend frameworks on such a low level, when I started with Angular it was very helpful to transition my React knowledge by spotting some commonalities and trying to associate Angular way of doing things with something I already understand and use.

In this episode you will see the difference in how React and Angular deal with components, their most fundamental building blocks. We will look into how components are rendered, composed and wired up together.

First things first

Let's create a to-do item component and render it as a child of the root app component.

// React
const TodoItemComponent = () => {
  return <span>I am todo item</span>
}

function App() {
  return <TodoItemComponent />
}
Enter fullscreen mode Exit fullscreen mode
// Angular
@Component({
  selector: 'todo-item',
  template: ` I am a todo item `,
})
export class TodoItemComponent {}

@Component({
  selector: 'app-root',
  template: ` <todo-item></todo-item> `,
})
export class AppComponent {}
Enter fullscreen mode Exit fullscreen mode

Component properties

Probably, the first thing you would want to do is to pass some dynamic properties to your brand new components. In the world of React, every component is a function. JSX syntax allows to pass function parameters directly to the component and handle them as you would normally do in the JS function. All values passed to the react component are available in the props object.

In comparison, Angular employs a concept of data bindings which must be defined as fields in the component class. Angular distinguishes between one-way data binding, which represents a one-directional data flow from parent to child via Input or from child to parent via Output, and two-way data binding, which allows bidirectional data flow in the component tree.

// React
const TodoItemComponent = (props) => {
  // itemValue can be accessed in props object
  return <span>{props.itemValue}</span>
}

function App() {
  return <TodoItemComponent itemValue="My todo item" />
}
Enter fullscreen mode Exit fullscreen mode
// Angular
@Component({
  selector: 'todo-item',
  template: `{{ itemValue }}`,
})
export class TodoItemComponent {
  // itemValue is available via data binding
  @Input() itemValue: string;
}

@Component({
  selector: 'app-root',
  template: ` <todo-item itemValue="My todo item"></todo-item> `,
})
export class AppComponent {}
Enter fullscreen mode Exit fullscreen mode

Content projection

While we can pass a string as property, there is more semantic way to deal with rendering content inside the component. In the function-like React world there is a special prop called children. Anything put inside the component tags can be accessed via that component's props.children.

On the other hand, Angular must be explicitly instructed, that certain content is to be rendered inside component tags. This is achieved by providing a content projection tag ng-content, which will ensure that the content transclusion will happen in the specified place. In this case, data binding won't be required any more.

// React
const TodoItemComponent = (props) => {
  return <span>{props.children}</span>
}

function App() {
  return <TodoItemComponent>My todo item</TodoItemComponent>
}
Enter fullscreen mode Exit fullscreen mode
// Angular
@Component({
  selector: 'todo-item',
  template: `<ng-content></ng-content>`,
})
export class TodoItemComponent {}

@Component({
  selector: 'app-root',
  template: ` <todo-item>My todo item</todo-item> `,
})
export class AppComponent {}
Enter fullscreen mode Exit fullscreen mode

Rendering array of components

Now as we have a todo item component, would be great to have an entire todo list.

// React
const TodoItemComponent = (props) => {
  return <li>{props.children}</li>
}

const TodoListComponent = (props) => {
  return <ul>{props.children}</ul>
}

function App() {
  return (
    <TodoListComponent>
      <TodoItemComponent>My todo item</TodoItemComponent>
    </TodoListComponent>
  )
}
Enter fullscreen mode Exit fullscreen mode
// Angular
@Component({
  selector: 'todo-item',
  template: `<ng-content></ng-content>`,
})
export class TodoItemComponent {}

@Component({
  selector: 'todo-list',
  template: `
    <ul>
      <ng-content></ng-content>
    </ul>
  `,
})
export class TodoListComponent {}

@Component({
  selector: 'app-root',
  template: `
    <todo-list>
      <li><todo-item>My todo item</todo-item></li>
    </todo-list>
  `,
})
export class AppComponent {}
Enter fullscreen mode Exit fullscreen mode

One might notice, that in React it's fine to define a todo item component wrapped with li tag, however in Angular we do it in the parent component. This happens because React components do not have hosts. If you investigate the DOM tree, you will see that whatever returned from the React component is added directly to the DOM, however, Angular components always have a host component which has a name defined in the selector property.

To dynamically render an array of todo items inside the list in React we will simply use JS Array.prototype.map method directly in JSX, where return values will be components (or HTML elements to render). To achieve the same results in Angular, we will have to use a NgForOf structural directive. "Structural directives" are basically any directives in Angular that modify DOM.

// React
const TodoItemComponent = (props) => {
  return <li>{props.children}</li>
}

const TodoListComponent = (props) => {
  return <ul>{props.children}</ul>
}

function App() {
  const myTodos = ["make pizza", "write blog post"]
  return (
    <TodoListComponent>
      {
        myTodos.map(item => <TodoItemComponent key={item}>{item}</TodoItemComponent>)
      }
    </TodoListComponent>
  )
}
Enter fullscreen mode Exit fullscreen mode
// Angular
@Component({
  selector: 'todo-item',
  template: `<ng-content></ng-content>`,
})
export class TodoItemComponent {}

@Component({
  selector: 'todo-list',
  template: `
    <ul>
      <ng-content></ng-content>
    </ul>
  `,
})
export class TodoListComponent {}

@Component({
  selector: 'app-root',
  template: `
    <todo-list>
      <li *ngFor="let item of myTodos">
        <todo-item>{{ item }}</todo-item>
      </li>
    </todo-list>
  `,
})
export class AppComponent {
  myTodos = ['make pizza', 'write blog post'];
}
Enter fullscreen mode Exit fullscreen mode

Handling events

Now as we have todo items in place, would be great to tick something as done, right? Let's extend TodoItemComponent with checkboxes.

// React
const TodoItemComponent = (props) => {
  return (
    <li>
      <input type="checkbox"/>
      {props.children}
    </li>
    )
}

const TodoListComponent = (props) => {
  return <ul>{props.children}</ul>
}

function App() {
  const myTodos = ["make pizza", "write blog post"]
  return (
    <TodoListComponent>
      {
        myTodos.map(item => <TodoItemComponent key={item}>{item}</TodoItemComponent>)
      }
    </TodoListComponent>
  )
}
Enter fullscreen mode Exit fullscreen mode
// Angular
@Component({
  selector: 'todo-item',
  template: `
    <input type="checkbox" />
    <ng-content></ng-content>
  `,
})
export class TodoItemComponent {}

@Component({
  selector: 'todo-list',
  template: `
    <ul>
      <ng-content></ng-content>
    </ul>
  `,
})
export class TodoListComponent {}

@Component({
  selector: 'app-root',
  template: `
    <todo-list>
      <li *ngFor="let item of myTodos">
        <todo-item>{{ item }}</todo-item>
      </li>
    </todo-list>
  `,
})
export class AppComponent {
  myTodos = ['make pizza', 'write blog post'];
}
Enter fullscreen mode Exit fullscreen mode

You can go to the view in the browser and mark checkboxes as "checked". Now inputs are in "not controlled state". It means they have a default behaviour and are not directly controlled by the framework. We cannot set values and handle events.

Every user interaction with the DOM emits an event, which, once emerged, bubbles up the HTML tree. In Vanilla JS we would use EventTarget.addEventListener(cb) method, which handles side effects in a callback.

A very similar principle applies to React and Angular, however, we don't have to care about adding and removing listeners, frameworks handle it for us. Let's try to handle change event on the checkboxes and output it to the console.

// React
const TodoItemComponent = (props) => {
  // Function that executes side-effects when event is emited
  const handleChange = (event) => console.log(event.target.checked)
  return (
    <li>
      <input type="checkbox" onChange={handleChange}/>
      {props.children}
    </li>
    )
}

const TodoListComponent = (props) => {
  return <ul>{props.children}</ul>
}

function App() {
  const myTodos = ["make pizza", "write blog post"]
  return (
    <TodoListComponent>
      {
        myTodos.map(item => <TodoItemComponent key={item}>{item}</TodoItemComponent>)
      }
    </TodoListComponent>
  )
}

export default App;
Enter fullscreen mode Exit fullscreen mode
// Angular
@Component({
  selector: 'todo-item',
  template: `
    <input type="checkbox" (change)="handleChange($event)" />
    <ng-content></ng-content>
  `,
})
export class TodoItemComponent {
  // Function that executes side-effects when event is emited
  handleChange(event) {
    console.log(event.target.checked);
  }
}

@Component({
  selector: 'todo-list',
  template: `
    <ul>
      <ng-content></ng-content>
    </ul>
  `,
})
export class TodoListComponent {}

@Component({
  selector: 'app-root',
  template: `
    <todo-list>
      <li *ngFor="let item of myTodos">
        <todo-item>{{ item }}</todo-item>
      </li>
    </todo-list>
  `,
})
export class AppComponent {
  myTodos = ['make pizza', 'write blog post'];
}
Enter fullscreen mode Exit fullscreen mode

If you now toggle the state of the checkboxes, you will see that corresponding boolean is logged to the console.

Communication with parent component

As we have seen Angular and React allow as to easily pass data down the component tree as props, in case of React, or via data binding in Angular. Let's now try to pass checkbox state to the AppComponent. While TodoItemComponent is aware of changes, how can we pass this information up to the parent?

React deals with this problem by passing a callback function as a prop to hook up the changes from children and update parent state.

Angular, in its turn, uses Outputs which allow to emit custom events and propagate them up to the parent component. Parent component, in return, is responsible for handling the event by providing a callback.

// React
const TodoItemComponent = (props) => {
  return (
    <li>
      <input type="checkbox" onChange={props.handleChange}/>
      {props.children}
    </li>
    )
}

const TodoListComponent = (props) => {
  return <ul>{props.children}</ul>
}

function App() {
  const myTodos = ["make pizza", "write blog post"]
  // Now we handle event in parent and pass down function as a prop
  const handleItemChecked = (event) => {
    const isChecked = event.target.checked
    console.log(`last checkbox state is ${isChecked}`);
  }
  return (
    <div>
      <TodoListComponent>
        {
          myTodos.map(item => (
            <TodoItemComponent
              key={item}
              handleChange={handleItemChecked}
            >
              {item}
            </TodoItemComponent>
          ))
        }
      </TodoListComponent>
    </div>
  )
}

export default App;
Enter fullscreen mode Exit fullscreen mode
// Angular
@Component({
  selector: 'todo-item',
  template: `
    <input type="checkbox" (change)="handleChange($event)" />
    <ng-content></ng-content>
  `,
})
export class TodoItemComponent {
  // Custom event emiter propagates data up to the parent
  @Output() itemChecked = new EventEmitter<boolean>();

  handleChange(event) {
    this.itemChecked.emit(event.target.checked);
  }
}

@Component({
  selector: 'todo-list',
  template: `
    <ul>
      <ng-content></ng-content>
    </ul>
  `,
})
export class TodoListComponent {}

@Component({
  selector: 'app-root',
  template: `
    <todo-list>
      <li *ngFor="let item of myTodos">
        <todo-item (itemChecked)="handleItemChecked($event)">{{
          item
        }}</todo-item>
      </li>
    </todo-list>
  `,
})
export class AppComponent {
  myTodos = ['make pizza', 'write blog post'];
  // Callback function for our custom event emited in the child
  handleItemChecked(isChecked: boolean) {
    console.log(`last checkbox state is ${isChecked}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Summary

React and Angular differ in approaches and style guides, however, they are trying to achieve the same goal and therefore provide similar tools to solve similar tasks. I personally find it easier to digest new chunks of knowledge when you can bridge them with something you already know and understand. In this post, we looked into some basics of both frameworks and how they are trying to solve problems like reusability of the components and dynamic rendering. Leave your feedback if you think this kind of approach can be of any help and share your experience of transitioning between frameworks.

Useful links

React

Angular

Latest comments (0)