From 53d77d785bd7e3a5186893be7fe864f6f7f3205f Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Sat, 24 Dec 2016 17:22:39 +0100 Subject: [PATCH] Upgrade aot docs (#190) * docs(api/upgrade): add docs about AOT upgrade function helpers * docs(api/recipes/upgrade): add docs how to upgrade with new upgrade/static function helpers * docs(README): point to new v4 plunker and update rxjs version Closes #176 --- CONTRIBUTING.md | 5 +- README.md | 24 +- SUMMARY.md | 1 + docs/api/upgrade/class.md | 2 +- docs/api/upgrade/function.md | 393 ++++++++++++++++ docs/recipes/ng-upgrade.md | 521 ++++++++++++++++++++- src/upgrade/static/downgrade_component.ts | 4 + src/upgrade/static/downgrade_injectable.ts | 26 +- src/upgrade/static/upgrade_injectable.ts | 6 +- 9 files changed, 935 insertions(+), 47 deletions(-) create mode 100644 docs/api/upgrade/function.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2847e5e..b267b0f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,7 @@ ngMetadata channel or [StackOverflow][stackoverflow] or just ping us on [twitter ## Found an Issue? If you find a bug in the source code or a mistake in the documentation, you can help us by submitting an issue to our [GitHub Repository][github]. Even better you can submit a Pull Request -with a fix. We would appreciate if you provide a [Plunker][plunker-3.x] with simulated bug that you've found +with a fix. We would appreciate if you provide a [Plunker][plunker-4.x] with simulated bug that you've found **Please see the Submission Guidelines below**. @@ -67,7 +67,7 @@ chances of your issue being dealt with quickly: * **Motivation for or Use Case** - explain why this is a bug for you * **Angular/ngMetadata/Typescript Version(s)** - is it a regression? * **Browsers and Operating System** - is this a problem with all browsers or only IE8? -* **Reproduce the Error** - provide a live example (using [Plunker][plunker-3.x] or an unambiguous set of steps. +* **Reproduce the Error** - provide a live example (using [Plunker][plunker-4.x] or an unambiguous set of steps. * **Related Issues** - has a similar issue been reported before? * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be causing the problem (line of code or commit) @@ -250,4 +250,5 @@ You can find out more detailed information about contributing in the [plunker-1.x]: https://plnkr.co/edit/s2lYnI [plunker-2.x]: https://plnkr.co/edit/7Fr7oO [plunker-3.x]: https://plnkr.co/edit/Bds0Bk +[plunker-4.x]: https://plnkr.co/edit/hsz8KF diff --git a/README.md b/README.md index 4d6e7d2..9468f9d 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Behind the scenes it uses ES.next decorators extended by TypeScript (which adds ## Quick start -- [Plunkr][plunker-3.x] - try it out in your browser +- [Plunkr][plunker-4.x] - try it out in your browser - [Playground][playground] - live docs examples and mandatory TODO app example - [Production ready starter kit][ngParty-Angular-1-scaffold] - Starter Kit ready for production by ngParty team powered by Webpack @@ -218,7 +218,7 @@ ng-metadata requires certain polyfills in the application environment. We instal Install peer dependencies by running: -`npm i --save rxjs@5.0.0-rc.1` +`npm i --save rxjs@5.0.1` **rxjs** - a polyfill for the Observables specification currently before the TC39 committee that determines standards for the JavaScript language. Developers should be able to pick a preferred version of rxjs (within a compatible version range) without waiting for ng-metadata updates. @@ -271,7 +271,8 @@ We typically add a TypeScript configuration file (`tsconfig.json`) to our projec #### TypeScript Declaration Files -Many JavaScript libraries such as jQuery, the Jasmine testing library, and Angular itself, extend the JavaScript environment with features and syntax that the TypeScript compiler doesn't recognize natively. When the compiler doesn't recognize something, it throws an error. +Many JavaScript libraries such as jQuery, the Jasmine testing library, and Angular itself, extend the JavaScript environment with features and syntax that the TypeScript compiler doesn't recognize natively. +When the compiler doesn't recognize something, it throws an error. We use TypeScript type declaration files — *d.ts files* — to tell the compiler about the libraries we load. @@ -298,21 +299,7 @@ That's it! You are good to go! ## Why? -There is already an existing project, which gives us Angular 2 like syntax for Angular 1, [ng-forward](https://github.com/ngUpgraders/ng-forward) - -While I respect all the hard work of the `ng-forward` team, there were things that I didn't like about their solution. - -Anyway that project (ngForward) is unmaintained with old/wrong angular 2 API's and isn't production ready at all. - -- it tries to mirror angular 2 with lots of under the hood abstractions which is just not feasible because there are major differences, how things work in ng1 an ng2 -- it tries to do a lot unnecessary work, which was never finished ( support ES5/ES6 like angular 2 does ) -- doesn't provides angular 2 like DI via constructor parameters because `babel` just won't support parameter decorators -- forces you to rewrite templates, so you can't be just 100% sure that your code will work as before - -> although we started a discussion about [collaboration](https://github.com/ngUpgraders/ng-forward/issues/138) I just don't think after further analysis, that we can merge -our project one way or another.' - -so those are just few reasons why I made **ng-metadata**. +those are just few reasons why I made **ng-metadata**. **ng-metadata:** - can be used as part of an upgrade strategy, which may also include *ng-upgrade*, when migrating to Angular 2 @@ -342,5 +329,6 @@ if you aren't a member just join us [ngParty slack](https://ngparty.herokuapp.co [plunker-1.x]: https://plnkr.co/edit/s2lYnI [plunker-2.x]: https://plnkr.co/edit/7Fr7oO [plunker-3.x]: https://plnkr.co/edit/Bds0Bk +[plunker-4.x]: https://plnkr.co/edit/hsz8KF [playground]: https://github.com/ngParty/ng-metadata/tree/master/playground [ngParty-Angular-1-scaffold]: https://github.com/ngParty/Angular1-scaffold diff --git a/SUMMARY.md b/SUMMARY.md index a544277..be16524 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -35,6 +35,7 @@ * [Class](/docs/api/router-deprecated/class.md) * [Interface](/docs/api/router-deprecated/interface.md) * [ng-metadata/upgrade](/docs/api/upgrade/README.md) + * [Function](/docs/api/upgrade/function.md) * [Class](/docs/api/upgrade/class.md) * [angular 1 specific api](/docs/api/ng-1-misc/README.md) 4. [Contributing](/CONTRIBUTING.md) diff --git a/docs/api/upgrade/class.md b/docs/api/upgrade/class.md index caa87e4..8de1d98 100644 --- a/docs/api/upgrade/class.md +++ b/docs/api/upgrade/class.md @@ -1,4 +1,4 @@ -# Class +# Class ( Deprecated - use [upgrade/static](/docs/recipes/function.md)) - [NgMetadataUpgradeAdapter](#NgMetadataUpgradeAdapter) diff --git a/docs/api/upgrade/function.md b/docs/api/upgrade/function.md new file mode 100644 index 0000000..b8e9c8e --- /dev/null +++ b/docs/api/upgrade/function.md @@ -0,0 +1,393 @@ +# Function + +**@angular/upgrade/static upgrade helpers** + +> supports AOT + +- [bootstrap](#bootstrap) + +> upgrade/downgrade + +- [provideNg2Component](#provideng2component) +- [downgradeNg2Component](#downgradeng2component) +- [provideNg2Injectable](#provideng2injectable) +- [downgradeNg2Injectable](#downgradeng2injectable) +- [upgradeInjectable](#upgradeinjectable) + +--- + +## bootstrap + +### What it does + +Bootstraps hybrid ng1 ( with ngMetadata ) ng2 app + +### How to use + +```typescript +// main.ts +import { UpgradeModule } from '@angular/upgrade/static/'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { enableProdMode } from 'ng-metadata/core'; + +import { Ng1AppModule } from './app/app.module'; +import { AppModule } from './app/app.module.ng2'; + + +if ( ENV === 'production' ) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + + upgrade.bootstrap(document.body, [Ng1AppModule.name], {strictDi: true}); +}); +``` + +```typescript +// ./app/app.module.ts +import { downgradeComponent, downgradeInjectable } from '@angular/upgrade/static/'; +import { provideNg2Component, provideNg2Injectable } from 'ng-metadata/upgrade' +import { NgModule, Directive, provide, getInjectableName, bundle } from 'ng-metadata/core'; + +import { AppComponent } from './app.component'; + +import { Logger, LoggerToken } from './logger.service.ng2'; +import { LoginComponent } from './login.component.ng2'; + +import { HeroesService } from './heroes/heroes.service'; + +@NgModule( { + declarations: [ + AppComponent, + provideNg2Component({component: LoginComponent, downgradeFn: downgradeComponent}), + ], + providers: [ + HeroesService, + provideNg2Injectable({injectable: Logger, downgradeFn: downgradeInjectable}), + ] +} ) +class AppModule { +} + +// we are using bundle to export regular ng1 module +export const Ng1AppModule = bundle(AppModule); +``` + + +## provideNg2Component + +### What it does + +Used to register an Angular 2 Component by including it in the `declarations` array of an ng-metadata `@NgModule`, +where the directive name and bindings(inputs,outputs) are automatically created from the selector. + +### How to use + +```typescript +// app.module.ts +import { downgradeComponent } from '@angular/upgrade/static/'; +import { provideNg2Component } from 'ng-metadata/upgrade'; +import { NgModule } from 'ng-metadata/core'; + +import { Ng2Component } from './components/ng2.component'; + +@NgModule({ + declarations:[ + provideNg2Component({component:Ng2Component,downgradeFn:downgradeComponent}) + ] +}) +export class AppModule {}; +``` + +### API + +```typescript +type downgradeComponent = (info: { + component: Type; + inputs?: string[]; + outputs?: string[]; +}) => any; +type ProvideNg2ComponentParams = { + component:Type, + downgradeFn:downgradeComponent +} +export function provideNg2Component({component,downgradeFn}: ProvideNg2ComponentParams): Function +``` + +Takes a Angular 2 component and downgradeFn reference to `downgradeComponent` from `@angular/upgrade/static` + +Returns a factory function that can be used to register the downgraded ng2 component on an Angular 1 via ngMetadata `@NgModule.declarations`. + + +## downgradeNg2Component + +### What it does + +Used to register an Angular 2 Component as a directive on an Angular 1 module, +where the directive name and bindings(inputs,outputs) are automatically created from the selector. + +### How to use + +```typescript +// app.module.ts +import * as angular from 'angular' +import { downgradeComponent } from '@angular/upgrade/static/'; +import { downgradeNg2Component } from 'ng-metadata/upgrade'; +import { provide } from 'ng-metadata/core'; + +import { Ng2Component } from './components/ng2.component'; + +export const AppModule = angular + .module('myApp',[]) + .directive(...downgradeNg2Component({component:Ng2Component,downgradeFn:downgradeComponent})) +``` + +### API +```typescript +type downgradeComponent = (info: { + component: Type; + inputs?: string[]; + outputs?: string[]; +}) => any; +type ProvideNg2ComponentParams = { + component:Type, + downgradeFn:downgradeComponent +} +export function downgradeNg2Component({component, downgradeFn}: ProvideNg2ComponentParams): [string, Function] +``` +Takes a Angular 2 component and downgradeFn reference to `downgradeComponent` from `@angular/upgrade/static` + +Returns a tuple of component name and factory function that can be used to register the downgraded ng2 component on an Angular 1 module. + + +## provideNg2Injectable + +### What it does + +Used to register an Angular 2 Service by including it in the `providers` array of an ng-metadata `@NgModule`, +where the service name and downgraded factory functions are automatically generated. + +**NOTE:** downgraded service must also be registered within Angular 2 Component or NgModule + +### How to use + +```typescript +// app.module.ts - Angular 1(ngMetadata) +import { downgradeInjectable } from '@angular/upgrade/static/'; +import { provideNg2Injectable } from 'ng-metadata/upgrade'; +import { NgModule } from 'ng-metadata/core'; + +import { Ng2Service } from './services/ng2.service'; +import { Ng2ServiceDecorated } from './services/ng2decorated.service' + +const OtherServiceToken = new OpaqueToken('otherService') + +@NgModule({ + providers: [ + provideNg2Injectable({token:'ng2Service', injectable: Ng2Service, downgradeFn: downgradeInjectable }), + provideNg2Injectable({token:OtherServiceToken, injectable: Ng2Service, downgradeFn: downgradeInjectable }), + provideNg2Injectable({injectable:Ng2ServiceDecorated, downgradeFn: downgradeInjectable}), + ], +}) +export class AppModule{} +``` + +as you've may noticed in one registration we've omitted `token`, how is that possible that it works you ask? +this is thanks to ngMetadata `@Injectable()` decorator, we can decorate Angular 2 Classes with our ngMetadata `@Injectable`, +which gives us benefit to omit Opaque tokens creation and use the same class for DI for both Angular 2 and Angular 1. +POWER OVERWHELMING RIGHT?! + +Enough Talk! Show me how the service looks like: +```typescript +// ./services/ng2decorated.service.ts + +import {Injectable} from '@angular/core'; +import {Injectable as KeepNg1Injectable} from 'ng-metadata/core'; + +@KeepNg1Injectable() +@Injectable() +export class Ng2ServiceDecorated { + constructor(){} + greet(){} +} +``` + +### API + +```typescript +type ProviderLiteral = { + provide: any; + useClass?: Type; + useValue?: any; + useFactory?: Function; + useExisting?: any; + deps?: Object[]; + multi?: boolean; +} +type ProvideNg2InjectableParams = { + injectable: Function | Type; + downgradeFn: Function; + /** + * We need token only if downgraded Angular 2 Service is not Decorated with both ng2 @Injectable and ngMetadata @Injectable + * + */ + token?: string | OpaqueToken; +}; + +export function provideNg2Injectable({injectable, downgradeFn, token}: ProvideNg2InjectableParams): ProviderLiteral; +``` + +Takes a Angular 2 service, downgradeFn reference to `downgradeInjectable` from `@angular/upgrade/static` and optional `token` that identifies a service provided from Angular 2+. + +Returns a `ProviderLiteral` which can be used to register an Angular 2 Provider/Injectable +by including it in the providers array of an ng-metadata annotated Angular 1 +`@Component` or `@NgModule`. Either a string or an ng-metadata OpaqueToken can be used for the name. + + +## downgradeNg2Injectable + +### What it does + +Downgrades an Angular 2 Injectable so that it can be registered as an Angular 1 +factory. Either a string or an ng-metadata `OpaqueToken` can be used for the name. + +**NOTE:** downgraded service must also be registered within Angular 2 `@Component` or `@NgModule` + +### How to use + +```typescript +// app.module.ts +import * as angular from 'angular' +import { downgradeInjectable } from '@angular/upgrade/static/'; +import { downgradeNg2Injectable } from 'ng-metadata/upgrade'; +import { provide } from 'ng-metadata/core'; + +import { Ng2Service } from './services/ng2.service'; +import { Ng2ServiceDecorated } from './services/ng2decorated.service'; + +export const OtherServiceToken = new OpaqueToken('otherService') + +export const AppModule = angular + .module('myApp',[]) + .factory(...downgradeNg2Injectable({token:'ng2Service', injectable: Ng2Service, downgradeFn: downgradeInjectable })) + .factory(...downgradeNg2Injectable({token: OtherServiceToken, injectable: Ng2Service, downgradeFn: downgradeInjectable })) + .factory(...downgradeNg2Injectable({injectable:Ng2ServiceDecorated, downgradeFn: downgradeInjectable})) + ``` +### API + +```typescript +type ProvideNg2InjectableParams = { + injectable: Function | Type; + downgradeFn: Function; + /** + * We need token only if downgraded Angular 2 Service is not Decorated with both ng2 @Injectable and ngMetadata @Injectable + * + */ + token?: string | OpaqueToken; +}; + +export function downgradeNg2Injectable( { injectable, downgradeFn, token }: ProvideNg2InjectableParams ): [string|Function] +``` + +Takes a Angular 2 service, downgradeFn reference to `downgradeInjectable` from `@angular/upgrade/static` and optional `token` that identifies a service provided from Angular 2+. + +Returns a tuple of service name and factory function that can be used to register the downgraded ng2 service on an Angular 1 module. + + + +## upgradeInjectable + +### What it does + +Helper function to upgrade Angular 1 services to Angular 2 without need of using lot of boilerplate + +### How to use + +Let's say we have ngMetadata angular 1 Service: + +```typescript +// heroes.service.ts +import { Injectable } from 'ng-metadata/core' +import { Hero } from './hero'; + +@Injectable() +export class HeroesService { + get() { + return [ + new Hero(1, 'Windstorm'), + new Hero(2, 'Spiderman'), + ]; + } +} +``` + +registered within ng-metadata NgModule: + +```typescript +// app.module.ts +import { NgModule } from 'ng-metadata/core'; +import { HeroesService } from './heroes/heroes.service'; + +@NgModule( { + providers: [ HeroesService ] +} ) +class AppModule {} +``` + +and we can upgrade it to Angular 2 like this: + +```typescript +// app.module.ng2.ts +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule } from '@angular/upgrade/static/'; +import { provideNg1Injectable } from 'ng-metadata/upgrade'; + +import { HeroComponent } from './heroes/hero.component.ng2'; +import { HeroesService } from './heroes/heroes.service'; + +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ], + declarations: [ + HeroComponent + ], + providers: [ + provideNg1Injectable('$routeParams'), + provideNg1Injectable(HeroesService), + ], + entryComponents: [ + HeroComponent + ] +}) +export class AppModule { + // preventing automatic Bootstrap + ngDoBootstrap() {} +} +``` + +and now we can use it within angular 2 Component: +```typescript +// hero.component.ng2.ts +import { Component, Inject } from '@angular/core'; + +@Component({ + selector: 'my-hero', + template: `

My Hero

`, +}) +class HeroComponent { + constructor( + @Inject('$routeParams') private $routeParams: any, // by name using @Inject + private myCoolSvc: MyCoolService, // by type using the user defined token + private heroesSvc: HeroesService // by type using ngMetadata @Injectable service class + ) {} +} +``` + + + diff --git a/docs/recipes/ng-upgrade.md b/docs/recipes/ng-upgrade.md index ebb3ecc..10e883c 100644 --- a/docs/recipes/ng-upgrade.md +++ b/docs/recipes/ng-upgrade.md @@ -6,9 +6,506 @@ one in which we use both Angular 1 and Angular 2 together. This is a very natural migration-step for large Angular 1 apps, because it allows us to mix and match Components and Providers from the two frameworks. -ng-metadata both supports `@angular/upgrade` and enhances it with some additional methods designed to help us take advantage of other ng-metadata features. +ng-metadata both supports `@angular/upgrade` and `@angular/upgrade/static` and enhances it with some additional methods designed to help us take advantage of other ng-metadata features. -## Creating the upgradeAdapter singleton +There are 2 ways how to proceed with hybrid/upgrade process: +- [The Upgrade Module helpers ( recommended )](#aot-upgrade/downgrade-helpers) +- [Singleton upgradeAdapter (which is now deprecated)](#singleton-upgradeAdapter-Deprecated) + +## AOT upgrade/downgrade helpers + +> before reading this, we highly recommend to read [angular 2 cookbook](https://angular.io/docs/ts/latest/guide/upgrade.html#!#upgrading-with-the-upgrade-module) so you now what is done under the hood + +### Creating the Angular 2 Root module + +we need to Create an `app.module.ng2.ts` file and add the following NgModule class: + +```typescript +// app.module.ng2.ts +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule } from '@angular/upgrade/static'; +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ] +}) +export class AppModule { + ngDoBootstrap() {} +} +``` + +next we need to export traditional Angular1Module instead of ngMetadata `@NgModule` from our root `app.module.ts` + +```diff +import { + NgModule, ++ bundle +} from 'ng-metadata/core'; + +import { AppComponent } from './app.component'; +import { FooComponent } from './components/foo.component'; +import { HeroesService } from './heroes/heroes.service'; + +@NgModule( { + declarations: [ + AppComponent, + FooComponent, + ], + providers: [ + HeroesService, + ] +} ) +- export class AppModule { +- } ++ class AppModule { ++ } + ++ export const Ng1AppModule = bundle(AppModule); +``` + +**FINAL CODE** +```typescript +// app.module +import { + NgModule, + bundle +} from 'ng-metadata/core'; + +import { AppComponent } from './app.component'; +import { FooComponent } from './components/foo.component'; +import { HeroesService } from './heroes/heroes.service'; + +@NgModule( { + declarations: [ + AppComponent, + FooComponent, + ], + providers: [ + HeroesService, + ] +} ) +class AppModule { +} + +export const Ng1AppModule = bundle(AppModule); +``` + +### Bootstrapping our hybrid app + +Now we bootstrap AppModule from `app.module.ng2.ts` using platformBrowserDynamic's bootstrapModule method. +Then we use dependency injection to get a hold of the UpgradeModule instance in AppModule, and use it to bootstrap our Angular 1 app. +The upgrade.bootstrap method takes the exact same arguments as `angular.bootstrap` + +We will no longer use ng-metadata to bootstrap our app: + +```diff +// main.ts +- import { platformBrowserDynamic } from 'ng-metadata/platform-browser-dynamic'; ++ import { UpgradeModule } from '@angular/upgrade/static/'; ++ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +- import { AppModule } from './app.module'; // ng-metadata NgModule ++ import { Ng1AppModule } from './app.module'; // ng-metadata NgModule ++ import { AppModule } from './app/app.module.ng2'; + +- platformBrowserDynamic().bootstrapModule(AppModule); ++ platformBrowserDynamic().bootstrapModule(AppModule) ++ .then(platformRef => { ++ const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; ++ ++ upgrade.bootstrap(document.body, [Ng1AppModule.name], {strictDi: true}); ++ }); +``` + +**FINAL CODE** +```typescript +// main.ts +import { UpgradeModule } from '@angular/upgrade/static/'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { Ng1AppModule } from './app.module'; // ng-metadata NgModule +import { AppModule } from './app/app.module.ng2'; + +platformBrowserDynamic().bootstrapModule(AppModule) + .then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + + upgrade.bootstrap(document.body, [Ng1AppModule.name], {strictDi: true}); +}); +``` + +now we can proceed with upgrade/downgrade our components/services + +### Upgrading an Angular 1 Component and downgrading it back to ng1 + +ng-metadata is a project designed to help us write our Angular 1 Components +just like Angular 2 Components, so "upgrading" them with the upgradeAdapter as an interim migration step doesn't really make sense. + +In a hybrid Angular 1 and 2 app, it is actually really easy for us to just change +a couple of things about our ng-metadata Component to make it a fully-fledged Angular 2 Component and then _downgrade_ it for use in our hybrid app. + +Here is an example of an ng-metadata Angular 1 Component which just renders its input: + +```typescript +// ./components/foo.component +import { Component, Input } from 'ng-metadata/core'; + +@Component({ + selector: 'my-foo', + template: '

Foo! {{ $ctrl.myInput }}

', +}) +export class FooComponent { + @Input() myInput: string; +} +``` + +To update this Component to a fully fledged Angular 2 Component, +all we need to do is change the import path of our decorators and make sure our template syntax is correct. + +In this case, the template only needs to have the `$ctrl` reference removed - in Angular 2 the `myInput` property is available directly. + +**Upgraded Angular 1 Component:** + +```diff +- import { Component, Input } from 'ng-metadata/core'; ++ import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'foo', +- template: '

Foo! {{ $ctrl.myInput }}

', ++ template: '

Foo! {{ myInput }}

', +}) +export class FooComponent { + @Input() myInput: string; +} +``` + +Now we need to _downgrade_ the Component using one of the two **functions** outlined in the next section. +We also need to register it within Angular 2 and ngMetadata `@NgModule`. + +#### Registering to NgModule + +we need to register upgraded Component to Angular 2 +```diff +// app.module.ng2.ts +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule } from '@angular/upgrade/static'; ++ import { FooComponent } from './components/foo.component'; + +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ], ++ declarations: [ ++ FooComponent ++ ] +}) +export class AppModule { + ngDoBootstrap() {} +} +``` + +and **downgrade** it back to Angular 1 and register it within ng-metadata @NgModule + +```diff +// app.module +import { + NgModule, + bundle +} from 'ng-metadata/core'; ++ import { provideNg2Component } from 'ng-metadata/upgrade'; ++ import { downgradeComponent } from '@angular/upgrade'; + +import { AppComponent } from './app.component'; +import { FooComponent } from './components/foo.component'; +import { HeroesService } from './heroes/heroes.service'; + +@NgModule( { + declarations: [ + AppComponent, +- FooComponent, ++ provideNg2Component({component:FooComponent,downgradeFn:downgradeComponent}), + ], + providers: [ + HeroesService, + ] +} ) +class AppModule { +} + +export const Ng1AppModule = bundle(AppModule); +``` + +Whole downgrade process and registration is handled by `provideNg2Component` ng-metadata function ( yes also `@Inputs` and `@Outputs`) + +That's it! DONE! + +We also provide helpers, if you are not using ng-metadata `@NgModule` for registration to angular 1 module + +[Please see API docs for further docs](/docs/api/function.md) + + +### Upgrading an Angular 1 Service and downgrading it back to ng1 + +We have 2 options here: + +- upgrade existing service to Angular 2 via Angular 2 `@NgModule.providers` and register it via ng-metadata `upgradeInjectable` helper +- upgrade the service to Angular 2 physically ( by changing import paths ) and downgrading it back to Angular 1 ng-metadata NgModule via `provideNg2Injectable` + +This is our initial ng-metadata Angular 1 service: + +```typescript +// ./heroes/heroes.service +import { Injectable } from 'ng-metadata/core' +import { Hero } from './hero'; + +@Injectable() +export class HeroesService { + get() { + return [ + new Hero(1, 'Windstorm'), + new Hero(2, 'Spiderman'), + ]; + } +} +``` + +#### `upgradeInjectable` method + +let's upgrade it to Angular 2 + +```diff +// app.module.ng2.ts +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule } from '@angular/upgrade/static'; +import { FooComponent } from './components/foo.component'; ++ import { HeroesService } from '/heroes/heroes.service'; + +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ], + declarations: [ + FooComponent + ], ++ providers: [ ++ provideNg1Injectable(HeroesService), ++ ] +}) +export class AppModule { + ngDoBootstrap() {} +} +``` + +done! we can now inject our Angular 1 service within Angular 2 entities, for example here is a ng2 component: +```typescript +import { Component, Inject } from '@angular/core'; +import { HeroesService } from '/heroes/heroes.service'; + +@Component({ + selector: 'my-hero', + template: `

My Hero

`, +}) +class HeroComponent { + constructor( + @Inject('$routeParams') private $routeParams: any, // by name using @Inject + private heroesSvc: HeroesService // by type using ngMetadata @Injectable service class + ) {} +} +``` + +#### `provideNg2Injectable` method + +We can migrate it directly to Angular 2 (if it doesn't has any Angular 1 injections) by changing path imports just like we did with Component + +Here is the result: + +```diff +// ./heroes/heroes.service +- import { Injectable } from 'ng-metadata/core' ++ import { Injectable } from '@angular/core' +import { Hero } from './hero'; + +@Injectable() +export class HeroesService { + get() { + return [ + new Hero(1, 'Windstorm'), + new Hero(2, 'Spiderman'), + ]; + } +} +``` + +now we need to register it to Angular 2 `@NgModule` + +```diff +// app.module.ng2.ts +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule } from '@angular/upgrade/static'; +import { FooComponent } from './components/foo.component'; ++ import { HeroesService } from '/heroes/heroes.service'; + +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ], + declarations: [ + FooComponent + ], ++ providers: [ ++ HeroesService, ++ ] +}) +export class AppModule { + ngDoBootstrap() {} +} +``` + +and downgrade it to Angular 1 ng-metadata `@NgModule.providers` + +```diff +// app.module +import { + NgModule, + bundle +} from 'ng-metadata/core'; ++ import { provideNg2Component } from 'ng-metadata/upgrade'; ++ import { downgradeComponent } from '@angular/upgrade/static'; + +import { AppComponent } from './app.component'; +import { FooComponent } from './components/foo.component'; +- import { HeroesService } from './heroes/heroes.service'; ++ import { HeroesService, HeroesServiceToken } from './heroes/heroes.service'; + +@NgModule( { + declarations: [ + AppComponent, + provideNg2Component({component:FooComponent,downgradeFn:downgradeComponent}), + ], + providers: [ +- HeroesService, ++ provideNg2Injectable({token:HeroesServiceToken, injectable:HeroesService, downgradeFn: downgradeInjectable}), + ] +} ) +class AppModule { +} + +export const Ng1AppModule = bundle(AppModule); +``` + +Note that we had to create ng-metadata `OpaqueToken` instance within `heroes.service.ts` so we have a Injectable reference within our Angular 1 app. +With this we need to change injection type in all Angular 1 ng-metadata entities, because ng1 cannot inject by Class type + +So we need to make changes like this: +```typescript +- constructor(private heroesSvc: HeroesService){} ++ constructor(@Inject(HeroesServiceToken) private heroesSvc: HeroesService){} +``` + +I don't know about you, but for me this is not very productive because we need to do even more refactoring. + +**DON'T YOU WORRY THERE IS BETTER A WAY ;)** + +First we need to annotate our upgraded ng2 service with ng-metadata `@Injectable`: + +```diff +// ./heroes/heroes.service +- import { Injectable } from 'ng-metadata/core' ++ import { Injectable as NgMetadataInjectable } from 'ng-metadata/core' ++ import { Injectable } from '@angular/core' +import { Hero } from './hero'; + ++ @NgMetadataInjectable() +@Injectable() +export class HeroesService { + get() { + return [ + new Hero(1, 'Windstorm'), + new Hero(2, 'Spiderman'), + ]; + } +} +``` + +then register it to Angular 2 `@NgModule` as previously + +```diff +// app.module.ng2.ts +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule } from '@angular/upgrade/static'; +import { FooComponent } from './components/foo.component'; ++ import { HeroesService } from '/heroes/heroes.service'; + +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ], + declarations: [ + FooComponent + ], ++ providers: [ ++ HeroesService, ++ ] +}) +export class AppModule { + ngDoBootstrap() {} +} +``` + +and downgrade it to Angular 1 ng-metadata `@NgModule` without `token` property ! + +**JUST LIKE THIS:** +```diff +// app.module +import { + NgModule, + bundle +} from 'ng-metadata/core'; ++ import { provideNg2Component } from 'ng-metadata/upgrade'; ++ import { downgradeComponent } from '@angular/upgrade/static'; + +import { AppComponent } from './app.component'; +import { FooComponent } from './components/foo.component'; +import { HeroesService } from './heroes/heroes.service'; + +@NgModule( { + declarations: [ + AppComponent, + provideNg2Component({component:FooComponent,downgradeFn:downgradeComponent}), + ], + providers: [ +- HeroesService, ++ provideNg2Injectable({injectable:HeroesService, downgradeFn: downgradeInjectable}), + ] +} ) +class AppModule { +} + +export const Ng1AppModule = bundle(AppModule); +``` + +With this you don't need to change DI Injection within your app. I call this a WIN WIN ! + + +We also provide helpers, if you are not using ng-metadata `@NgModule` for registration to angular 1 module +[Please see API docs for further docs](/docs/api/function.md) + + +--- + + +## Singleton upgradeAdapter ( Deprecated ) + +### Creating the upgradeAdapter singleton Just as outlined in the Angular 2 docs, we want to create a single instance of the UpgradeAdapter class and use that everywhere in our application. @@ -41,7 +538,7 @@ const instantiatedAdapter = new UpgradeAdapter(UpgradeModule) export const upgradeAdapter = new NgMetadataUpgradeAdapter(instantiatedAdapter) ``` -## Bootstrapping our hybrid app +### Bootstrapping our hybrid app In order for the dependency injection and change detection systems of both frameworks to work harmoniously together, we need to update the bootstrap process of our app to @@ -72,7 +569,7 @@ import { AppModule } from './app.module.ts'; // ng-metadata NgModule upgradeAdapter.bootstrap( AppModule ); ``` -## Upgrading an Angular 1 Component +### Upgrading an Angular 1 Component ng-metadata is a project designed to help us write our Angular 1 Components just like Angular 2 Components, so "upgrading" them with the upgradeAdapter as an interim migration step doesn't really make sense. @@ -119,7 +616,7 @@ export class FooComponent { We can now _downgrade_ the Component using one of the two methods outlined in the next section. -## Downgrading an Angular 2 Component +### Downgrading an Angular 2 Component When we start creating "native" Angular 2 Components in our hybrid application, we will need to downgrade them before we can register them as directives. @@ -146,7 +643,7 @@ export class Ng2Component { ng-metadata offers two ways for us to downgrade our Ng2Component for use in our hybrid app... -### 1) `upgradeAdapter.downgradeNg2Component()` +#### 1) `upgradeAdapter.downgradeNg2Component()` If we want to manually register the downgraded Component as a directive on an Angular 1 module, we can do that in a very similar way to how we have traditionally used ng-metadata's `provide()` function for Angular 1 Components. @@ -178,7 +675,7 @@ export const FooModule = angular.module( 'foo', [] ) .directive( ...upgradeAdapter.downgradeNg2Component( Ng2Component ) ); ``` -### 2) `upgradeAdapter.provideNg2Component()` +#### 2) `upgradeAdapter.provideNg2Component()` In order to make our ng-metadata apps match as closely to Angular 2 apps as is reasonable, we want to avoid dealing with Angular 1 modules directly in our code. @@ -210,7 +707,7 @@ import { Ng2Component } from './ng2.component.ts'; export class SomeParentModule {} ``` -## Upgrading an Angular 1 Provider +### Upgrading an Angular 1 Provider If we need to use an Angular 1 Provider within Angular 2 Components and Providers during our migration phase, we can use `upgradeAdapter.upgradeNg1Provider()`. @@ -251,14 +748,14 @@ class Ng2Component { } ``` -## Downgrading an Angular 2 Provider +### Downgrading an Angular 2 Provider Naturally, we might also want to inject Angular 2 Providers into the Angular 1 Components in our hybrid app. Just like with downgrading Angular 2 Components (as described above), ng-metadata offers two ways for us to downgrade our Angular 2 Providers and register their compiled factory functions... -### 1) `upgradeAdapter.downgradeNg2Provider()` +#### 1) `upgradeAdapter.downgradeNg2Provider()` The `downgradeNg2Provider()` helper function works in a similar way to the `downgradeNg2Component()` function. In the case of Providers, however, there is no metadata to use to infer the name from, so we need to provide @@ -284,7 +781,7 @@ export const FooModule = angular.module( 'foo', [] ) .factory( ...upgradeAdapter.downgradeNg2Provider( otherServiceToken, { useClass: Ng2Service }) ) ``` -### 2) `upgradeAdapter.provideNg2Provider()` +#### 2) `upgradeAdapter.provideNg2Provider()` The alternative to directly interacting with an Angular 1 module (not recommended) for the purposes of registering Provider, is to make use of the providers array on an NgModule. @@ -326,7 +823,7 @@ export class FooComponent { } ``` -### NOTE: Using downgraded Angular 2 Providers in other Angular 2 Components/Providers +#### NOTE: Using downgraded Angular 2 Providers in other Angular 2 Components/Providers If we want to also use our downgraded Angular 2 Providers in other Angular 2 Providers or Components, we need to _additionally_ add it as a Provider to the Angular 2 NgModule that we pass into the `@angular/upgrade` UpgradeAdapter in `upgrade-adapter.ts`. diff --git a/src/upgrade/static/downgrade_component.ts b/src/upgrade/static/downgrade_component.ts index 78cbbfa..2e19294 100644 --- a/src/upgrade/static/downgrade_component.ts +++ b/src/upgrade/static/downgrade_component.ts @@ -26,6 +26,8 @@ export type downgradeComponent = (info: { * import { downgradeNg2Component } from 'ng-metadata/upgrade'; * import { provide } from 'ng-metadata/core'; * + * import { Ng2Component } from './components/ng2.component'; + * * export const AppModule = angular * .module('myApp',[]) * .directive(...downgradeNg2Component({component:Ng2Component,downgradeFn:downgradeComponent})) @@ -47,6 +49,8 @@ export function downgradeNg2Component({component,downgradeFn}: ProvideNg2Compone * import { provideNg2Component } from 'ng-metadata/upgrade'; * import { NgModule } from 'ng-metadata/core'; * + * import { Ng2Component } from './components/ng2.component'; + * * @NgModule({ * declarations:[ * provideNg2Component({component:Ng2Component,downgradeFn:downgradeComponent}) diff --git a/src/upgrade/static/downgrade_injectable.ts b/src/upgrade/static/downgrade_injectable.ts index c8b13d3..0bf19c2 100644 --- a/src/upgrade/static/downgrade_injectable.ts +++ b/src/upgrade/static/downgrade_injectable.ts @@ -7,7 +7,7 @@ export type ProvideNg2InjectableParams = { injectable: Function | Type, downgradeFn: Function, /** - * We need token only if downgraded ANgular 2 Service is not Decorated with both ng2 @Injectable and ngMetadata @Injectable + * We need token only if downgraded Angular 2 Service is not Decorated with both ng2 @Injectable and ngMetadata @Injectable * */ token?: string | OpaqueToken, @@ -17,7 +17,7 @@ export type ProvideNg2InjectableParams = { * Downgrades an Angular 2 Injectable so that it can be registered as an Angular 1 * factory. Either a string or an ng-metadata OpaqueToken can be used for the name. * - * **NOTE:** downgraded service must also be registered within Angular 2 Component or NgModule + * **NOTE:** downgraded service must also be registered within Angular 2 `@Component` or `@NgModule` * * @example * ```typescript @@ -27,8 +27,8 @@ export type ProvideNg2InjectableParams = { * import { downgradeNg2Injectable } from 'ng-metadata/upgrade'; * import { provide } from 'ng-metadata/core'; * - * import { Ng2Service } from './services/ng2.service; - * import { Ng2ServiceDecorated } from './services/ng2decorated.service + * import { Ng2Service } from './services/ng2.service'; + * import { Ng2ServiceDecorated } from './services/ng2decorated.service'; * * export const OtherServiceToken = new OpaqueToken('otherService') * @@ -50,12 +50,16 @@ export function downgradeNg2Injectable( { injectable, downgradeFn, token }: Prov /** - * Returns a ProviderLiteral which can be used to register an Angular 2 Provider/Injectable - * by including it in the providers array of an ng-metadata annotated Angular 1 - * Component or NgModule. Either a string or an ng-metadata OpaqueToken can be used for the name. + * + * Used to register an Angular 2 Service by including it in the `providers` array of an ng-metadata `@NgModule`, + * where the service name and downgraded factory functions are automatically generated. * * **NOTE:** downgraded service must also be registered within Angular 2 Component or NgModule * + * Returns a `ProviderLiteral` which can be used to register an Angular 2 Provider/Injectable + * by including it in the providers array of an ng-metadata annotated Angular 1 + * `@Component` or `@NgModule`. Either a string or an ng-metadata OpaqueToken can be used for the name. + * * @example * ``` * // foo.component.ts - Angular 1(ngMetadata) @@ -63,8 +67,8 @@ export function downgradeNg2Injectable( { injectable, downgradeFn, token }: Prov * import { provideNg2Injectable } from 'ng-metadata/upgrade'; * import { Component } from 'ng-metadata/core'; * - * import { Ng2Service } from './services/ng2.service; - * import { Ng2ServiceDecorated } from './services/ng2decorated.service; + * import { Ng2Service } from './services/ng2.service'; + * import { Ng2ServiceDecorated } from './services/ng2decorated.service'; * * const OtherServiceToken = new OpaqueToken('otherService') * @@ -90,8 +94,8 @@ export function downgradeNg2Injectable( { injectable, downgradeFn, token }: Prov * import { provideNg2Injectable } from 'ng-metadata/upgrade'; * import { NgModule } from 'ng-metadata/core'; * - * import { Ng2Service } from './services/ng2.service; - * import { Ng2ServiceDecorated } from './services/ng2decorated.service + * import { Ng2Service } from './services/ng2.service'; + * import { Ng2ServiceDecorated } from './services/ng2decorated.service'; * * const OtherServiceToken = new OpaqueToken('otherService') * diff --git a/src/upgrade/static/upgrade_injectable.ts b/src/upgrade/static/upgrade_injectable.ts index b0557ca..c657084 100644 --- a/src/upgrade/static/upgrade_injectable.ts +++ b/src/upgrade/static/upgrade_injectable.ts @@ -71,7 +71,7 @@ import { getInjectableName } from '../../core/di/provider'; * and now we can use it within angular 2 Component: * ```typescript * // hero.component.ng2.ts - * import { Component } from '@angular/core'; + * import { Component, Inject } from '@angular/core'; * * @Component({ * selector: 'my-hero', @@ -80,7 +80,7 @@ import { getInjectableName } from '../../core/di/provider'; * class HeroComponent { * constructor( * @Inject('$routeParams') private $routeParams: any, // by name using @Inject - * private myCoolSvc: MyCoolService // by type using the user defined token + * private myCoolSvc: MyCoolService, // by type using the user defined token * private heroesSvc: HeroesService // by type using ngMetadata @Injectable service class * ) {} * } @@ -118,7 +118,7 @@ import { getInjectableName } from '../../core/di/provider'; * ] * }) * export class AppModule { - * // preventig automatic Bootstrap + * // preventing automatic Bootstrap * ngDoBootstrap() {} * } * ```