Angular Platforms in depth. Part 1. What are Angular Platforms?
In this series of articles, I’m going to reveal to you how does it even possible — execute Angular applications across different environments. Also, we’ll learn how to build custom Angular platform which renders applications inside the system’s terminal using ASCII graphics.

The Angular framework was designed to be a platform independent. That approach allows Angular applications to be executed across different environments — browser, server, web-worker, and even mobile devices.
Articles:
- Angular Platforms in depth. Part 1. What are Angular Platforms?
- Angular Platforms in depth. Part 2. Application bootstrap process
- Angular Platforms in depth. Part 3. Rendering Angular applications in Terminal

Table of contents
- Angular is a cross-platform framework
- What are Angular platforms?
- How do Angular platforms allow cross-platform execution?
Angular is a cross-platform framework
As I stated previously, Angular was designed with flexibility in mind. That’s why Angular is a cross-platform framework, which is not limited by the browser. The only thing which is required by Angular to be executed is a JavaScript engine. Let’s take a look at the most popular Angular environments.
Browser
When we’re creating a new Angular application using Angular CLI ng new MyNewApplication
it sets the browser environment as a default environment for our application.
Server
Angular applications can be compiled and executed at the server side. In that case, we can compile Angular application to static HTML files, then send that files to the clients.
That approach allows us to speed up applications loading, also as make sure that application will be properly indexed by all search engines.
Web worker
Also, we can move part of Angular application to the separate thread. To the web worker thread. In that case, we’re leaving only a small part of the application at the main thread, that part will allow the web worker part to communicate with the document API’s.
That approach allows you to produce a smoother UI, free of “janks”, as most of your application’s computations will occur separately from the UI.
Web worker environment was experimental from the beginning and since Angular 8 it’s deprecated.
NativeScript
Also, there is plenty of third-party libraries that allow us to execute Angular applications across different environments. For instance NativeScript. NativeScript enables Angular to be executed on mobile devices utilizing all the functionality of native platforms.
But how does it even possible to execute Angular applications across different environments? ?
The answer is platforms!
What are Angular platforms?
To figure out what are Angular platforms we need to take a look at the entry point of each Angular application — main.ts
file:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
platformBrowserDynamic().bootstrapModule(AppModule);
Here we could notice two important parts:
platformBrowserDynamic()
function called and returned some object.- That object is used to bootstrap our application.
If we slightly rewrite it we’ll find one interesting detail here:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { PlatformRef } from '@angular/core';
// Create Browser Platform
const platformRef: PlatformRef = platformBrowserDynamic();
// Bootstrap Application
platformRef.bootstrapModule(AppModule);
platformBrowserDynamic
is a platform factory — a function which creates new instances of platforms. The result of the platformBrowserDynamic
function call is a PlatformRef
. PlatformRef
is a plain Angular service which knows how to bootstrap our applications. For a better understanding of how that instance of PlatformRef
created, let’s have a deeper look at the platformBrowserDynamic
implementation:
export const platformBrowserDynamic = createPlatformFactory(
// Parent platform factory
platformCoreDynamic,
// New factory name
'browserDynamic',
// Additional services
INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
);
Here we can see that platformBrowserDynamic
function is just a result of the createPlatformFactory
function, which accepts the following params:
- Parent platform factory —
platformCoreDynamic
- Name for a new platform —
‘browserDynamic’
- Additional providers —
INTERNAL_BROWSER_DYNAMIC_PLAFORM_PROVIDERS
,
Well, platformCoreDynamic
here is a parent platform factory function. We can treat the relation between platformCoreDynamic
and platformBrowserDynamic
as inheritance. So, createPlatformFactory
function just helps us to inherit one platform factory from another. Easy as that.
The more interesting part takes place slightly deeper in the inheritance hierarchy. In fact, platformCoreDynamic
inherits platformCore
which in its turn haven’t got a parent.
So, here is the full hierarchy for platformBrowserDynamic
:

But during inheritance, Angular platform factories don’t change the behavior of parent platform factories. Instead, they’re providing additional tokens and services for our applications.
Ok, kind of complicated. Let’s dive deeper into createPlatformFactory
function to figure out how exactly Angular platform factories created.
Here is the super simplified code for the createPlatformFactory
:
type PlatformFactory = (extraProviders?: StaticProvider[]) => PlatformRef;
export function createPlatformFactory(
parentPlatformFactory: PlatformFactory,
name: string,
providers: StaticProvider[] = [],
): PlatformFactory {
return (extraProviders: StaticProvider[] = []) => {
const injectedProviders: StaticProvider[] = providers.concat(extraProviders);
if (parentPlatformFactory) {
return parentPlatformFactory(injectedProviders);
} else {
return createPlatform(Injector.create({ providers: injectedProviders }));
}
};
}
When we’re calling that function, it returns a platform factory function. Which accepts additional StaticProviders
for our applications. And if we’re providing parent platform factory, createPlatformFactory
function will call it and return its value, otherwise, it’ll just create and return a new platform. Let’s take a look at the platformBrowserDynamic
creation process step by step for better understanding:
platformBrowserDynamic
created as a result ofcreatePlatformFactory
function call withplatformCoreDynamic
as a parent platform.
2. We’re calling platformBrowserDynamic
function to create a new platform.
3. It checks that parentPlatformFactory
exists and calls it with additional providers array and then just returns its value:
if (parentPlatformFactory) {
return parentPlatformFactory(injectedProviders);
}
4. On that stage we could notice, that result of the platformBrowserDynamic
function is actually a result of platformCoreDynamic
function with all services provided by the platformBrowserDynamic
.
5. platformCoreDynamic
created the same way as platformBrowserDynamic
With two differences — it extends platformCore
and provides its own providers.
export const platformCoreDynamic = createPlatformFactory(
platformCore,
'coreDynamic',
CORE_DYNAMIC_PROVIDERS,
);
Here we could notice that we have the same situation: because of the existence of parent platform we’ll just return parent platform factory result with additional providers:
platformCore([ ...CORE_DYNAMIC_PROVIDERS, ...BROWSER_DYNAMIC_PROVIDERS ]);
6. But inside platformCore
we have a slightly different situation.
export const platformCore = createPlatformFactory(
null,
'core',
CORE_PLATFORM_PROVIDERS,
);
Here, CORE_PLATFORM_PROVIDERS
is an array which contains the crucial provider — PlatformRef
service. As we’re providing null
as a parent platform factory, createPlatformFactory
function will just return the result of createPlatform
function.
7. createPlatform
function in its turn will just retrieve PlatformRef
from the injector. And return it to the caller.
function createPlatform(injector: Injector): PlatformRef {
return injector.get(PlatformRef);
}
8. Now we have PlatformRef
created:
const ref: PlatformRef = platformBrowserDynamic();
But look, during inheritance platforms doesn’t change the behavior of the PlatformRef
explicitly. Instead, they’re providing new sets of services that will be used by the PlatformRef
during the bootstrap process.

Here we can notice, that platformCore
is not like other platforms. platformCore
is kind of special. Because it’s responsible for providingPlatformRef
for the platform creation process, and that’s why it serves as a root platform for each platform in the Angular ecosystem.
After that we could say that each Angular platform consists of two important parts:
PlatformRef
— service that will bootstrap Angular application.- Providers — Array of tokens and services provided to be used during bootstrap and execution phases.
How do Angular platforms allow cross-platform execution?
On that stage, we’ve learned what are Angular platforms and how do they created. So, let’s discuss how Angular platforms allow Angular to be the cross-platform framework.
It’s all about abstraction. As we know Angular highly relies on the dependency injection system. And that’s why a pretty big part of Angular itself is declared as abstract services:
- Renderer2
- Compiler
- ElementSchemaRegistry
- Sanitizer
- etc.
Services mentioned above and many others declared as abstract classes inside Angular. And when we’re using different platforms, those platforms provide appropriate implementations for that abstract classes. Let me explain, for instance, here we a number of abstract services declared by angular. I do personally prefer to imagine them as blue circles:

But it’s just abstract classes that lack any implementation or functionality. And when we’re using Browser Platform, it provides its own implementations for those services:

However, if we’re using Server Platform, for instance, it provides its own implementations for those abstract core services.

Ok, Let’s have a concrete example here.
For instance, Angular uses DomAdapter
abstraction to manipulate DOM in an environment-agnostic way. Here’s a simplified version of the DomAdapter
abstract class.
export abstract class DomAdapter {
abstract setProperty(el: Element, name: string, value: any): any;
abstract getProperty(el: Element, name: string): any;
abstract querySelector(el: any, selector: string): any;
abstract querySelectorAll(el: any, selector: string): any[];
abstract appendChild(el: any, node: any): any;
abstract removeChild(el: any, node: any): any;
//... and so on
}
And when we’re using Browser Platform it provides appropriate browser implementation for that abstract class:
export class BrowserDomAdapter extends DomAdapter { ... }
BrowserDomAdapter
interacts with browser DOM directly and that’s why can’t be used anywhere outside the browser.
That’s why to be allowed to be executed on the server side for the server side rendering purposes we’re using Server Platform, which in its turn provides another implementation:
export class DominoAdapter extends DomAdapter { ... }
DominAdapter
doesn’t interact with the DOM because we haven’t got DOM on the server side. Instead, it utilizes the domino library, which emulates DOM for the node.js.
As a result, we have the following structure:

Conclusion
Congratulations ? you’ve reached the end of the article. During the article, we’ve covered what are Angular platforms, how are they created, went step by step during the platformBrowserDynamic
creation process. And finally, figured out how platforms concept allows Angular to be a cross-platform framework.
If you want to get deeper knowledge on Angular platforms, take a look at the rest articles of the series:
- Angular Platforms in depth. Part 1. What are Angular Platforms?
- Angular Platforms in depth. Part 2. Application bootstrap process
- Angular Platforms in depth. Part 3. Rendering Angular applications in Terminal
Also, follow me on twitter to be notified about new Angular articles as soon as possible!