Core Concepts

Let's look at the core concepts of React and Angular: How components work, what is already included and more.

A React component is, in the most simple case, a function that creates a React element. Batteries are not included: For certain tasks, you will need additional libraries.

An Angular component contains of a template that defines how the component is rendered and a class containing the component's logic. Batteries are included: Angular comes with everything you need to develop most single-page applications.

Component-Based

Both React and Angular are component-based, meaning that you will break down your application into components. And then you will break down those components into even smaller components.

Decomposing an application into components

Those components encapsulate everything needed to show a part of your application in the browser: The inputs they get from other components, internal state and application logic. They can also encapsulate CSS styles, images and other resources directly into the component.

Components are responsible for a certain part of the screen, like a single toolbar button in the image above. They define how this part of the screen should look and how it should behave: Components process events, update the internal application state and change the visual representation when the application state changes.

Components in React

A component is responsible for a part of the screen, like a single toolbar button. And in React, there are two ways to write such a component: As a class-based component or as a "simple" javascript function.

So, the following two components are mostly equivalent:

export function ToolbarButton(props: ToolbarButtonProps) {
  const [count, setCount] = useState(0);
  return <button onClick={e => { setCount(count+1) }}>
    {props.text} - {count}
  </button>
}
export class ToolbarButton extends React.Component<ToolbarButtonProps, { count: number }> {
  constructor(props: ToolbarButtonProps) {
    super(props)
    this.state = { count: 0 }
  }

  render() {
    return <button onClick={e => { this.setState({ count: this.state.count + 1 })}}>
      {this.props.text} - {this.state.count}
    </button>
  }
}

You can mix and match class-based components and function components in the same application. For the code that uses the component, it makes no difference how it was written. In both cases, you just use <ToolbarButton ...>.

Whether you wrote a certain component as a class-based component or a function component, there are two mental models that can be useful for thinking about how the component works. Both are not 100% correct, but good enough in most situations.

The first mental model is that a React component is just a function. This model is not accurate because your React component is always more than just a function, even when you wrote it as a function. But in most situations, you can think of your components as a function that gets the props and the internal state as an input and produces something that will appear on the screen:

Mental model: React component as a function

When you write your components so that the rendered result only depends on the props and the state of the component, this mental model holds true. And when it works, this is a very simple model for thinking about your application. props and state come in, and something appears or changes on the screen.

Your components, of course, do not render something on the screen directly. They create some representation that React understands—a React Element. React then translates this representation to something the browser understands, which in turn is responsible for displaying the result on the screen.

Just note that even though you can think of your components as working like that, in reality, there are differences. The most obvious of those differences is that the state is never an input to your component—it is something that the component uses internally.

Which brings us to the second mental model for thinking about React components: They are something that gets data from the outside via props, has an internal state, and also has a life cycle and a means of rendering a result:

Mental model: React component as a box that contains stuff

This model closely resembles the class-based component from above, but you can also think of your function components in this way.

In this mental model, components have:

  • props: Inputs they get from the outside, from further up the component hierarchy
  • state: An internal state that the component itself can manage
  • A life cycle: Components are mounted and then they display something; later they update; and when they are not needed anymore, they are unmounted
  • render: A way of rendering the result (initially and after every update)

You can think of a function component as only a render function without the class around it. But even such a function component still has the other three characteristics: It can get props from the outside, it can have an internal state and it definitely has a life cycle.

Note that the term render, which is used by class-based components, is a little bit misleading: It does not actually render something to the screen. It returns a data structure (React Elements) that tells React how to create DOM elements, which in turn tells the browser how to render the component.

Components in Angular

In Angular, a component consists of a class that contains the component's logic and an HTML template that defines the component's view. The view of a component mixes regular HTML code with Angular directives and binding markup, so Angular can modify the HTML (based on the component's logic) before displaying it.

Mental model: React component as a box that contains stuff

Components can get @Inputs from other components further up the hierarchy, can manage some internal state and have a life cycle. They also provide data to the view (which is rendered or bound in the HTML template) and can react to events from the view.

Components (and also modules and services) are TypeScript classes that use decorators. The decorators provide metadata about the component, its inputs, etc. For simple components, the template can be defined in the metadata of the component itself, but usually, it lives in a separate file:

@Component({
  selector: 'app-toolbar-button',
  templateUrl: './toolbar-button.component.html',
  styleUrls: ['./toolbar-button.component.css']
})
export class ToolbarButtonComponent implements OnInit {
  @Input() text: string = ''
  count: number = 0

  constructor() { }

  ngOnInit(): void {
  }

  onButtonClicked() {
    this.count++
  }
}
<button (click)="onButtonClicked()">
  {{text}} - {{count}}
</button>

This code provides the same functionality as the React components above, but it works differently: Angular compiles the template and uses it to render the view. The class containing the logic provides data to the view and controls the behavior of the component.

The component above uses two different kinds of binding:

  • {{text}} - {{count}}: "interpolation" - the double curly brackets and everything in between will be replaced by the result of the expression—in this case, the values of the text and count fields from the component class.
  • (click)="...": "event binding"—the parenthesis in "(click) denotes a one-way binding from the view to the data source using an event.

There are other kinds of binding you can use, so I will write more about data binding in one of the next sections.

Components use services: While in React, you are free to structure your application however you like (caveat: see below in "Component Structure"). The Angular way of providing additional functionality to components is to use services.

You can create a service with the CLI tool:

npm run ng generate service toolbar-events

And then inject this service into one or more components using the built-in dependency injection:

@Component({
  selector: 'app-toolbar-button',
  templateUrl: './toolbar-button.component.html',
  styleUrls: ['./toolbar-button.component.css']
})
export class ToolbarButtonComponent implements OnInit {
  constructor(private toolbarEventsService: ToolbarEventsService) { }

  ...
}

Components "live" in Modules: An Angular app is defined by a set of NgModules. Your application has at least one root module used for bootstrapping. In this root module, you must define which components your application provides and which other modules it needs:

@NgModule({
  declarations: [
    AppComponent,
    HelloComponent,
    ToolbarButtonComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Component Structure

Let's now look at how you can use components to structure your application and how to structure the code that makes up your components.

Since this is still the "Core Concepts" chapter, I will only quickly explain what you'll need to structure an application using components. I will go into more detail in the next chapter, Components, which comes after Getting Started.

Component Structure in React

When React components "render" their result, they return React Elements. And you can define how exactly this rendered result looks in plain TypeScript code.

For example, if you want to render some part of the application conditionally, you can evaluate this condition in the TypeScript code before returning the result:

export function Toolbar(props: ToolbarProps) {
  let labels = props.showLabels?
    <div>Toolbar</div> :
    undefined
  
  return <>
    {labels}
  </>
}

And you can use similar techniques for iteration and for mapping collections of data to child components (more on that in a later section). Remember: In your components, you use plain TypeScript code and JSX to build the result that will be shown in the browser.

Many of your components will be like that: They get some data, like a domain model, and they render a component based on that data.

But you will also write components that group other components or define how other components look. You will want to pass other components to such a component. E.g., a toolbar could get multiple toolbar buttons from the outside and render them in a horizontal grid.

<Toolbar showLabels={true}>
  <ToolbarButton text="func" />
  <ToolbarButtonClass text="class" />
</Toolbar>
type ToolbarProps = {
  children: React.ReactNode,
}

export function Toolbar(props: ToolbarProps) {
  return <>
    <div>{ props.children }</div>
  </>
}

When you pass in child components like the two ToolbarButtons to the Toolbar, the component will get those components in a special prop called children.

And since we are using TypeScript, you must also add this field to the ToolbarProps type. Unfortunately, there is no easy way to restrict the children to a particular React component (or a group of components) like ToolbarButton. So, you should just use React.ReactNode here, since it is the most general type for the children.

You can even write functions that create or modify components—those are called Higher-Order Components.

React does not limit you in any way when it comes to structuring your code. You can put all of your components—and everything else that makes up your application—in a single directory. But that is probably not a good idea.

I would strongly recommend grouping all your code by domain concepts. If, for example, your application deals, among other things, with users who can register, log in, edit their data, pay their bills, etc., you should group all code that belongs to users—such as React components, services, etc.—under a common folder. And you might want to create sub-folders to refine the grouping.

Do not group your code by purely technical concepts. E.g., do not create one folder for components, another for services, repositories and so on. That creates high coupling and low cohesion in those groupings and can become a maintenance nightmare later.

Component Structure in Angular

When you want to create some conditional output, you apply a structural directive to some HTML elements in your template:

<div *ngIf="showLabels">Toolbar</div>

showLabels refers to a field of the component class. And Angular will only render the whole <div> when showLabels is truthy. There can only be one structural directive (starting with an asterisk, like *ngIf) on a HTML element.

Iterating over a list of data or mapping data to some rendered result works in a similar way: You will define structural directives in your template that refer to data in the component class.

You can also write components that group other components or define how exactly other components should be rendered. In Angular, you can again do this in the template HTML using <ng-content>:

<app-toolbar>
  <app-toolbar-button text="angular 1"></app-toolbar-button>
  <app-toolbar-button text="angular 2"></app-toolbar-button>
</app-toolbar>
<div>
  <ng-content></ng-content>
</div>

Angular is somewhat opinionated about how you should structure your code. When you create a component with the Angular CLI, it will create a directory for that component.

npm run ng generate component user/login
src
+--- app
|    +--- user
|    |    +--- login
|    |    |    +--- login.component.html
|    |    |    +--- login.component.spec.ts
|    |    |    +--- login.component.ts
|    |    |    \--- login.component.css

When, on the other hand, you use the Angular CLI to create a service, it will put the code for the service directly into the parent directoy you specified:

npm run ng generate service user/login
src
+--- app
|    +--- user
|    |    +--- login.service.spec.ts
|    |    \--- login.service.ts

So, Angular already "forces" you to group the components. And in both cases, you can further group by domain concept—like user in both examples above.

Data Flow

React is based on one-way data flow, while Angular supports two-way binding. But the two-way binding in Angular is different from what you might expect, and under the hood, it uses events, which is also one way to pass data "the other way" in React.

Data Flow in React

In React, data flows down from the top (outer-most) component to the leaves.

Components can pass data down to their children, and that data will end up in the props of that child component:

export function Outer() {
	return <Inner someValue="concrete value" />
}
export function Inner(props: InnerProps) {
	return <button>{props.someValue}</button>
}

Components can also maintain some internal state. No other component than the component which defined it can access that state. But components can pass their internal state to their children via their props:

export function Outer() {
	const [ val, setVal ] = useState('concrete value')
	return <Inner someValue={val} />
}
export function Inner(props: InnerProps) {
	return <button>{props.someValue}</button>
}

In other words, React supports one-way data binding. When the val in the Outer component changes (via a call to setVal), the Inner component will be rendered again.

Remember that you can think of your React components as pure functions. The browser will always show the result of those functions based on the inputs props and state. So, you can also think of the one-way binding as working from your components to what you see in the browser. What is shown in the browser is always in sync with what your components rendered.

Since React only supports one-way binding, there is no built-in way to pass data "up" the component tree. If you want to do that, you must write some code. One of the easiest ways to do that is to use events (but you can also use more elaborate state-management solutions, like Redux):

function Inner(props: any) {
  return <button onClick={e => props.onInnerClicked() }>{props.someValue}</button>
}
function Outer() {
  const [val, setVal] = useState('concrete value')
  return <Inner onInnerClicked={() => setVal('clicked') } someValue={val} />
}

Here, Inner implements an event listener on the button. And when the button is clicked, it calls another listener—a function that it gets passed from Outer.

So, when a user clicks the button, the event listener in Inner will call props.onInnerClicked, which is an anonymous function in Outer that will call setVal. This, in turn, sets the state val, the component re-renders and Inner now shows the text "clicked".

But how does React determine exactly what to re-render? A very useful (albeit not 100% correct) mental model for that is: Whenever anything changes, React will re-render all components, and the blue box labeled React in the picture below takes care of ensuring that this is done really fast.

Mental model: React component as a box that contains stuff

Since this mental model is not 100% correct, we will re-visit it again in a future section, when we talk about performance.

Data Flow in Angular

Angular knows four different kinds data binding:

  • Interpolation: One-way from data source to view target ({{buttonText}})
  • Property / Attribute / ...: One-way from data source to view target ([value]="buttonText)
  • Event: One-way from view target to data source ((click)="onButtonClicked())
  • Two-Way: Two-way ([(count)]="itemCount")

Let's look into all of them in a little bit more detail.

Mental model: React component as a box that contains stuff

Interpolation runs some code, converts the result into a string and inserts that string into the rendered view. The code you can write inside the double curly brackets looks like regular TypeScript but is actually a template expression. Basically, it's a subset of TypeScript and a few special operators.

In many cases, you can simply bind a value from the component class to the view in this way.

export class InnerComponent implements OnInit {
  buttonText: string = '...';
}
<button (click)="clicked()">{{buttonText}}</button>

Use attribute binding to pass inputs via HTML attributes. On a regular HTML element, you can set an attribute in this way:

<input type="submit" [value]="submitButtonText" />

You can also use this type of binding to pass @Input() parameters down the component hierarchy from the outer component to the inner:

<app-inner [headline]="headline"></app-inner>
export class InnerComponent implements OnInit {
  @Input() headline: string = '';
}

Use event binding to pass data back up the component hierarchy. To do this, you need an EventEmitter in the inner component. And then, you can bind a callback function from the outer component class in the HTML template of the outer component:

<app-inner (onButtonClicked)="onInnerClickedOutput()"></app-inner>
export class InnerComponent implements OnInit {
  @Output() onButtonClicked = new EventEmitter<void>();

  clicked() {
    this.onButtonClicked.emit();
  }
}

You can also use two-way binding to pass data down from the outer to the inner component and then change that data in the outer component when it was modified in the inner. This kind of binding is "only" a convenient way to use an attribute binding and an event binding. So, while in the outer component, you use the syntax for two-way binding, you need an Input() field and an Output() event in the inner component, and they must follow a certain naming convention.

<app-inner [(buttonText)]="buttonText"></app-inner>
export class InnerComponent implements OnInit {
  @Input() buttonText: string = '';
  @Output() buttonTextChange = new EventEmitter<string>();

  clicked() {
    this.buttonTextChange.emit('text change: clicked');
  }
}

And when one of those events is emitted, Angular updates all the data and re-renders the single-page application. It does that by running what Angular calls change detection:

When an event occurs that might possibly change some data, Angular starts to check all components for whether some of their bound data has changed. It starts the change detection at the top of the component hierarchy and then moves down. When Angular has identified all of the components that need to be updated, it re-renders them.

For this to work, Angular must even patch several low-level browser APIs at startup. Then, it can run the change detection after every browser event, HTTP request, etc.

Batteries Included?

Angular comes with "batteries included". It comes with everything you need to develop single-page applications (at least most single-page apps). It contains tools and techniques for:

  • Modularization
  • Routing
  • Dependency Injection: Angular's way for doing inversion of control for dependencies
  • Handling Asynchronous Events
  • TypeScript
  • Testing Support

And it is also quite opinionated about them. For example, when you use the Angular CLI to create your components and services, it will create a folder structure for you.

React, on the other hand, is just a rendering library. It does not provide its own implementation for any of the bullet points above. But for some of them, like routing, there are great libraries. Others, like inversion of control for dependencies, are super-easy to implement yourself.

And while React is not based on TypeScript, it has great TypeScript support. So, React does not have the batteries included, but sometimes you don't need them at all, and they are also very easy to get.