it('should instantiate ng2 in ng1 template and project content', async(() => { // the ng2 component that will be used in ng1 (downgraded) @Component({selector: 'ng2', template: `{{ 'NG2' }}(<ng-content></ng-content>)`}) class Ng2Component { } // our upgrade module to host the component to downgrade @NgModule({ imports: [BrowserModule, UpgradeModule], declarations: [Ng2Component], entryComponents: [Ng2Component] }) class Ng2Module { ngDoBootstrap() {} } // the ng1 app module that will consume the downgraded component const ng1Module = angular .module('ng1', []) // create an ng1 facade of the ng2 component .directive('ng2', downgradeComponent({component: Ng2Component})); const element = html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>'); bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]'); }); }));
it('should verify UpgradeAdapter example', async(() => { // This is wrapping (upgrading) an AngularJS component to be used in an Angular // component @Directive({selector: 'ng1'}) class Ng1Component extends UpgradeComponent { @Input() title: string; constructor(elementRef: ElementRef, injector: Injector) { super('ng1', elementRef, injector); } } // This is an Angular component that will be downgraded @Component({ selector: 'ng2', template: 'ng2[<ng1 [title]="nameProp">transclude</ng1>](<ng-content></ng-content>)' }) class Ng2Component { @Input('name') nameProp: string; } // This module represents the Angular pieces of the application @NgModule({ declarations: [Ng1Component, Ng2Component], entryComponents: [Ng2Component], imports: [BrowserModule, UpgradeModule] }) class Ng2Module { ngDoBootstrap() { /* this is a placeholder to stop the boostrapper from complaining */ } } // This module represents the AngularJS pieces of the application const ng1Module = angular .module('myExample', []) // This is an AngularJS component that will be upgraded .directive( 'ng1', () => { return { scope: {title: '='}, transclude: true, template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)' }; }) // This is wrapping (downgrading) an Angular component to be used in AngularJS .directive('ng2', downgradeComponent({component: Ng2Component})); // This is the (AngularJS) application bootstrap element // Notice that it is actually a downgraded Angular component const element = html('<ng2 name="World">project</ng2>'); // Let's use a helper function to make this simpler bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { expect(multiTrim(element.textContent)) .toBe('ng2[ng1[Hello World!](transclude)](project)'); }); }));
it('should interleave scope and component expressions', async(() => { const log: any[] /** TODO #9100 */ = []; const l = (value: any /** TODO #9100 */) => { log.push(value); return value + ';'; }; @Directive({selector: 'ng1a'}) class Ng1aComponent extends UpgradeComponent { constructor(elementRef: ElementRef, injector: Injector) { super('ng1a', elementRef, injector); } } @Directive({selector: 'ng1b'}) class Ng1bComponent extends UpgradeComponent { constructor(elementRef: ElementRef, injector: Injector) { super('ng1b', elementRef, injector); } } @Component({ selector: 'ng2', template: `{{l('2A')}}<ng1a></ng1a>{{l('2B')}}<ng1b></ng1b>{{l('2C')}}` }) class Ng2Component { l: (value: any) => string; constructor() { this.l = l; } } @NgModule({ declarations: [Ng1aComponent, Ng1bComponent, Ng2Component], entryComponents: [Ng2Component], imports: [BrowserModule, UpgradeModule] }) class Ng2Module { ngDoBootstrap() {} } const ng1Module = angular.module('ng1', []) .directive('ng1a', () => ({template: '{{ l(\'ng1a\') }}'})) .directive('ng1b', () => ({template: '{{ l(\'ng1b\') }}'})) .directive('ng2', downgradeComponent({component: Ng2Component})) .run(($rootScope: any /** TODO #9100 */) => { $rootScope.l = l; $rootScope.reset = () => log.length = 0; }); const element = html('<div>{{reset(); l(\'1A\');}}<ng2>{{l(\'1B\')}}</ng2>{{l(\'1C\')}}</div>'); bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;'); // https://github.com/angular/angular.js/issues/12983 expect(log).toEqual(['1A', '1B', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']); }); }));
export function downgrade(component: any): void { const { selector, inputs, outputs } = extractMetadata(component); if (!selector) { throw new Error(`Angular component *selector* missing for @Downgrade()! (${component.name})`); } createModule(component) .directive(toCamel(selector), downgradeComponent({ component, inputs, outputs })); }
it('should propagate changes to a downgraded component inside the ngZone', async(() => { let appComponent: AppComponent; @Component({selector: 'my-app', template: '<my-child [value]="value"></my-child>'}) class AppComponent { value: number; constructor() { appComponent = this; } } @Component({ selector: 'my-child', template: '<div>{{valueFromPromise}}', }) class ChildComponent { valueFromPromise: number; @Input() set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); } constructor(private zone: NgZone) {} ngOnChanges(changes: SimpleChanges) { if (changes['value'].isFirstChange()) return; this.zone.onMicrotaskEmpty.subscribe( () => { expect(element.textContent).toEqual('5'); }); Promise.resolve().then(() => this.valueFromPromise = changes['value'].currentValue); } } @NgModule({ declarations: [AppComponent, ChildComponent], entryComponents: [AppComponent], imports: [BrowserModule, UpgradeModule] }) class Ng2Module { ngDoBootstrap() {} } const ng1Module = angular.module('ng1', []).directive( 'myApp', downgradeComponent({component: AppComponent})); const element = html('<my-app></my-app>'); bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { appComponent.value = 5; }); }));
export const downgradeModuleFactory = (ngModule) => { const boostrapFn = (extraProviders: StaticProvider[]) => { const platformRef = platformBrowserDynamic(extraProviders); return platformRef.bootstrapModule(ngModule); }; const downgradedModule = downgradeModule(boostrapFn); angular .module(downgradedModule) .directive('serviceBootstrap', downgradeComponent({component: ServiceBootstrapComponent})) .run(['$compile', '$rootScope', ($compile: ng.ICompileService, $root: ng.IRootScopeService) => { $compile('<service-bootstrap></service-bootstrap>')($root); }]); return downgradedModule; };
SPINNAKER_COMPONENT_DOWNGRADES.forEach((item) => { DOWNGRADED_COMPONENT_MODULE_NAMES.push(item.moduleName); declarations.push(item.moduleClass); // ng2 AoT requires we specify the inputs/outputs for downgraded components because the metadata is lost at runtime const component: any = { component: item.moduleClass }; if (item.inputs) { component.inputs = item.inputs; } if (item.outputs) { component.outputs = item.outputs; } angular.module(item.moduleName, []).directive(item.injectionName, downgradeComponent(component) as ng.IDirectiveFactory); });
it('should instantiate ng1 in ng2 template and project content', async(() => { @Component({ selector: 'ng2', template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`, }) class Ng2Component { } @Directive({selector: 'ng1'}) class Ng1WrapperComponent extends UpgradeComponent { constructor(elementRef: ElementRef, injector: Injector) { super('ng1', elementRef, injector); } } @NgModule({ declarations: [Ng1WrapperComponent, Ng2Component], entryComponents: [Ng2Component], imports: [BrowserModule, UpgradeModule] }) class Ng2Module { ngDoBootstrap() {} } const ng1Module = angular.module('ng1', []) .directive( 'ng1', () => { return { transclude: true, template: '{{ "ng1" }}(<ng-transclude></ng-transclude>)' }; }) .directive('ng2', downgradeComponent({component: Ng2Component})); const element = html('<div>{{\'ng1(\'}}<ng2></ng2>{{\')\'}}</div>'); bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { expect(document.body.textContent).toEqual('ng1(ng2(ng1(transclude)))'); }); }));
imports: [ BrowserModule, UpgradeModule ], declarations: [ HeroDetailComponent ], entryComponents: [ HeroDetailComponent ] }) export class AppModule { ngDoBootstrap() {} } // #docregion downgradecomponent angular.module('heroApp', []) .controller('MainController', MainController) .directive('heroDetail', downgradeComponent({ component: HeroDetailComponent, inputs: ['hero'], outputs: ['deleted'] }) as angular.IDirectiveFactory); // #enddocregion downgradecomponent platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => { const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true}); });
import { HeroDetailComponent } from './hero-detail.component'; // #enddocregion downgradecomponent @NgModule({ imports: [ BrowserModule, UpgradeModule ], declarations: [ HeroDetailComponent ], entryComponents: [ HeroDetailComponent ] }) export class AppModule { constructor(private upgrade: UpgradeModule) { } ngDoBootstrap() { this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true }); } } // #docregion downgradecomponent angular.module('heroApp', []) .controller('MainController', MainController) .directive('heroDetail', downgradeComponent({component: HeroDetailComponent}) as angular.IDirectiveFactory); // #enddocregion downgradecomponent platformBrowserDynamic().bootstrapModule(AppModule);