Kevin Buchs
Kevin Buchs

Reputation: 2872

Trying to understand how Angular v4.4 actually works with platformBrowserDynamic and PlatformRef

I have inherited an Angular 4.4 application using Webpack v3.5 and TypeScript v2.3.3. I have been struggling to understand what the code I see (imported) is actually doing and part of it is I don't understand how it can even be correct. Note, I have performed some simplification in the following code to make it easier to focus on the question.

My main.ts contains these statements (among others):

import { platformBrowserDynamic }  from '@angular/platform-browser-dynamic;'
return platformBrowserDynamic().bootstrapModule(AppModule).then...;

​ So, platformBrowserDynamic is a function which is invoked there. Meanwhile, in @angular/platform-browser-dynamic/platform-browser-dynamic.d.ts is this statement

export * from './src/platform-browser-dynamic';

In @angular/platform-browser-dynamic/src/platform-browser-dynamic/platform-browser-dynamic.d.ts are these statements:

import { PlatformRef, Provider } from '@angular/core';
export declare const platformBrowserDynamic: (extraProviders?: Provider[] | undefined) => PlatformRef;

In other words, platformBrowserDynamic is a function returning whatever PlatformRef is. In @angular/core/core.d.ts is this export:

export * from './public_api';

In @angular/core/public_api.d.ts is

export * from './src/core';

In @angular/core/src/core.d.ts is

export { createPlatform, assertPlatform, destroyPlatform, getPlatform, PlatformRef, ApplicationRef, enableProdMode, isDevMode, createPlatformFactory, NgProbeToken } from './application_ref';

So, that is where the definition of PlatformRef is coming from. In @angular/core/src/application_ref.d.ts, are these statements:

export declare abstract class PlatformRef { 
   abstract bootstrapModuleFactory<M>(
...
}

export declare class PlatformRef_ extends PlatformRef { ... }

PlatformRef is defined as an abstract base class. Now, PlatformRef_ (with underscore appended) is defined as a "concrete" class, but that class name is not referenced. BUT, https://www.typescriptlang.org/docs/handbook/classes.html#abstract-classes says:

Abstract classes are base classes from which other classes may be derived. They may not be instantiated directly.

So WHAT is the call to platformBrowserDynamic() actually returning? An ABC? Or, an invocation of an ABC, which can't happen, according to the docs? And, if an ABC, then the story is even worse, as the reference to platformBrowserDynamic actually immediately calls an abstract method of that ABC, which is, of course, undefined. And, for sure, it is a working application, but I don't know how it could work.

Upvotes: 1

Views: 707

Answers (3)

bryan60
bryan60

Reputation: 29325

wanted to comment this but it's getting long....

you've got a couple fundamental misunderstandings of typescript and how it works in relation to javascript, and js package distribution in general.

first.. .d.ts extension files are only type declaration files for the benefit of the typescript transpiler. they don't have any actual runnable code, they just tell the transpiler what type functions and variables etc are.. they're (optionally) generated by the transpiler when you bundle your code for distribution, or sometimes maintained separately for libraries that aren't natively TS for TS compatibility purposes. You could put a my-function.d.ts file in your app root right now with the following:

declare function myFunction(a: string): string;

and typescript will happily take your word that myFunction exists and will let you run it anywhere in your project and tell you the typings for it, even though you haven't actually implemented it, and doing so will throw runtime errors. Because sometimes js libraries are loaded in a way that typescript can't know about, and this is how you tell it they're there. It's also how you tell it the typings for previously bundled packages you got from npm that are now in JS. more about declaration files: https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html

the package.json in angular core is pointing to an actual js file that has the real transpiled source code that webpack will grab and use in your bundled package, but it's barely human readable as it's generated code from typescript and webpack. the stuff you get from npm is not the source code you should be looking at to figure out angular's inner workings as npm generally delivers bundled packages for efficiency, along with type declaration files for the benefit of typescript users. to find the non transpiled / bundled typescript source, you should visit the angular github (https://github.com/angular/angular). it's all open source, and you can clone the repo and explore as much as you'd like, but you're trying to do the javascript equivalent of reading binaries or packages from nuget right now.

second... when it says the return type is PlatformRef which is an abstract class, it doesn't mean it's returning an instance of PlatformRef, it means it's returning an instance of a class that extends or implements PlatformRef. It could be any number of different concrete implementations of PlatformRef. This is useful because it allows developers to have different platform refs for different platforms, like browser vs mobile app, so the data and event bindings can be abstracted away and swapped out at the lowest level. This makes angular code extremely portable. It also means that most angular developers don't have to worry about the particular implementation of PlatformRef while they're coding their app. They just need to know how to use the tools angular provides to create a portable application, as it's all been well abstracted away to let app developers focus on the app rather than the idiosyncrasies of browsers and mobile platforms.

I'll reiterate my point from my comments here, PlatformRef is just defining the application context so that angular can bind events and data correctly. It needs to know if it's running in a browser or a mobile context so it can hook into button clicks or scrolls or gestures or whatever using the correct methods for the context while providing a consistent API for the developer. It's just an abstraction so you as the developer can always bind to a click event with <button (click)="myFunction()"> without ever worrying about the application context. I've personally gotten pretty deep into angular source over nearly a decade of working with it, and the information has been useful to me maybe a handful of times in very far edge scenarios. Angular source is really large and complex as it does a whole lot. It's not a good starting point for angular, the countless development guides and tutorials are, and then get into the source later if you're still interested.

You'd need to know more about it if, lets say you were hoping to make your application work in like a smart tv/fridge/toaster/whatever. Then you would very much need to write your own concrete implementation of PlatformRef and write a factory function to deliver it.

Upvotes: 2

David
David

Reputation: 34445

If you have a look at the angular 4.4 source code for platform-browser-dynamic, you'll see the following

export const platformBrowserDynamic = createPlatformFactory(
    platformCoreDynamic, 'browserDynamic', INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS);

So platformBrowserDynamic is the function invocation of createPlatformFactory, that is to say it will be an instance of a factory class.

A factory class is used to instantiate object without necessarily knowing which actual class will be used, or when creating an object is a bit of work (like creating a component in angular for instance).

In this example, invoking the platformBrowserDynamic function/factory instantiate a child class extending PlatformRef, not an instance of the PlatformRef class itself (since it is abstract, like you pointed out)

However, like bryan60 said in the comments, you do not need to worry about all that stuff

Edit

I've digged a little deeper to answer your question regarding where the actual instance is created.

The child class extending the PlatformRef class is called PlatformRef_ and is defined here.

You can see that it implements all the necessary methods from the abstract parent class.

Note that you will not find any instance of new PlatformRef_(). PlatformRef_ class is marked as @Injectable and will be eventually created with angular's injector. For that, it needs to be defined in the providers, which is done here for the core platform. Other platforms include browser, server and web worker.

The method which will eventually instantiate a new platform based on the providers is the method returned by createPlatformFactory

As to "how come a factory function programmed to return an abstract class can return a child class extending that class", this is part of the principles of object oriented programming (abstraction, inheritance and polymorphism). PlatformRef is a contract, you don't have to care about the implementation. It just means that the factory will return something that implements PlatformRef. Maybe it would have bean clearer to you if it was returning an interface, then abstract and implementation classes would remain hidden?

Upvotes: 3

leonardfactory
leonardfactory

Reputation: 3501

I'll start this answer specifying, as others said in comments, that this is not necessary at all in order to understand Angular basics and to get it working. That said, I understand the passion to dig inside libraries sources and trying to grasp their "magic", so I'll try to explain it the better I can.

First, an abstract class is useful exactly for the purpouse it is used right here. An abstract class is, essentially, a contract (like an Interface, but potentially containing some core implementations usable by the Inheriting child classes).

Given this contract, Angular is making the promise to create and return to you an instance of a PlatformRef. As the name says, this is something that may differ given the platform you're on, quoting the docs (emphasis mine):

In Angular terminology, a platform is the context in which an Angular application runs. The most common platform for Angular applications is a web browser, but it can also be an operating system for a mobile device, or a web server

Angular is not saying that it will return to you an instance of the abstract class. It is saying that it will give you an instance of a class that respects the PlatformRef contract. You don't need to know which it is, and you can use the PlatformRef methods in the contract without worrying, because it's Angular responsability to check the platform and provide you the right implementation of PlatformRef, given the platform you're on.

On the other side, if you think about the Angular devs point of view, they needed to give you some methods that can work in every platform. When you are installing Angular, you are their customer, and the Abstract Class is their contract with you. Or, simply, an Angular dev himself needs this contract in order to implement other Angular features without worrying about the platform.

In the source, you've seen:

export const platformBrowserDynamic = createPlatformFactory(
    platformCoreDynamic, 'browserDynamic', INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS)

This means that there are some internally registered providers, and the createPlatformFactory (a function that returns a factory function) is responsible for PlatformRef contract instancing. To give you an extremely simplified example of how this could work, let's think about this:

function createPlatform(): PlatformRef {
  if (typeof window !== 'undefined') { // I'm clearly joking here..
    return new BrowserPlatformRef();
  } 
  // ..
}

class BrowserPlatformRef extends PlatformRef { /* ... */ }

As you said, PlatformRef by itself is not instantiable, meaning that createPlatform is forced to return a child class deriving from PlatformRef, since new PlatformRef() is not allowed.

Upvotes: 1

Related Questions