From 824ea8406c9f763353703ad546ab7bbf7cc4bd68 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 16 Nov 2016 10:17:10 +0000 Subject: [PATCH] docs(upgrade/static): improve API docs with examples Closes #12717 --- .../upgrade/src/aot/component_info.ts | 6 + .../upgrade/src/aot/downgrade_component.ts | 44 +++++++ .../upgrade/src/aot/downgrade_injectable.ts | 42 +++++- .../upgrade/src/aot/upgrade_component.ts | 55 +++++++- .../upgrade/src/aot/upgrade_module.ts | 121 +++++++++++++++++- 5 files changed, 258 insertions(+), 10 deletions(-) diff --git a/modules/@angular/upgrade/src/aot/component_info.ts b/modules/@angular/upgrade/src/aot/component_info.ts index 16a8a2df13e02..a9dc0c3a74e67 100644 --- a/modules/@angular/upgrade/src/aot/component_info.ts +++ b/modules/@angular/upgrade/src/aot/component_info.ts @@ -14,6 +14,12 @@ export interface ComponentInfo { outputs?: string[]; } +/** + * A `PropertyBinding` represents a mapping between a property name + * and an attribute name. It is parsed from a string of the form + * `"prop: attr"`; or simply `"propAndAttr" where the property + * and attribute have the same identifier. + */ export class PropertyBinding { prop: string; attr: string; diff --git a/modules/@angular/upgrade/src/aot/downgrade_component.ts b/modules/@angular/upgrade/src/aot/downgrade_component.ts index 590bb06dcf22d..ec846f5d3b451 100644 --- a/modules/@angular/upgrade/src/aot/downgrade_component.ts +++ b/modules/@angular/upgrade/src/aot/downgrade_component.ts @@ -16,6 +16,50 @@ import {DowngradeComponentAdapter} from './downgrade_component_adapter'; let downgradeCount = 0; /** + * @whatItDoes + * + * *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic) + * library for hybrid upgrade apps that support AoT compilation* + * + * Allows an Angular 2+ component to be used from Angular 1. + * + * @howToUse + * + * Let's assume that you have an Angular 2+ component called `ng2Heroes` that needs + * to be made available in Angular 1 templates. + * + * {@example upgrade/static/ts/module.ts region="ng2-heroes"} + * + * We must create an Angular 1 [directive](https://docs.angularjs.org/guide/directive) + * that will make this Angular 2+ component available inside Angular 1 templates. + * The `downgradeComponent()` function returns a factory function that we + * can use to define the Angular 1 directive that wraps the "downgraded" component. + * + * {@example upgrade/static/ts/module.ts region="ng2-heroes-wrapper"} + * + * In this example you can see that we must provide information about the component being + * "downgraded". This is because once the AoT compiler has run, all metadata about the + * component has been removed from the code, and so cannot be inferred. + * + * We must do the following: + * * specify the Angular 2+ component class that is to be downgraded + * * specify all inputs and outputs that the Angular 1 component expects + * + * @description + * + * A helper function that returns a factory function to be used for registering an + * Angular 1 wrapper directive for "downgrading" an Angular 2+ component. + * + * The parameter contains information about the Component that is being downgraded: + * + * * `component: Type`: The type of the Component that will be downgraded + * * `inputs: string[]`: A collection of strings that specify what inputs the component accepts. + * * `outputs: string[]`: A collection of strings that specify what outputs the component emits. + * + * The `inputs` and `outputs` are strings that map the names of properties to camelCased + * attribute names. They are of the form `"prop: attr"`; or simply `"propAndAttr" where the + * property and attribute have the same identifier. + * * @experimental */ export function downgradeComponent(info: /* ComponentInfo */ { diff --git a/modules/@angular/upgrade/src/aot/downgrade_injectable.ts b/modules/@angular/upgrade/src/aot/downgrade_injectable.ts index 67a70dc952035..f2a069d6cbf62 100644 --- a/modules/@angular/upgrade/src/aot/downgrade_injectable.ts +++ b/modules/@angular/upgrade/src/aot/downgrade_injectable.ts @@ -10,14 +10,44 @@ import {Injector} from '@angular/core'; import {INJECTOR_KEY} from './constants'; /** - * Create an Angular 1 factory that will return an Angular 2 injectable thing - * (e.g. service, pipe, component, etc) + * @whatItDoes * - * Usage: + * *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic) + * library for hybrid upgrade apps that support AoT compilation* * - * ``` - * angular1Module.factory('someService', downgradeInjectable(SomeService)) - * ``` + * Allow an Angular 2+ service to be accessible from Angular 1. + * + * @howToUse + * + * First ensure that the service to be downgraded is provided in an {@link NgModule} + * that will be part of the upgrade application. For example, let's assume we have + * defined `HeroesService` + * + * {@example upgrade/static/ts/module.ts region="ng2-heroes-service"} + * + * and that we have included this in our upgrade app {@link NgModule} + * + * {@example upgrade/static/ts/module.ts region="ng2-module"} + * + * Now we can register the `downgradeInjectable` factory function for the service + * on an Angular 1 module. + * + * {@example upgrade/static/ts/module.ts region="downgrade-ng2-heroes-service"} + * + * Inside an Angular 1 component's controller we can get hold of the + * downgraded service via the name we gave when downgrading. + * + * {@example upgrade/static/ts/module.ts region="example-app"} + * + * @description + * + * Takes a `token` that identifies a service provided from Angular 2+. + * + * Returns a [factory function](https://docs.angularjs.org/guide/di) that can be + * used to register the service on an Angular 1 module. + * + * The factory function provides access to the Angular 2+ service that + * is identified by the `token` parameter. * * @experimental */ diff --git a/modules/@angular/upgrade/src/aot/upgrade_component.ts b/modules/@angular/upgrade/src/aot/upgrade_component.ts index 8904f277e730a..0ed8199de8e45 100644 --- a/modules/@angular/upgrade/src/aot/upgrade_component.ts +++ b/modules/@angular/upgrade/src/aot/upgrade_component.ts @@ -43,6 +43,43 @@ interface IControllerInstance extends IBindingDestination { type LifecycleHook = '$onChanges' | '$onDestroy' | '$onInit' | '$postLink'; /** + * @whatItDoes + * + * *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic) + * library for hybrid upgrade apps that support AoT compilation* + * + * Allows an Angular 1 component to be used from Angular 2+. + * + * @howToUse + * + * Let's assume that you have an Angular 1 component called `ng1Hero` that needs + * to be made available in Angular 2+ templates. + * + * {@example upgrade/static/ts/module.ts region="ng1-hero"} + * + * We must create a {@link Directive} that will make this Angular 1 component + * available inside Angular 2+ templates. + * + * {@example upgrade/static/ts/module.ts region="ng1-hero-wrapper"} + * + * In this example you can see that we must derive from the {@link UpgradeComponent} + * base class but also provide an {@link Directive `@Directive`} decorator. This is + * because the AoT compiler requires that this information is statically available at + * compile time. + * + * Note that we must do the following: + * * specify the directive's selector (`ng1-hero`) + * * specify all inputs and outputs that the Angular 1 component expects + * * derive from `UpgradeComponent` + * * call the base class from the constructor, passing + * * the Angular 1 name of the component (`ng1Hero`) + * * the {@link ElementRef} and {@link Injector} for the component wrapper + * + * @description + * + * A helper class that should be used as a base class for creating Angular directives + * that wrap Angular 1 components that need to be "upgraded". + * * @experimental */ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy { @@ -63,6 +100,22 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy { private controllerInstance: IControllerInstance = null; private bindingDestination: IBindingDestination = null; + /** + * Create a new `UpgradeComponent` instance. You should not normally need to do this. + * Instead you should derive a new class from this one and call the super constructor + * from the base class. + * + * {@example upgrade/static/ts/module.ts region="ng1-hero-wrapper" } + * + * * The `name` parameter should be the name of the Angular 1 directive. + * * The `elementRef` and `injector` parameters should be acquired from Angular by dependency + * injection into the base class constructor. + * + * Note that we must manually implement lifecycle hooks that call through to the super class. + * This is because, at the moment, the AoT compiler is not able to tell that the + * `UpgradeComponent` + * already implements them and so does not wire up calls to them at runtime. + */ constructor(private name: string, private elementRef: ElementRef, private injector: Injector) { this.$injector = injector.get($INJECTOR); this.$compile = this.$injector.get($COMPILE); @@ -77,7 +130,7 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy { this.bindings = this.initializeBindings(this.directive); this.linkFn = this.compileTemplate(this.directive); - // We ask for the Angular 1 scope from the Angular 2 injector, since + // We ask for the Angular 1 scope from the Angular 2+ injector, since // we will put the new component scope onto the new injector for each component const $parentScope = injector.get($SCOPE); // QUESTION 1: Should we create an isolated scope if the scope is only true? diff --git a/modules/@angular/upgrade/src/aot/upgrade_module.ts b/modules/@angular/upgrade/src/aot/upgrade_module.ts index cf9744e42e484..46cef6c605b04 100644 --- a/modules/@angular/upgrade/src/aot/upgrade_module.ts +++ b/modules/@angular/upgrade/src/aot/upgrade_module.ts @@ -17,15 +17,130 @@ import {$$TESTABILITY, $DELEGATE, $INJECTOR, $PROVIDE, $ROOT_SCOPE, INJECTOR_KEY /** - * The Ng1Module contains providers for the Ng1Adapter and all the core Angular 1 services; - * and also holds the `bootstrapNg1()` method fo bootstrapping an upgraded Angular 1 app. + * @whatItDoes + * + * *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic) + * library for hybrid upgrade apps that support AoT compilation* + * + * Allows Angular 1 and Angular 2+ components to be used together inside a hybrid upgrade + * application, which supports AoT compilation. + * + * Specifically, the classes and functions in the `upgrade/static` module allow the following: + * 1. Creation of an Angular 2+ directive that wraps and exposes an Angular 1 component so + * that it can be used in an Angular 2 template. See {@link UpgradeComponent}. + * 2. Creation of an Angular 1 directive that wraps and exposes an Angular 2+ component so + * that it can be used in an Angular 1 template. See {@link downgradeComponent}. + * 3. Creation of an Angular 2+ root injector provider that wraps and exposes an Angular 1 + * service so that it can be injected into an Angular 2+ context. See + * {@link UpgradeModule#upgrading-an-angular-1-service Upgrading an Angular 1 service} below. + * 4. Creation of an Angular 1 service that wraps and exposes an Angular 2+ injectable + * so that it can be injected into an Angular 1 context. See {@link downgradeInjectable}. + * 3. Bootstrapping of a hybrid Angular application which contains both of the frameworks + * coexisting in a single application. See the + * {@link UpgradeModule#example example} below. + * + * ## Mental Model + * + * When reasoning about how a hybrid application works it is useful to have a mental model which + * describes what is happening and explains what is happening at the lowest level. + * + * 1. There are two independent frameworks running in a single application, each framework treats + * the other as a black box. + * 2. Each DOM element on the page is owned exactly by one framework. Whichever framework + * instantiated the element is the owner. Each framework only updates/interacts with its own + * DOM elements and ignores others. + * 3. Angular 1 directives always execute inside the Angular 1 framework codebase regardless of + * where they are instantiated. + * 4. Angular 2+ components always execute inside the Angular 2+ framework codebase regardless of + * where they are instantiated. + * 5. An Angular 1 component can be "upgraded"" to an Angular 2+ component. This is achieved by + * defining an Angular 2+ directive, which bootstraps the Angular 1 component at its location + * in the DOM. See {@link UpgradeComponent}. + * 6. An Angular 2+ component can be "downgraded"" to an Angular 1 component. This is achieved by + * defining an Angular 1 directive, which bootstraps the Angular 2+ component at its location + * in the DOM. See {@link downgradeComponent}. + * 7. Whenever an "upgraded"/"downgraded" component is instantiated the host element is owned by + * the framework doing the instantiation. The other framework then instantiates and owns the + * view for that component. + * a. This implies that the component bindings will always follow the semantics of the + * instantiation framework. + * b. The DOM attributes are parsed by the framework that owns the current template. So + * attributes + * in Angular 1 templates must use kebab-case, while Angular 1 templates must use camelCase. + * c. However the template binding syntax will always use the Angular 2+ style, e.g. square + * brackets (`[...]`) for property binding. + * 8. Angular 1 is always bootstrapped first and owns the root component. + * 9. The new application is running in an Angular 2+ zone, and therefore it no longer needs calls + * to + * `$apply()`. + * + * @howToUse + * + * `import {UpgradeModule} from '@angular/upgrade/static';` + * + * ## Example + * Import the {@link UpgradeModule} into your top level {@link NgModule Angular 2+ `NgModule`}. + * + * {@example upgrade/static/ts/module.ts region='ng2-module'} + * + * Then bootstrap the hybrid upgrade app's module, get hold of the {@link UpgradeModule} instance + * and use it to bootstrap the top level [Angular 1 + * module](https://docs.angularjs.org/api/ng/type/angular.Module). + * + * {@example upgrade/static/ts/module.ts region='bootstrap'} + * + * + * ## Upgrading an Angular 1 service + * + * There is no specific API for upgrading an Angular 1 service. Instead you should just follow the + * following recipe: + * + * Let's say you have an Angular 1 service: + * + * {@example upgrade/static/ts/module.ts region="ng1-title-case-service"} + * + * Then you should define an Angular 2+ provider to be included in your {@link NgModule} `providers` + * property. + * + * {@example upgrade/static/ts/module.ts region="upgrade-ng1-service"} + * + * Then you can use the "upgraded" Angular 1 service by injecting it into an Angular 2 component + * or service. + * + * {@example upgrade/static/ts/module.ts region="use-ng1-upgraded-service"} + * + * @description + * + * This class is an `NgModule`, which you import to provide Angular 1 core services, + * and has an instance method used to bootstrap the hybrid upgrade application. + * + * ## Core Angular 1 services + * Importing this {@link NgModule} will add providers for the core + * [Angular 1 services](https://docs.angularjs.org/api/ng/service) to the root injector. + * + * ## Bootstrap + * The runtime instance of this class contains a {@link UpgradeModule#bootstrap `bootstrap()`} + * method, which you use to bootstrap the top level Angular 1 module onto an element in the + * DOM for the hybrid upgrade app. + * + * It also contains properties to access the {@link UpgradeModule#injector root injector}, the + * bootstrap {@link NgZone} and the + * [Angular 1 $injector](https://docs.angularjs.org/api/auto/service/$injector). + * * @experimental */ @NgModule({providers: angular1Providers}) export class UpgradeModule { + /** + * The Angular 1 `$injector` for the upgrade application. + */ public $injector: any /*angular.IInjectorService*/; - constructor(public injector: Injector, public ngZone: NgZone) {} + constructor( + /** The root {@link Injector} for the upgrade application. */ + public injector: Injector, + /** The bootstrap zone for the upgrade application */ + public ngZone: NgZone) {} /** * Bootstrap an Angular 1 application from this NgModule