title image

Understanding between Angular & React / Part 3: Services & Context API

Understanding between Angular & React. Part 3: Services & Context API

Examples of logic and code decomposition, extension and replacement on one simple application

Maksim Dolgikh
ITNEXT
Published in
18 min readApr 30, 2024

--

Previous part: Understanding between Angular & React. Part 2: Lifecycle hooks

Introduction

Services are an essential part of any modern application. They play a key role in organizing and scaling code.

They are used to organize and exchange data between components, as well as to perform common tasks such as processing HTTP requests, working with data, authentication, caching, and others.

The main benefits of using services are:

  • Separation of areas of responsibility between parts of the application
  • Reusing code
  • Sharing data or state between different component layers
  • Easy testing

As part of building a simple app, I would like to a show what tools Angular and React provide to ensure code scalability and how easy it is to create and use services for the needs of the app

Separation of Responsibilities

Initial Task

Let’s say our task is to develop a simple Cookie Clicker Game.
The point of the game is simple — you click on a cookie and increase the counter by +1

cookie-clicker game
Cookie Clicker Game

Angular implementation

The first implementation option which has the right to exist is to write all the game logic in one component

@Component({
selector: 'ng-cookie-clicker',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="cookie-clicker-active-zone" (click)="onAddCookie()">
<img [src]="cookieSkinUrl" class="cookie-clicker-skin">
</div>
<p class="cookie-clicker-counter">{{cookies()}} cookies</p>
`,
})
export class CookieClickerComponent {
public readonly cookies = signal(0);

public readonly cookieSkinUrl = '/assets/cookie.png';

public onAddCookie(): void {
this.cookie.set(s => s + 1);
}
}

This code will work fine, but as the application grows and the requirements (for customization, additional counters, etc.) increase, you will find it more and more difficult to maintain this code.

In any application you develop, the MVC design pattern should be followed first and foremost. Within this pattern, a component is only responsible for displaying any data and for delegating events, and the component should perform no other logic. The model for displaying and the logic for updating it can be separated into its own layer

new work scheme of current game
  1. Service

First of all, let’s put all the logic of the state updating into a separate class.

@Injectable()
export class CookieClickerService {
public readonly cookies = signal(0);

public addCookie(): void {
this.cookies.update(s => s + 1);
}
}

For a class to become a service, from Angular’s point of view, it only needs to add the @Injectable() decorator and nothing else.

2. Component

You may notice that the logic of the component has changed, but the template itself and its bindings remain the same

@Component({
selector: 'ng-cookie-clicker',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="cookie-clicker-active-zone" (click)="onAddCookie()">
<img [src]="cookieSkinUrl" class="cookie-clicker-skin">
</div>
<p class="cookie-clicker-counter">{{cookies()}} cookies</p>
`,
})
export class CookieClickerComponent {
+ private readonly cookieClickerService = inject(CookieClickerService);

- public readonly cookies = signal(0);
+ public readonly cookies = this.cookieClickerService.cookies;

public readonly cookieSkinUrl = '/assets/cookie.png';

public onAddCookie(): void {
- this.cookie.set(s => s + 1);
+ this.cookieClickerService.addCookie();
}
}

Let’s break it down in order:

  • To use any services in Angular, we need to use the inject() function. Thanks to it we get the created service with all the necessary states.
private readonly cookieClickerService = inject(CookieClickerService);
  • Replacing the game logic in the component with the work using the service
public readonly cookies = this.cookieClickerService.cookies;

public onAddCookie(): void {
this.cookieClickerService.addCookie();
}

3. Host component

The host component is an additional abstraction level for the Cookie Clicker application and creates its environment. Within this component, we make the providers of all the necessary services for the application.

@Component({
selector: 'ng-cookie-app-host',
template: `
<ng-cookie-clicker class="cookie-clicker-container"></ng-cookie-clicker>
`,
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CookieClickerComponent],
providers: [CookieClickerService]
})
export class CookieAppHostComponent {}

There is nothing preventing you from registering the service directly in the CookieClickerComponent, but this will cause the component to become closed for extension, which I will show you later

If we don’t register the CookieClickerService, when we call the inject() function in the CookieClickerComponent, Angular won’t find the required service and everything will fail.

NullInjectorError: R3InjectorError(Environment Injector)[_CookieClickerService -> _CookieClickerService]:
NullInjectorError: No provider for _CookieClickerService!

So it’s important to always control this process, and Angular has a lot of options for this — providers: [CookieClickerService] is one of them

React implementation

Similar to Angular, we will also create a component with all the logic first, but we will also divide it into separate parts

const CookieClicker = () => {
const [cookies, setCookies] = useState(0);

const onAddCookie = () => {
setCookies((s) => s + 1);
};

const cookieSkinUrl = '/assets/cookie.png';

return (
<>
<div className="cookie-clicker-active-zone" onClick={onAddCookie}>
<img src={cookieSkinUrl} className="cookie-clicker-skin" />
</div>
<p className="cookie-clicker-counter">{cookies} cookies</p>
</>
);
};
export default CookieClicker;
  1. Context

React has its own mechanism for creating and providing data for a component called Context API. It is similar to the logic of providing services in Angular because we get data from some “context”. But the mechanisms for creating such “services” are much different

const CookieClickerContext = createContext<null | {
cookies: number;
addCookie: () => void;
}>(null);

const CookieClickerProvider: React.FC<PropsWithChildren> = ({ children }) => {
const [cookies, setCookies] = useState(0);
const addCookie = () => {
setCookies((s) => s + 1);
};

return (
<CookieClickerContext.Provider
value={{
cookies,
addCookie,
}}
>
{children}
</CookieClickerContext.Provider>
);
};

const useCookieClickerContext = () => useContext(CookieClickerContext)!;

export { useCookieClickerContext, CookieClickerProvider };

This implementation is created according to the provider pattern

Let’s break it down in order:

  • Everything starts by creating a “context” via the createContext() function. It allows you to declare the initial implementation and the interface of the data that components will use.
const CookieClickerContext = createContext<null | {
cookies: number;
addCookie: () => void;
}>(null);
  • We create a component wrapper for the context, within which components can receive data from CookieClickerContext. In it, methods and functions are created to work within the context operation.
const CookieClickerProvider: React.FC<PropsWithChildren> = ({ children }) => {
const [cookies, setCookies] = useState(0);
const addCookie = () => {
setCookies((s) => s + 1);
};

return (
<CookieClickerContext.Provider
value={{
cookies,
addCookie,
}}
>
{children}
</CookieClickerContext.Provider>
);
};

All the required data and methods that the context needs are passed in value to the context wrapper CookieClickerContext.Provider.

Components that read CookieClickerContext are placed down the component tree from the Provider by children.

  • To read the context inside a component, we will create our own hook, which will help to hide the details of the implementation of context retrieval and provide a declarative entry point. The useContext hook is used to get the context.
const useCookieClickerContext = () => useContext(CookieClickerContext)!;
  • Within the framework of encapsulation, we provide only those functions for working with context that can be used. It is possible to export all of them, but then there is a high probability of making the code fragile due to custom context changes in unexpected places
export { useCookieClickerContext, CookieClickerProvider };

2. Component

const CookieClicker = () => {
+ const cookieContext = useCookieClickerContext();

- const [cookies, setCookies] = useState(0);
+ const cookies = cookieContext.cookies;

const onAddCookie = () => {
- setCookies((s) => s + 1);
+ cookieContext.addCookie();
};

const cookieSkinUrl = '/assets/cookie.png';

return (
<>
<div className="cookie-clicker-active-zone" onClick={onAddCookie}>
<img src={cookieSkinUrl} className="cookie-clicker-skin" />
</div>
<p className="cookie-clicker-counter">{cookies} cookies</p>
</>
);
};
export default CookieClicker;

Let’s break it down in order:

  • Get the “context” to work with the useCookieClickerContext hook
const cookieClickerContext = useCookieClickerContext();
  • Replacing the game logic in the component with the work using the service
const cookies = cookieClickerContext.cookies;

const onAddCookie = () => {
cookieClickerContext.addCookie();
}

3. Host component

In order to register the provider and make the context available to its consumers, all components that use that context must be placed within the CookieClickerProvider creation.

const CookieAppHost = () => {
return (
<CookieClickerProvider>
<div className="cookie-clicker-container">
<CookieClicker></CookieClicker>
</div>
</CookieClickerProvider>
)
}

export default CookieAppHost;

Using the provider directly in the source component would lead to an error because the context provider is created later than the code where it is used. Instead of the required data handling methods, we would get default values from the createContext() initiation, namely null.

Composition of providers

New task

Our application is growing and now needs to take external factors into account, such as the user’s global design theme.

Each time the theme changes, we will change the cookie image to match the corresponding theme

change cookie skin showcase
Change cookie skin

For clarity of code organization, I’ll depict schematically the way services are used for this example.

scheme of providers and components
scheme of providers and components
  • ThemeChangerService must be common to both ThemeChangerComponent and CookieClicker Context, so both must be in the same ThemeChanger Context
  • ThemeChangerComponent is for demonstration purposes, so its implementation will be skipped. The component is located “somewhere” in the application and is only responsible for the manual theme switch trigger

It is possible to add new logic for handling the skinUrl to the current CookieClickerService, but that would violate SOLID principles.

That’s why I suggest dividing the areas of responsibility into separate services, where:

  • CookieClickerSkinUrl — provider of the primitive and reactive value of the image URL to the cookie
  • CookieClickerService — provider of game state with counter update logic, remains unchanged

Yes, it is possible to additionally introduce Facade Service, but for the current example it will lead to unnecessary complexity

Angular implementation

  1. ThemeChangerService

ThemeChangerService is a regular stateful service with corresponding logic for storing the state and methods of working with it.

export type Theme = 'light' | 'dark';

@Injectable({
providedIn: 'root'
})
export class ThemeChangerService {
private readonly _theme = signal<Theme>('light');
public readonly theme = this._theme.asReadonly()

public setTheme(value: Theme): void {
this._theme.set(value);
}
}

The only difference from the CookieClickerService is the setting of the @Injectable decorator. In it, we can specify the service registration level without having to manually register the service in components.

Angular provides several levels of service registration which can be upward from the original entry point of the application (read more)

For this example and our implementation, ‘root’ is sufficient. Angular will create this service as the single and root service for the entire application, ensuring that all subsequent consumers of this class will reference the same class and its state

2. CookieClickerSkinUrl

In Angular, there is an alternative way to create providers called Injection Token. This is a more flexible way of providing a value through the DI mechanism because you can create any type of value other than an object value like a class.

const skins: Record<Theme, string> = {
light: '/assets/cookie.png',
dark: '/assets/dark-cookie.png',
};

export const COOKIE_CLICKER_SKIN_URL: InjectionToken<Signal<string>> =
new InjectionToken('COOKIE_CLICKER_SKIN_URL', {
factory: () => {
const themeService = inject(ThemeChangerService);

return computed(() => {
const theme: Theme = themeService.theme();

return skins[theme] || skins['light'];
});
},
});

A token can be created simply as a constant to be referenced later, but can also be provided with a default value via the factory() function.

The factory() works within the Injection Context, which allows services to be requested directly via inject()

As part of the current task, we need to adapt the value of the current topic to the value of the URL. To adapt the reactive value of a topic, computed is used as a way to convert one signal into another

3. CookieClickerComponent

It is necessary to replace the skinUrl in the component with a skinUrl from the service. All we have to do is get the provider via inject()

@Component({
selector: 'ng-cookie-clicker',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="cookie-clicker-active-zone" (click)="onAddCookie()">
- <img [src]="cookieSkinUrl" class="cookie-clicker-skin">
+ <img [src]="cookieSkinUrl()" class="cookie-clicker-skin">
</div>
<p class="cookie-clicker-counter">{{cookies()}} cookies</p>
`,
})
export class CookieClickerComponent {
private readonly cookieClickerService = inject(CookieClickerService);

public readonly cookies = this.cookieClickerService.cookies;

- public readonly cookieSkinUrl = '/assets/cookie.png';
+ public readonly cookieSkinUrl = inject(COOKIE_CLICKER_SKIN_URL)

public onAddCookie(): void {
this.cookieClickerService.addCookie();
}
}

Didn’t you notice anything strange?

COOKIE_CLICKER_SKIN_URL has not been registered anywhere, but the task is completed and the code works. Why? — by declaring factory() Angular has resolved the dependency and registered it as part of the component creation, without our involvement.

React implementation

  1. ThemeChangerProvider

Similarly to the creation of CookieClickerContext, let’s implement a provider for ThemeChangerContext. Provide methods of working with the context via value

export type Theme = 'light' | 'dark';

const ThemeChangerContext = createContext<{
theme: Theme;
setTheme: (value: Theme) => void;
} | null>(null);

const ThemeChangerProvider: React.FC<PropsWithChildren> = ({ children }) => {
const [theme, setTheme] = useState<Theme>('light');

return (
<ThemeChangerContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeChangerContext.Provider>
);
};

const useThemeChanger = () => useContext(ThemeChangerContext)!

export {ThemeChangerProvider, useThemeChanger}

2. CookieClickerSkinUrl

React doesn’t introduce any alternative ways to declare context, instead providing versatility of use. You decide what you want to provide in value — object, array, function, or primitive value.

For our case, we simply provide the calculated skinUrl value directly to value

const CookieClickerSkinUrlContext = createContext<null | string>(null);

const CookieClickerSkinUrlProvider: React.FC<PropsWithChildren> = ({
children,
}) => {
const { theme } = useThemeChanger();
const skins: Record<Theme, string> = {
light: '/assets/cookie.png',
dark: '/assets/dark-cookie.png',
};

const skinUrl = skins[theme] || skins['light'];

return (
<CookieClickerSkinUrlContext.Provider value={skinUrl}>
{children}
</CookieClickerSkinUrlContext.Provider>
);
};

const useCookieClickerSkinUrl = () => useContext(CookieClickerSkinUrlContext)!;

export { CookieClickerSkinUrlProvider, useCookieClickerSkinUrl };

3. HostComponent

React doesn’t give you complex features to work with context, but there is a downside — provider composition.

Unlike Angular, React doesn’t have its own dedicated layers for provider registration. Each provider that is created and will be used must be used as a “parent component” for the rest of the consumers of its “context”.

const CookieAppHost = () => {
return (
<ThemeChangerProvider>
<ThemeChanger></ThemeChanger>
<CookieClickerSkinUrlProvider>
<CookieClickerProvider>
<div className="cookie-clicker-container">
<CookieClicker></CookieClicker>
</div>
</CookieClickerProvider>
</CookieClickerSkinUrlProvider>
</ThemeChangerProvider>
);
};

export default CookieAppHost;

For this implementation, we need CookieClickerSkinUrl to be down the tree from ThemeChangerProvider. This makes sense because you can’t get ThemeChangerContext if you are above the place where it is created

For the same reason, the ThemeChanger component must be within the ThemeChangerProvider if we want to modify and use the same context

4. CookieClickerComponent

The last step is to replace the skinUrl in the component, with getting the skinUrl from the useCookieClickerSkinUrl

const CookieClicker = () => {
const cookieContext = useCookieClickerContext();

const cookies = cookieContext.cookies;

const onAddCookie = () => {
cookieContext.addCookie();
};

- const cookieSkinUrl = '/assets/cookie.png';
+ const cookieSkinUrl = useCookieClickerSkinUrl();

return (
<>
<div className="cookie-clicker-active-zone" onClick={onAddCookie}>
<img src={cookieSkinUrl} className="cookie-clicker-skin" />
</div>
<p className="cookie-clicker-counter">{cookies} cookies</p>
</>
);
};
export default CookieClicker;

Redefining and decomposing the providers

Due to the current decomposition of the game logic and representation, another example can be made of changing the rules for each individual game

New task

You need to implement 2 games running simultaneously on 1 screen:

  • For the first game, the rules do not change, it remains the same
  • For the second game, each click on a cookie increases the counter by +5

Also, games should have a common ThemeChanger Context to synchronize theme changes

example of 2 games running at the same time
Example of 2 games running at the same time

If we visualize the provider scheme, in the context of the second application, we need to create an alternative CookieClickerService with new rules and games, which will be referenced by the CookieClickerComponent. The component will also call the addCookie() method, but the rules for changing the number of cookies will be different.

scheme of providers for 2 games
scheme of providers for 2 games

Angular Implementation

In Angular, the levels of provider creation can be categorized into:

  • Environment Injector (Root Injector, Null Injector, etc.) — the level of registering services outside the component tree. (ThemeChangerService)
  • Element Injector is the level of creating services within an Angular element. Since any component in Angular is an element, any component can create its ElementInjector and override services for descendants. (CookieClickerService, COOKIE_CLICKER_SKIN_URL)

More details about the provider tree in Angular can be found here

Since Environment Injectors are global providers, you will need to create two separate Element Injectors with different game rules for each game.

Or in simpler language, for each game, you need to create a separate component, in which you need to create a different game rule provider for the CookieClickerComponent component

  1. Game 1

This component follows the logic of the CookieAppHostComponent presented in the previous sections.

@Component({
selector: 'ng-cookie-clicker-game-1',
template: `
<ng-cookie-clicker class="cookie-clicker-container"></ng-cookie-clicker>
`,
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [CookieClickerService],
imports: [CookieClickerComponent],
})
export class CookieClickerComponentGame1 {}

The only thing to pay attention to is the field
providers: [CookieClickerService]. In it, you can both create and override one or more services for the descendants that use them.

If you do not change the current CookieAppHostComponent and leave the list of providers unchanged, the CookieClickerComponentGame1 component providers will be prioritized for descendants because they are closer in the component tree.

2. Game 2

Finally, we can get down to defining the rules for the second game.

@Component({
selector: 'ng-cookie-clicker-game-2',
template: `
<ng-cookie-clicker class="cookie-clicker-container"></ng-cookie-clicker>
`,
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: CookieClickerService,
useFactory: () => {
const cookies = signal(0);
const addCookie = () => {
cookies.update((s) => s + 5);
};

return { cookies, addCookie };
},
},
],
imports: [CookieClickerComponent],
})
export class CookieClickerComponentGame2 {}

Service redefinition consists of two steps:

  • Specify in the provide field which service or token will be defined
provide: CookieClickerService
  • Define new values to replace the old service with one of the configuration methods
 useFactory: () => {
const cookies = signal(0);
const addCookie = () => {
cookies.update((s) => s + 5);
};

return { cookies, addCookie };
}

Angular has a very flexible system for creating providers

3. Host Component

Let’s announce two new games within the current host component

@Component({
selector: 'ng-cookie-app-host',
template: `
<ng-services-theme-changer
class="theme-changer-container"
></ng-services-theme-changer>

- <ng-cookie-clicker class="cookie-clicker-container"></ng-cookie-clicker>-->
+ <div style="display: flex; gap: 4px">
+ <ng-cookie-clicker-game-1></ng-cookie-clicker-game-1>
+ <ng-cookie-clicker-game-2></ng-cookie-clicker-game-2>
+ </div>
`,
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ThemeChangerComponent,
- CookieClickerComponent,
+ CookieClickerComponentGame1,
+ CookieClickerComponentGame2,
],
- providers: [CookieClickerService],
})
export class CookieAppHostComponent {}

You can get rid of the old CookieClickerService registration, as it is not used anywhere else

React implementation

As I said earlier, React doesn’t have dedicated layers for registering providers, and you have to manually set a provider for each component tree (analogous to Element Injector in Angular). But they also work by the neighbour rule, where the context that is registered later in the current component tree will be used

  1. Game 1

Similar to the Angular implementation, we need to create a component with a separate provider for the first game

const Game1 = () => {
return (
<CookieClickerProvider>
<div className="cookie-clicker-container">
<CookieClicker></CookieClicker>
</div>
</CookieClickerProvider>
);
};

2. Game 2

Create a new component provider for the second game

const Game2 = () => {
const [cookies, setCookies] = useState(0);
const addCookie = () => {
setCookies((s) => s + 5);
};

return (
<CookieClickerContext.Provider value={{ cookies, addCookie }}>
<div className="cookie-clicker-container">
<CookieClicker></CookieClicker>
</div>
</CookieClickerContext.Provider>
);
};

Context redefinition consists of two steps:

  • Creating new game logic
 const [cookies, setCookies] = useState(0);
const addCookie = () => {
setCookies((s) => s + 5);
};
  • Provide a new value for the provider in value, which will be used as a context for components
<CookieContext.Provider value={{ cookies, addCookie }}>
...components
</CookieContext.Provider>

3. Host Component

Finally, let’s add two new games to the host component

const CookieAppHost = () => {
return (
<ThemeChangerProvider>
<ThemeChanger></ThemeChanger>
<CookieClickerSkinUrlProvider>
- <CookieClickerProvider>
- <div className="cookie-clicker-container">
- <CookieClicker></CookieClicker>
- </div>
- </CookieClickerProvider>
+ <div style={{ display: 'flex', gap: '4px' }}>
+ <Game1></Game1>
+ <Game2></Game2>
+ </div>
</CookieClickerSkinUrlProvider>
</ThemeChangerProvider>
);
};

In both cases, in addition to tampering with the game logic, it will be possible to change the provider of skinUrl, providing the ability to customize the game without directly modifying components

In Conclusion

At first glance, it may seem that registering and using providers in React is easier and more intuitive than in Angular. But the basic examples discussed in this article work well within Angular, not React

Getting a data update

The main point of using providers is to avoid effects like “props drilling”- where child components need to pass some data from parent components through several layers of other components that don’t need this data, which causes unnecessary redraws and performance loss.

Angular

At the moment (04.2024), not everything is so clear about updating components, in the form of the presence of an old API by ZoneJs. Any update to a child component marks the current branch of the component tree up to the root as necessary to check for a change. Your changes don’t happen in isolation.

If you don’t use OnPush, other branches will be checked as well, which will definitely affect performance

React

Changing the state of a context provider also causes all its child components that are consumers of that context to be redrawn.

On the one hand that’s the point — “changed? — needs to be updated”, but if it’s an object value, the data may still be the same, although the value will be “new”, which will trigger an update

This is why the Context API in React should only be used:

  • For global states that change infrequently and should affect the entire application — current theme, authorization or localization
  • For small, dedicated areas of the component tree with a specific state only for that area

If you want to use some state between multiple component trees and maintain performance, look to the new solution — Zustand, as your primary tool for state management.

Promote decomposition

For example, we need to implement a service usage pattern according to the following scheme

example - tree of providers

Angular

It is enough to declare services once per provider, without thinking about dependencies concerning each other.

providers: [ServiceA, ServiceD, FacadeService, ServiceC, ServiceB],

Even if cyclic use of services is detected, Angular will report it.

You can infinitely break logic into services and embed them back into the code, thanks to the DI mechanism. You can implement any design patterns within the DI framework easily

React

You would have to create this tree manually

<ServiceA>
<ServiceB></ServiceB>
<ServiceC>
<ServiceD>
<FacadeService>
{children}
</FacadeService>
</ServiceD>
</ServiceC>
</ServiceA>

The subsequent expansion and addition of a new service would require taking into account the hierarchy of providers.

Do you need the Context API mechanism if you can create functions that require entities of a particular class as arguments? — Probably no

Availability of dedicated zones

Angular

The magic of dependency resolution in Angular can be disastrous in the “wrong hands”, because you may have services needed by only a small part of the application registered globally via providedIn: ‘root’ or providedIn: ‘platform’.

If you figure out this mechanism in Angular, it will serve you well. You are always sure that you are getting the only implementation of the service used for the rest of the application

React

You don’t have dedicated zones, and all providers need to be declared at the very root of the component tree.

Therefore, this kind of code can be considered the normal

<ThemeContextProvider>
<UserContextProvider>
<LanguageContextProvider>
<AuthContextProvider>
<SettingsContextProvider>
<NotificationContextProvider>
<Layout />
</NotificationContextProvider>
</SettingsContextProvider>
</AuthContextProvider>
</LanguageContextProvider>
</UserContextProvider>
</ThemeContextProvider>

To avoid such code, you can create HoC-functions to register providers

Interaction with some API

Angular

Angular provides additional layers of abstraction when interacting with any API in the browser:

  • Do you want to make a request? — to inject HttpClient
  • Do you want to change the properties of an HTML-element? — to inject Renderer2 service
  • Do you want to interact with document API? — to inject DOCUMENT token

You will also create tokens or wrapper services over the native API, and register the services for later reuse. The subsequent code development will maximize the use of services and DI.

The point of the additional layers is to provide a single entry point without affecting the code, with the ability to tamper with the logic if we work with SSR

React

You don’t need to use the Context API and create providers:

  • Do you want to make a request? — you can use fetch() function
  • Do you want to change the properties of an HTML-element? — interact directly with elements without additional classes
  • Do you want to interact with document API? — interact directly

React allows you to work directly with the browser API because of the “use client” and “use server” directives. You don’t have a universal code that executes in different environments, and you’re talking about where that code can execute.

Last word

Services and tokens are an important part of Angular, but React can do without them by using a function or working with native APIs without using the Context API.

Angular is about creating complex SPA applications. There can be countless external factors that need to be considered before the final screen for the user.

React encourages simplicity. A lot of code is not always a good solution. The frontend should be as silly as possible, and the logic should be put on the backend

Whatever technology you use, the thing to remember is — don’t create complex components and ensure SOLID principle

Chapters

Before we talk about ways to optimize, I’d like to break down another Angular entity — directives

Next

Understanding between Angular & React. Part 4: Working with DOM

Reading List

Understanding between Angular & React

4 stories
main image
title image
cover of story

My content is often saved to favourites, but unfortunately, Medium’s algorithms also look at the number of claps a story has.

If my content was useful, not only save it but also give your “clap” as well. This helps promote the content

All the cookies that were clicked on were given to Cookie Monster

“Om Nom Nom Nom” Want to know what true love sounds like?
“Om Nom Nom Nom” Want to know what true love sounds like?

--