Reputation: 2872
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
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
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
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