Angular Platforms in depth. Part 1. What are Angular Platforms?

Post Editor

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.

7 min read
post

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.

post
post
7 min read

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:

Content imageContent image

Table of contents
Link to this section

  • Angular is a cross-platform framework
  • What are Angular platforms?
  • How do Angular platforms allow cross-platform execution?

Angular is a cross-platform framework
Link to this section

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?
Link to this section

To figure out what are Angular platforms we need to take a look at the entry point of each Angular application — main.tsfile:

<>Copy
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:

<>Copy
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:

<>Copy
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:

Content imageContent image

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:

<>Copy
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:

  1. platformBrowserDynamic created as a result of createPlatformFactory function call with platformCoreDynamic 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:

<>Copy
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 platformBrowserDynamicWith two differences — it extends platformCore and provides its own providers.

<>Copy
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:

<>Copy
platformCore([ ...CORE_DYNAMIC_PROVIDERS, ...BROWSER_DYNAMIC_PROVIDERS ]);

6. But inside platformCore we have a slightly different situation.

<>Copy
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.

<>Copy
function createPlatform(injector: Injector): PlatformRef { return injector.get(PlatformRef); }

8. Now we have PlatformRef created:

<>Copy
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.

Content imageContent image

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?
Link to this section

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:

Content imageContent image

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:

Content imageContent image

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

Content imageContent image

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.

<>Copy
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:

<>Copy
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:

<>Copy
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:

Content imageContent image

Conclusion
Link to this section

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:

Also, follow me on twitter to be notified about new Angular articles as soon as possible!

Share

About the author

author_image

I’m a Solution Architect at Akveo, open-source contributor and tech author from Belarus. I passionate about sharing knowledge and digging into the depths of technologies. Google Developer Expert.

author_image

About the author

Nikita Poltoratsky

I’m a Solution Architect at Akveo, open-source contributor and tech author from Belarus. I passionate about sharing knowledge and digging into the depths of technologies. Google Developer Expert.

About the author

author_image

I’m a Solution Architect at Akveo, open-source contributor and tech author from Belarus. I passionate about sharing knowledge and digging into the depths of technologies. Google Developer Expert.

Featured articles