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
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
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
- 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;
- 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
For clarity of code organization, I’ll depict schematically the way services are used for this example.
ThemeChangerService
must be common to bothThemeChangerComponent
andCookieClicker Context
, so both must be in the sameThemeChanger 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 cookieCookieClickerService
— 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
- 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
- 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
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.
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
- 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
- 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
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 injectRenderer2
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
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