Before we can dive into how many front-end frameworks that you may have heard of work, we need to set a baseline of information. If you're already familiar with how the DOM represents a tree and how the browser takes that information and utilizes it, great! You're ready to read ahead! Otherwise, it's strongly suggested that you take a look at our post introducing the concepts required to understanding some of the baseline to this post
You may have heard about various frameworks and libraries that modern front-end developers utilize to build large-scale applications. Some of these frameworks you may have heard of are Angular, React, and Vue. While each of these libraries bring their own strengths and weaknesses, many of the core concepts are shared between them.
With this series of articles, we're going to be outlining core concepts that are shared between them and how you can implement them in code in all three of the frameworks. This should provide a good reference when trying to learn one of these frameworks without a pre-requisite knowledge or even trying to learn another framework with some pre-requisite of a different one.
Components
Let's first explain why frameworks like Angular, React, or Vue differ from other libraries that may have come before it, like jQuery.
It all comes down to a single core concept at the heart of each of them: Componentization.
The idea of a component is that you have a modular system of HTML, JS, and CSS comprised of individual "components" that you them compose to make up a larger UI. For example, instead of one HTML file that contains:
- Header
- Sidenav
- Form logic
- Footer
You might have a component for each of them and end up with a component to roll them all into a single page like this:
<div>
<header></header>
<sidenav></sidenav>
<form-logic></form-logic>
<footer></footer>
</div>
As you'll notice, the layout of these "components" is very similar to the way we structure a DOM tree
Virtual DOM
Please note that "Virtual DOM" is somewhat of a catch-all term (and a devisive one at that). There is no standard definition of what a virtual DOM is or what it looks like, even, and there is huge contestions between whether we should standardize that concept at all or not
One of the main advan
Inputs
Outputs
Fragments
One of the advantages that having a virtual representation of the DOM is that you're able to have a non 1:1 layout of virtual node to real DOM node. What does this mean? Consider a scenario like this: let's say that you have various CDNs for different media types to place into a video tag. However, instead of hardcoding the CDN delivery every time, you want to end up with a single video name and a component that pre/post-pends the rest of the URL to the correct source location
---------- Angular --------
<video>
<custom-html-video-sources [videoName]="'best-video-ever'"></custom-html-video-sources>
</video>
---------- React --------
<video>
<custom-html-video-sources videoName={'best-video-ever'}/>
</video>
---------- Vue --------
<video>
<custom-html-video-sources :videoName="'best-video-ever'"></custom-html-video-sources>
</video>
In this example, we may want to have the video
tag and seperate out the source
tags. Something like this might apply:
---------- Angular --------
@Component({
selector: "custom-html-video-sources",
template: `
<ng-container>
<source [src]="'https://cdn.example.com/mp4/' + videoName + '.mp4'"/>
<source [src]="'https://cdn.example.com/mp4/' + videoName + '.mp4'"/>
</ng-container>
`
})
class CustomHTMLVideoComponent {
@Input() videoName: string;
}
---------- React --------
<custom-html-video videoTypes={[
{
type: 'mp4',
src: 'video.mp4'
},
{
type: 'mkv'.
src: 'video.mkv'
}
]}/>
---------- Vue --------
<custom-html-video :videoTypes="[
{
type: 'mp4',
src: 'video.mp4'
},
{
type: 'mkv'.
src: 'video.mkv'
}
]"/>
Content Projection
---------- Angular --------
---------- React --------
const Component = ({children}) => {
<>
{children}
</>
}
---------- Vue --------
Named Scopes
Similar to how content projection works by displaying children of a component where present, you're also able to do this for more than a single set of children by providing what's called a "named slot" in order to project various peices of UI in more than a single location. For example, you could have an AppLayout
component that would provide a place to insert a sidenav bar, main set of content, and more. This would allow you to project dynamic content where expected while keeping a consistent UI layout for the primary elements of your app
---------- Angular --------
@Component({
selector: "app-layout",
template: `
<ng-container>
<ng-content></ng-content>
</ng-container>
`
})
class AppLayoutComponent {
}
---------- React -------- Because a JSX tag simply returns a value as if it were any other function, you're able to utilize the props inside of a React component in order to pass those values and display them
const AppLayout = ({sidebar, mainContent}) => {
<div>
{sidebar}
<header>{content}</header>
<main>
{mainContent}
</main>
</div>
}
const App = () => {
return <AppLayout
sidebar={<Sidebar/>}
mainContent={<AppContent/>}
/>
}
---------- Vue --------
Lifecycle methods
If you've used vanilla JavaScript for a project for an extended period of time, you may be familiar with document.onReady
in order to load some code opperations when the DOM is fully loaded.
Event Listeners
Various frameworks even build upon the system the DOM itself has for raising state. Oftentimes, you're able to raise state by using events.
If you've used a Google Maps API before, you may be familiar with this pattern.
Portals
There may be times where you might want to render a component from one part of your virtual DOM to an entirely different place in the browser DOM. A good usecase for something like this might be something like a modal that you want to render at the root of the DOM in order to avoid z-indexing.
However, this modal needs to contain properties from part of your component system and emit a response back to that same part of the virtual DOM.
---------- React --------
Consider an example where you might have a dialog-display
component with an onAccept
property, an onReject
property and a message
property:
<div>
<header></header>
<main>
<section>
{shouldShow &&
<dialog-display
message="Licence agreement, do you accept?"
onReject={handleClode}
onAccept={saveResponse}
/>
}
<p>Hello there!</p>
</section>
</main>
</div>
---------- Angular --------
Consider an example where you might have a dialog-display
component with an accept
event emitter, a reject
event emitter and a message
property:
<div>
<header></header>
<main>
<section>
<dialog-display
*ngIf="shouldShow"
[message]="'Licence agreement, do you accept?'"
(reject)="handleClode($event)"
(accept)="saveResponse($event)"
>
</dialog-display>
<p>Hello there!</p>
</section>
</main>
</div>
---------- Vue --------
Consider an example where you might have a dialog-display
component with an accept
event emitter, a reject
event emitter and a message
property:
<div>
<header></header>
<main>
<section>
<dialog-display
v-if="shouldShow"
:message="'Licence agreement, do you accept?'"
@reject="handleClode($event)"
@accept)="saveResponse($event)"
>
</dialog-display>
<p>Hello there!</p>
</section>
</main>
</div>
But to have the following display in the DOM, when the component renders:
<html>
<body>
<div class="display-dialog">
<p>Licence agreement, do you accept?</p>
<button>Accept</button>
<button>Reject</button>
</div>
<div>
<header></header>
<main>
<section>
<p>Hello there!</p>
</section>
</main>
</div>
</body>
</html>
In this example, when the user selects accept
or reject
, it passes the values "up" to the section, despite not being a child in the browser DOM due to it's placement in the virtual DOM. Data passed in also shows up as-expected, again, despite not being a child of that element in the browser.
Depending on implementation, even DOM events (click
ing, focus
events, and others) are even passed to the respective component section
rather than to the parent body
tag
Angular | React | Vue | Notes |
---|---|---|---|
Portals* | Portals | No Official API | * Angular CDK is what contains the Portal logic rather than built into Angular. It interacts differently with event bubbling/etc than how React (which is built in and uses VirtualDOM instead of template injection) works |
Side Effects
Composing
A Note About Standards
Please do note that while there is web components, they may not serve the same functional purpose. Some frameworks you may be aware of oftentimes even play nicely with web components
https://custom-elements-everywhere.com/
Similarities:
Angular | React | Vue | Notes |
---|---|---|---|
{{}} |
{} * |
{{}} |
* JSX (React) handles this a bit differently than the others. While you can use some JS in Vue and Angular, you can run all JavaScript (JS) within JSX |
[prop] |
prop={} |
v-bind:prop="" /:prop="" |
|
(event) |
Functions passed via props | v-on:event /@event |
|
OnChanges | componentDidUpdate */useEffect |
watch /vm.$watch **/watchEffect |
*This is a bit different. This is called when a render is called. This is because of the differences between local state and not with React/others ** This only listens to a single properties and not a list of others |
constructor / OnInit |
constructor /ComponentDidMount/useEffect(()=>{}, []); |
created /mounted /onMounted |
|
OnDestroy | ComponentWillUnmount/Return useEffect |
beforeDestroy /onBeforeDestroy |
|
*ngFor | map with key in JSX |
v-for with key |
|
*ngIf | Ternary operator with a null /undefined to prevent rendering |
v-if |
|
OnChanges preveousValue diff checking/ChangeDetection.OnPush* |
ShouldComponentUpdate**/PureComponent/React.memo /React Forget |
* ChangeDetection.OnPush does NOT directly do the same thing as PureComponent as it pertains to a very specific Angular-only logic. That being said, if you're familiar with OnPush . ** In the future React may treat shouldComponentUpdate() as a hint rather than a strict directive, and returning false may still result in a re-rendering of the component. |
|
ChangeDetectorRef* | forceUpdate | vm.$forceUpdate() ** |
* This does not guarantee that the component will re-render - see back to differences between Angular and React **Does not apply to children |
[attr.atrib]="val" |
atrib={val} |
v-bind:atrib="val" /:atrib="val" |
|
([ngModel]) |
Separate value and setter function props | v-model |
|
@ViewChild /elementRef.nativeElement |
React.createRef /useRef |
vm.$el |
|
Pipes | useMemo |
computed * |
* While Vue computed fields have a similar caching ability to pure Angular pipes, they can also have setters rather than just being used as a data pipe |
[ngClass]="{'active': isActive}" |
No special API - className={} as with any other prop |
v-bind:class="{ active: isActive }" |
|
@ViewChild |
React.Children |
ref /$vm.refs |
|
ngContent |
this.props.children |
slot |
|
event.emit(value) |
Child function pass* | $emit('event', val) |
*This goes much more inline with the raise state logic that React pushes very hard - and is probably the main reason for this |
ng-container |
Fragments | Vue 2: BYOF - Bring your own fragments Vue 3: <template> |
|
Class props | state = {} /useState |
data /useRef /reactive |
|
Angular Universal/ScullyIO* | Next.JS*/Gatsby* | Nuxt.JS* | * These are unofficial solutions but are the most popular versions of these concepts |
Output (?). Classes sorta handle this for free |
forwardRef |
expose |
Differences:
Angular | React | Vue | |
---|---|---|---|
Change Detection | Zones (ZoneJS ) |
Maniually State Calling (setState ) |
getters and setters (Object.definedProperty v2/Proxy v3) |