PR Close #18487 PR Close #18487
This commit is contained in:
parent
ca0a55f4ee
commit
2ac2ab7ff6
|
@ -7,16 +7,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {browser, by, element} from 'protractor';
|
import {browser, by, element} from 'protractor';
|
||||||
import {verifyNoBrowserErrors} from '../../../../_common/e2e_util';
|
import {verifyNoBrowserErrors} from '../../../../../_common/e2e_util';
|
||||||
|
|
||||||
function loadPage(url: string) {
|
function loadPage() {
|
||||||
browser.ng12Hybrid = true;
|
|
||||||
browser.rootEl = 'example-app';
|
browser.rootEl = 'example-app';
|
||||||
browser.get(url);
|
browser.get('/upgrade/static/ts/full/');
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('upgrade(static)', () => {
|
describe('upgrade/static (full)', () => {
|
||||||
beforeEach(() => { loadPage('/upgrade/static/ts/'); });
|
beforeEach(loadPage);
|
||||||
afterEach(verifyNoBrowserErrors);
|
afterEach(verifyNoBrowserErrors);
|
||||||
|
|
||||||
it('should render the `ng2-heroes` component', () => {
|
it('should render the `ng2-heroes` component', () => {
|
|
@ -5,11 +5,14 @@
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {Component, Directive, ElementRef, EventEmitter, Inject, Injectable, Injector, Input, NgModule, Output, SimpleChanges} from '@angular/core';
|
|
||||||
|
import {Component, Directive, ElementRef, EventEmitter, Inject, Injectable, Injector, Input, NgModule, Output} from '@angular/core';
|
||||||
import {BrowserModule} from '@angular/platform-browser';
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||||
import {UpgradeComponent, UpgradeModule, downgradeComponent, downgradeInjectable} from '@angular/upgrade/static';
|
import {UpgradeComponent, UpgradeModule, downgradeComponent, downgradeInjectable} from '@angular/upgrade/static';
|
||||||
|
|
||||||
|
declare var angular: ng.IAngularStatic;
|
||||||
|
|
||||||
interface Hero {
|
interface Hero {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
@ -116,7 +119,6 @@ class Ng2AppModule {
|
||||||
// #docregion Angular 1 Stuff
|
// #docregion Angular 1 Stuff
|
||||||
// #docregion ng1-module
|
// #docregion ng1-module
|
||||||
// This Angular 1 module represents the AngularJS pieces of the application
|
// This Angular 1 module represents the AngularJS pieces of the application
|
||||||
declare var angular: ng.IAngularStatic;
|
|
||||||
const ng1AppModule = angular.module('ng1AppModule', []);
|
const ng1AppModule = angular.module('ng1AppModule', []);
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
|
||||||
|
@ -136,7 +138,7 @@ ng1AppModule.component('ng1Hero', {
|
||||||
// This AngularJS service will be "upgraded" to be used in Angular
|
// This AngularJS service will be "upgraded" to be used in Angular
|
||||||
ng1AppModule.factory(
|
ng1AppModule.factory(
|
||||||
'titleCase',
|
'titleCase',
|
||||||
(() => (value: string) => value.replace(/((^|\s)[a-z])/g, (_, c) => c.toUpperCase())) as any);
|
() => (value: string) => value.replace(/((^|\s)[a-z])/g, (_, c) => c.toUpperCase()));
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
|
||||||
// #docregion downgrade-ng2-heroes-service
|
// #docregion downgrade-ng2-heroes-service
|
||||||
|
@ -145,7 +147,7 @@ ng1AppModule.factory('heroesService', downgradeInjectable(HeroesService) as any)
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
|
||||||
// #docregion ng2-heroes-wrapper
|
// #docregion ng2-heroes-wrapper
|
||||||
// This is directive will act as the interface to the "downgraded" Angular component
|
// This directive will act as the interface to the "downgraded" Angular component
|
||||||
ng1AppModule.directive('ng2Heroes', downgradeComponent({component: Ng2HeroesComponent}));
|
ng1AppModule.directive('ng2Heroes', downgradeComponent({component: Ng2HeroesComponent}));
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
|
||||||
|
@ -155,12 +157,10 @@ ng1AppModule.component('exampleApp', {
|
||||||
// We inject the "downgraded" HeroesService into this AngularJS component
|
// We inject the "downgraded" HeroesService into this AngularJS component
|
||||||
// (We don't need the `HeroesService` type for AngularJS DI - it just helps with TypeScript
|
// (We don't need the `HeroesService` type for AngularJS DI - it just helps with TypeScript
|
||||||
// compilation)
|
// compilation)
|
||||||
controller:
|
controller: [
|
||||||
[
|
'heroesService', function(heroesService: HeroesService) { this.heroesService = heroesService; }
|
||||||
'heroesService',
|
|
||||||
function(heroesService: HeroesService) { this.heroesService = heroesService; }
|
|
||||||
],
|
],
|
||||||
// This template make use of the downgraded `ng2-heroes` component
|
// This template makes use of the downgraded `ng2-heroes` component
|
||||||
// Note that because its element is compiled by AngularJS we must use kebab-case attributes
|
// Note that because its element is compiled by AngularJS we must use kebab-case attributes
|
||||||
// for inputs and outputs
|
// for inputs and outputs
|
||||||
template: `<link rel="stylesheet" href="./styles.css">
|
template: `<link rel="stylesheet" href="./styles.css">
|
||||||
|
@ -168,7 +168,7 @@ ng1AppModule.component('exampleApp', {
|
||||||
<h1>Heroes</h1>
|
<h1>Heroes</h1>
|
||||||
<p class="extra">There are {{ $ctrl.heroesService.heroes.length }} heroes.</p>
|
<p class="extra">There are {{ $ctrl.heroesService.heroes.length }} heroes.</p>
|
||||||
</ng2-heroes>`
|
</ng2-heroes>`
|
||||||
} as any);
|
});
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {ElementFinder, by} from 'protractor';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace jasmine {
|
||||||
|
interface Matchers {
|
||||||
|
toBeAHero(): Promise<void>;
|
||||||
|
toHaveName(exectedName: string): Promise<void>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTitleCased = (text: string) =>
|
||||||
|
text.split(/\s+/).every(word => word[0] === word[0].toUpperCase());
|
||||||
|
|
||||||
|
export function addCustomMatchers() {
|
||||||
|
jasmine.addMatchers({
|
||||||
|
toBeAHero:
|
||||||
|
() => ({
|
||||||
|
compare(actualNg1Hero: ElementFinder | undefined) {
|
||||||
|
const getText = (selector: string) =>
|
||||||
|
actualNg1Hero !.element(by.css(selector)).getText();
|
||||||
|
const result = {
|
||||||
|
message: 'Expected undefined to be an `ng1Hero` ElementFinder.',
|
||||||
|
pass: !!actualNg1Hero &&
|
||||||
|
Promise.all(['.title', 'h2', 'p'].map(getText) as PromiseLike<string>[])
|
||||||
|
.then(([actualTitle, actualName, actualDescription]) => {
|
||||||
|
const pass = (actualTitle === 'Super Hero') && isTitleCased(actualName) &&
|
||||||
|
(actualDescription.length > 0);
|
||||||
|
|
||||||
|
const actualHero =
|
||||||
|
`Hero(${actualTitle}, ${actualName}, ${actualDescription})`;
|
||||||
|
result.message =
|
||||||
|
`Expected ${actualHero}'${pass ? ' not' : ''} to be a real hero.`;
|
||||||
|
|
||||||
|
return pass;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
toHaveName: () => ({
|
||||||
|
compare(actualNg1Hero: ElementFinder | undefined, expectedName: string) {
|
||||||
|
const result = {
|
||||||
|
message: 'Expected undefined to be an `ng1Hero` ElementFinder.',
|
||||||
|
pass: !!actualNg1Hero && actualNg1Hero.element(by.css('h2')).getText().then(actualName => {
|
||||||
|
const pass = actualName === expectedName;
|
||||||
|
result.message =
|
||||||
|
`Expected Hero(${actualName})${pass ? ' not' : ''} to have name '${expectedName}'.`;
|
||||||
|
return pass;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
} as any);
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {ElementArrayFinder, ElementFinder, browser, by, element} from 'protractor';
|
||||||
|
|
||||||
|
import {verifyNoBrowserErrors} from '../../../../../_common/e2e_util';
|
||||||
|
import {addCustomMatchers} from './e2e_util';
|
||||||
|
|
||||||
|
function loadPage() {
|
||||||
|
browser.rootEl = 'example-app';
|
||||||
|
browser.get('/upgrade/static/ts/lite/');
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('upgrade/static (lite)', () => {
|
||||||
|
let showHideBtn: ElementFinder;
|
||||||
|
let ng2Heroes: ElementFinder;
|
||||||
|
let ng2HeroesHeader: ElementFinder;
|
||||||
|
let ng2HeroesExtra: ElementFinder;
|
||||||
|
let ng2HeroesAddBtn: ElementFinder;
|
||||||
|
let ng1Heroes: ElementArrayFinder;
|
||||||
|
|
||||||
|
const expectHeroes = (isShown: boolean, ng1HeroCount = 3, statusMessage = 'Ready') => {
|
||||||
|
// Verify the show/hide button text.
|
||||||
|
expect(showHideBtn.getText()).toBe(isShown ? 'Hide heroes' : 'Show heroes');
|
||||||
|
|
||||||
|
// Verify the `<ng2-heroes>` component.
|
||||||
|
expect(ng2Heroes.isPresent()).toBe(isShown);
|
||||||
|
if (isShown) {
|
||||||
|
expect(ng2HeroesHeader.getText()).toBe('Heroes');
|
||||||
|
expect(ng2HeroesExtra.getText()).toBe(`Status: ${statusMessage}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the `<ng1-hero>` components.
|
||||||
|
expect(ng1Heroes.count()).toBe(isShown ? ng1HeroCount : 0);
|
||||||
|
if (isShown) {
|
||||||
|
ng1Heroes.each(ng1Hero => expect(ng1Hero).toBeAHero());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
showHideBtn = element(by.binding('toggleBtnText'));
|
||||||
|
|
||||||
|
ng2Heroes = element(by.css('.ng2-heroes'));
|
||||||
|
ng2HeroesHeader = ng2Heroes.element(by.css('h1'));
|
||||||
|
ng2HeroesExtra = ng2Heroes.element(by.css('.extra'));
|
||||||
|
ng2HeroesAddBtn = ng2Heroes.element(by.buttonText('Add Hero'));
|
||||||
|
|
||||||
|
ng1Heroes = element.all(by.css('.ng1-hero'));
|
||||||
|
});
|
||||||
|
beforeEach(addCustomMatchers);
|
||||||
|
beforeEach(loadPage);
|
||||||
|
afterEach(verifyNoBrowserErrors);
|
||||||
|
|
||||||
|
it('should initially not render the heroes', () => expectHeroes(false));
|
||||||
|
|
||||||
|
it('should toggle the heroes when clicking the "show/hide" button', () => {
|
||||||
|
showHideBtn.click();
|
||||||
|
expectHeroes(true);
|
||||||
|
|
||||||
|
showHideBtn.click();
|
||||||
|
expectHeroes(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a new hero when clicking the "add" button', () => {
|
||||||
|
showHideBtn.click();
|
||||||
|
ng2HeroesAddBtn.click();
|
||||||
|
|
||||||
|
expectHeroes(true, 4, 'Added hero Kamala Khan');
|
||||||
|
expect(ng1Heroes.last()).toHaveName('Kamala Khan');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove a hero when clicking its "remove" button', () => {
|
||||||
|
showHideBtn.click();
|
||||||
|
|
||||||
|
const firstHero = ng1Heroes.first();
|
||||||
|
expect(firstHero).toHaveName('Superman');
|
||||||
|
|
||||||
|
const removeBtn = firstHero.element(by.buttonText('Remove'));
|
||||||
|
removeBtn.click();
|
||||||
|
|
||||||
|
expectHeroes(true, 2, 'Removed hero Superman');
|
||||||
|
expect(ng1Heroes.first()).not.toHaveName('Superman');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,220 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
// #docplaster
|
||||||
|
import {Component, Directive, ElementRef, EventEmitter, Inject, Injectable, Injector, Input, NgModule, Output, StaticProvider} from '@angular/core';
|
||||||
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
|
// #docregion basic-how-to
|
||||||
|
// Alternatively, we could import and use an `NgModuleFactory` instead:
|
||||||
|
// import {MyLazyAngularModuleNgFactory} from './my-lazy-angular-module.ngfactory';
|
||||||
|
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||||
|
// #enddocregion
|
||||||
|
/* tslint:disable: no-duplicate-imports */
|
||||||
|
import {UpgradeComponent} from '@angular/upgrade/static';
|
||||||
|
import {downgradeComponent} from '@angular/upgrade/static';
|
||||||
|
import {downgradeInjectable} from '@angular/upgrade/static';
|
||||||
|
// #docregion basic-how-to
|
||||||
|
import {downgradeModule} from '@angular/upgrade/static';
|
||||||
|
// #enddocregion
|
||||||
|
/* tslint:enable: no-duplicate-imports */
|
||||||
|
|
||||||
|
|
||||||
|
declare var angular: ng.IAngularStatic;
|
||||||
|
|
||||||
|
interface Hero {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This Angular service will use an "upgraded" AngularJS service.
|
||||||
|
@Injectable()
|
||||||
|
class HeroesService {
|
||||||
|
heroes: Hero[] = [
|
||||||
|
{name: 'superman', description: 'The man of steel'},
|
||||||
|
{name: 'wonder woman', description: 'Princess of the Amazons'},
|
||||||
|
{name: 'thor', description: 'The hammer-wielding god'}
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(@Inject('titleCase') titleCase: (v: string) => string) {
|
||||||
|
// Change all the hero names to title case, using the "upgraded" AngularJS service.
|
||||||
|
this.heroes.forEach((hero: Hero) => hero.name = titleCase(hero.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
addHero() {
|
||||||
|
const newHero: Hero = {name: 'Kamala Khan', description: 'Epic shape-shifting healer'};
|
||||||
|
this.heroes = this.heroes.concat([newHero]);
|
||||||
|
return newHero;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeHero(hero: Hero) { this.heroes = this.heroes.filter((item: Hero) => item !== hero); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This Angular component will be "downgraded" to be used in AngularJS.
|
||||||
|
@Component({
|
||||||
|
selector: 'ng2-heroes',
|
||||||
|
// This template uses the "upgraded" `ng1-hero` component
|
||||||
|
// (Note that because its element is compiled by Angular we must use camelCased attribute names.)
|
||||||
|
template: `
|
||||||
|
<div class="ng2-heroes">
|
||||||
|
<header><ng-content selector="h1"></ng-content></header>
|
||||||
|
<ng-content selector=".extra"></ng-content>
|
||||||
|
<div *ngFor="let hero of this.heroesService.heroes">
|
||||||
|
<ng1-hero [hero]="hero" (onRemove)="onRemoveHero(hero)">
|
||||||
|
<strong>Super Hero</strong>
|
||||||
|
</ng1-hero>
|
||||||
|
</div>
|
||||||
|
<button (click)="onAddHero()">Add Hero</button>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
class Ng2HeroesComponent {
|
||||||
|
@Output() private addHero = new EventEmitter<Hero>();
|
||||||
|
@Output() private removeHero = new EventEmitter<Hero>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject('$rootScope') private $rootScope: ng.IRootScopeService,
|
||||||
|
public heroesService: HeroesService) {}
|
||||||
|
|
||||||
|
onAddHero() {
|
||||||
|
const newHero = this.heroesService.addHero();
|
||||||
|
this.addHero.emit(newHero);
|
||||||
|
|
||||||
|
// When a new instance of an "upgraded" component - such as `ng1Hero` - is created, we want to
|
||||||
|
// run a `$digest` to initialize its bindings. Here, the component will be created by `ngFor`
|
||||||
|
// asynchronously, thus we have to schedule the `$digest` to also happen asynchronously.
|
||||||
|
this.$rootScope.$applyAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemoveHero(hero: Hero) {
|
||||||
|
this.heroesService.removeHero(hero);
|
||||||
|
this.removeHero.emit(hero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This Angular directive will act as an interface to the "upgraded" AngularJS component.
|
||||||
|
@Directive({selector: 'ng1-hero'})
|
||||||
|
class Ng1HeroComponentWrapper extends UpgradeComponent {
|
||||||
|
// The names of the input and output properties here must match the names of the
|
||||||
|
// `<` and `&` bindings in the AngularJS component that is being wrapped.
|
||||||
|
@Input() hero: Hero;
|
||||||
|
@Output() onRemove: EventEmitter<void>;
|
||||||
|
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
// We must pass the name of the directive as used by AngularJS to the super.
|
||||||
|
super('ng1Hero', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This Angular module represents the Angular pieces of the application.
|
||||||
|
@NgModule({
|
||||||
|
imports: [BrowserModule],
|
||||||
|
declarations: [Ng2HeroesComponent, Ng1HeroComponentWrapper],
|
||||||
|
providers: [
|
||||||
|
HeroesService,
|
||||||
|
// Register an Angular provider whose value is the "upgraded" AngularJS service.
|
||||||
|
{provide: 'titleCase', useFactory: (i: any) => i.get('titleCase'), deps: ['$injector']}
|
||||||
|
],
|
||||||
|
// All components that are to be "downgraded" must be declared as `entryComponents`.
|
||||||
|
entryComponents: [Ng2HeroesComponent]
|
||||||
|
// Note that there are no `bootstrap` components, since the "downgraded" component
|
||||||
|
// will be instantiated by ngUpgrade.
|
||||||
|
})
|
||||||
|
class MyLazyAngularModule {
|
||||||
|
// Empty placeholder method to prevent the `Compiler` from complaining.
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #docregion basic-how-to
|
||||||
|
|
||||||
|
|
||||||
|
// The function that will bootstrap the Angular module (when/if necessary).
|
||||||
|
// (This would be omitted if we provided an `NgModuleFactory` directly.)
|
||||||
|
const ng2BootstrapFn = (extraProviders: StaticProvider[]) =>
|
||||||
|
platformBrowserDynamic(extraProviders).bootstrapModule(MyLazyAngularModule);
|
||||||
|
// #enddocregion
|
||||||
|
// (We are using the dynamic browser platform, as this example has not been compiled AoT.)
|
||||||
|
|
||||||
|
|
||||||
|
// #docregion basic-how-to
|
||||||
|
|
||||||
|
|
||||||
|
// This AngularJS module represents the AngularJS pieces of the application.
|
||||||
|
const myMainAngularJsModule = angular.module('myMainAngularJsModule', [
|
||||||
|
// We declare a dependency on the "downgraded" Angular module.
|
||||||
|
downgradeModule(ng2BootstrapFn)
|
||||||
|
// or
|
||||||
|
// downgradeModule(MyLazyAngularModuleFactory)
|
||||||
|
]);
|
||||||
|
// #enddocregion
|
||||||
|
|
||||||
|
|
||||||
|
// This AngularJS component will be "upgraded" to be used in Angular.
|
||||||
|
myMainAngularJsModule.component('ng1Hero', {
|
||||||
|
bindings: {hero: '<', onRemove: '&'},
|
||||||
|
transclude: true,
|
||||||
|
template: `
|
||||||
|
<div class="ng1-hero">
|
||||||
|
<div class="title" ng-transclude></div>
|
||||||
|
<h2>{{ $ctrl.hero.name }}</h2>
|
||||||
|
<p>{{ $ctrl.hero.description }}</p>
|
||||||
|
<button ng-click="$ctrl.onRemove()">Remove</button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// This AngularJS service will be "upgraded" to be used in Angular.
|
||||||
|
myMainAngularJsModule.factory(
|
||||||
|
'titleCase', () => (value: string) => value.replace(/(^|\s)[a-z]/g, m => m.toUpperCase()));
|
||||||
|
|
||||||
|
|
||||||
|
// This directive will act as the interface to the "downgraded" Angular component.
|
||||||
|
myMainAngularJsModule.directive(
|
||||||
|
'ng2Heroes', downgradeComponent({
|
||||||
|
component: Ng2HeroesComponent,
|
||||||
|
// Optionally, disable `$digest` propagation to avoid unnecessary change detection.
|
||||||
|
// (Change detection is still run when the inputs of a "downgraded" component change.)
|
||||||
|
propagateDigest: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
// This is our top level application component.
|
||||||
|
myMainAngularJsModule.component('exampleApp', {
|
||||||
|
// This template makes use of the "downgraded" `ng2-heroes` component,
|
||||||
|
// but loads it lazily only when/if the user clicks the button.
|
||||||
|
// (Note that because its element is compiled by AngularJS,
|
||||||
|
// we must use kebab-case attributes for inputs and outputs.)
|
||||||
|
template: `
|
||||||
|
<link rel="stylesheet" href="./styles.css">
|
||||||
|
<button ng-click="$ctrl.toggleHeroes()">{{ $ctrl.toggleBtnText() }}</button>
|
||||||
|
<ng2-heroes
|
||||||
|
ng-if="$ctrl.showHeroes"
|
||||||
|
(add-hero)="$ctrl.setStatusMessage('Added hero ' + $event.name)"
|
||||||
|
(remove-hero)="$ctrl.setStatusMessage('Removed hero ' + $event.name)">
|
||||||
|
<h1>Heroes</h1>
|
||||||
|
<p class="extra">Status: {{ $ctrl.statusMessage }}</p>
|
||||||
|
</ng2-heroes>
|
||||||
|
`,
|
||||||
|
controller: function() {
|
||||||
|
this.showHeroes = false;
|
||||||
|
this.statusMessage = 'Ready';
|
||||||
|
|
||||||
|
this.setStatusMessage = (msg: string) => this.statusMessage = msg;
|
||||||
|
this.toggleHeroes = () => this.showHeroes = !this.showHeroes;
|
||||||
|
this.toggleBtnText = () => `${this.showHeroes ? 'Hide' : 'Show'} heroes`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// We bootstrap the Angular module as we would do in a normal Angular app.
|
||||||
|
angular.bootstrap(document.body, [myMainAngularJsModule.name]);
|
|
@ -0,0 +1,17 @@
|
||||||
|
ng2-heroes {
|
||||||
|
border: solid black 2px;
|
||||||
|
display: block;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ng1-hero {
|
||||||
|
border: solid green 2px;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
background-color: blue;
|
||||||
|
color: white;
|
||||||
|
}
|
|
@ -34,14 +34,14 @@ interface Thenable<T> {
|
||||||
* Let's assume that you have an Angular component called `ng2Heroes` that needs
|
* Let's assume that you have an Angular component called `ng2Heroes` that needs
|
||||||
* to be made available in AngularJS templates.
|
* to be made available in AngularJS templates.
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region="ng2-heroes"}
|
* {@example upgrade/static/ts/full/module.ts region="ng2-heroes"}
|
||||||
*
|
*
|
||||||
* We must create an AngularJS [directive](https://docs.angularjs.org/guide/directive)
|
* We must create an AngularJS [directive](https://docs.angularjs.org/guide/directive)
|
||||||
* that will make this Angular component available inside AngularJS templates.
|
* that will make this Angular component available inside AngularJS templates.
|
||||||
* The `downgradeComponent()` function returns a factory function that we
|
* The `downgradeComponent()` function returns a factory function that we
|
||||||
* can use to define the AngularJS directive that wraps the "downgraded" component.
|
* can use to define the AngularJS directive that wraps the "downgraded" component.
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region="ng2-heroes-wrapper"}
|
* {@example upgrade/static/ts/full/module.ts region="ng2-heroes-wrapper"}
|
||||||
*
|
*
|
||||||
* @param info contains information about the Component that is being downgraded:
|
* @param info contains information about the Component that is being downgraded:
|
||||||
*
|
*
|
||||||
|
|
|
@ -26,21 +26,21 @@ import {INJECTOR_KEY} from './constants';
|
||||||
* that will be part of the upgrade application. For example, let's assume we have
|
* that will be part of the upgrade application. For example, let's assume we have
|
||||||
* defined `HeroesService`
|
* defined `HeroesService`
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region="ng2-heroes-service"}
|
* {@example upgrade/static/ts/full/module.ts region="ng2-heroes-service"}
|
||||||
*
|
*
|
||||||
* and that we have included this in our upgrade app `NgModule`
|
* and that we have included this in our upgrade app `NgModule`
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region="ng2-module"}
|
* {@example upgrade/static/ts/full/module.ts region="ng2-module"}
|
||||||
*
|
*
|
||||||
* Now we can register the `downgradeInjectable` factory function for the service
|
* Now we can register the `downgradeInjectable` factory function for the service
|
||||||
* on an AngularJS module.
|
* on an AngularJS module.
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region="downgrade-ng2-heroes-service"}
|
* {@example upgrade/static/ts/full/module.ts region="downgrade-ng2-heroes-service"}
|
||||||
*
|
*
|
||||||
* Inside an AngularJS component's controller we can get hold of the
|
* Inside an AngularJS component's controller we can get hold of the
|
||||||
* downgraded service via the name we gave when downgrading.
|
* downgraded service via the name we gave when downgrading.
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region="example-app"}
|
* {@example upgrade/static/ts/full/module.ts region="example-app"}
|
||||||
*
|
*
|
||||||
* @param token an `InjectionToken` that identifies a service provided from Angular.
|
* @param token an `InjectionToken` that identifies a service provided from Angular.
|
||||||
*
|
*
|
||||||
|
|
|
@ -17,7 +17,86 @@ import {angular1Providers, setTempInjectorRef} from './angular1_providers';
|
||||||
import {NgAdapterInjector} from './util';
|
import {NgAdapterInjector} from './util';
|
||||||
|
|
||||||
|
|
||||||
/** @experimental */
|
/**
|
||||||
|
* <!-- TODO(gkalpak): Add link to guide. -->
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
*
|
||||||
|
* A helper function for creating an AngularJS module that can bootstrap an Angular module
|
||||||
|
* "on-demand" (possibly lazily) when a {@link downgradeComponent downgraded component} needs to be
|
||||||
|
* instantiated.
|
||||||
|
*
|
||||||
|
* *Part of the [upgrade/static](api?query=upgrade/static) library for hybrid upgrade apps that
|
||||||
|
* support AoT compilation.*
|
||||||
|
*
|
||||||
|
* It allows loading/bootstrapping the Angular part of a hybrid application lazily and not having to
|
||||||
|
* pay the cost up-front. For example, you can have an AngularJS application that uses Angular for
|
||||||
|
* specific routes and only instantiate the Angular modules if/when the user visits one of these
|
||||||
|
* routes.
|
||||||
|
*
|
||||||
|
* The Angular module will be bootstrapped once (when requested for the first time) and the same
|
||||||
|
* reference will be used from that point onwards.
|
||||||
|
*
|
||||||
|
* `downgradeModule()` requires either an `NgModuleFactory` or a function:
|
||||||
|
* - `NgModuleFactory`: If you pass an `NgModuleFactory`, it will be used to instantiate a module
|
||||||
|
* using `platformBrowser`'s {@link PlatformRef#bootstrapModuleFactory bootstrapModuleFactory()}.
|
||||||
|
* - `Function`: If you pass a function, it is expected to return a promise resolving to an
|
||||||
|
* `NgModuleRef`. The function is called with an array of extra {@link StaticProvider Providers}
|
||||||
|
* that are expected to be available from the returned `NgModuleRef`'s `Injector`.
|
||||||
|
*
|
||||||
|
* `downgradeModule()` returns the name of the created AngularJS wrapper module. You can use it to
|
||||||
|
* declare a dependency in your main AngularJS module.
|
||||||
|
*
|
||||||
|
* {@example upgrade/static/ts/lite/module.ts region="basic-how-to"}
|
||||||
|
*
|
||||||
|
* @usageNotes
|
||||||
|
*
|
||||||
|
* Apart from `UpgradeModule`, you can use the rest of the `upgrade/static` helpers as usual to
|
||||||
|
* build a hybrid application. Note that the Angular pieces (e.g. downgraded services) will not be
|
||||||
|
* available until the downgraded module has been bootstrapped, i.e. by instantiating a downgraded
|
||||||
|
* component.
|
||||||
|
*
|
||||||
|
* <div class="alert is-important">
|
||||||
|
*
|
||||||
|
* You cannot use `downgradeModule()` and `UpgradeModule` in the same hybrid application.<br />
|
||||||
|
* Use one or the other.
|
||||||
|
*
|
||||||
|
* </div>
|
||||||
|
*
|
||||||
|
* ### Differences with `UpgradeModule`
|
||||||
|
*
|
||||||
|
* Besides their different API, there are two important internal differences between
|
||||||
|
* `downgradeModule()` and `UpgradeModule` that affect the behavior of hybrid applications:
|
||||||
|
*
|
||||||
|
* 1. Unlike `UpgradeModule`, `downgradeModule()` does not bootstrap the main AngularJS module
|
||||||
|
* inside the {@link NgZone Angular zone}.
|
||||||
|
* 2. Unlike `UpgradeModule`, `downgradeModule()` does not automatically run a
|
||||||
|
* [$digest()](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) when changes are
|
||||||
|
* detected in the Angular part of the application.
|
||||||
|
*
|
||||||
|
* What this means is that applications using `UpgradeModule` will run change detection more
|
||||||
|
* frequently in order to ensure that both frameworks are properly notified about possible changes.
|
||||||
|
* This will inevitably result in more change detection runs than necessary.
|
||||||
|
*
|
||||||
|
* `downgradeModule()`, on the other side, does not try to tie the two change detection systems as
|
||||||
|
* tightly, restricting the explicit change detection runs only to cases where it knows it is
|
||||||
|
* necessary (e.g. when the inputs of a downgraded component change). This improves performance,
|
||||||
|
* especially in change-detection-heavy applications, but leaves it up to the developer to manually
|
||||||
|
* notify each framework as needed.
|
||||||
|
*
|
||||||
|
* <div class="alert is-helpful">
|
||||||
|
*
|
||||||
|
* You can manually trigger a change detection run in AngularJS using
|
||||||
|
* [scope.$apply(...)](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply) or
|
||||||
|
* [$rootScope.$digest()](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest).
|
||||||
|
*
|
||||||
|
* You can manually trigger a change detection run in Angular using {@link NgZone#run
|
||||||
|
* ngZone.run(...)}.
|
||||||
|
*
|
||||||
|
* </div>
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
export function downgradeModule<T>(
|
export function downgradeModule<T>(
|
||||||
moduleFactoryOrBootstrapFn: NgModuleFactory<T>|
|
moduleFactoryOrBootstrapFn: NgModuleFactory<T>|
|
||||||
((extraProviders: StaticProvider[]) => Promise<NgModuleRef<T>>)): string {
|
((extraProviders: StaticProvider[]) => Promise<NgModuleRef<T>>)): string {
|
||||||
|
|
|
@ -42,12 +42,12 @@ class Bindings {
|
||||||
* Let's assume that you have an AngularJS component called `ng1Hero` that needs
|
* Let's assume that you have an AngularJS component called `ng1Hero` that needs
|
||||||
* to be made available in Angular templates.
|
* to be made available in Angular templates.
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region="ng1-hero"}
|
* {@example upgrade/static/ts/full/module.ts region="ng1-hero"}
|
||||||
*
|
*
|
||||||
* We must create a `Directive` that will make this AngularJS component
|
* We must create a `Directive` that will make this AngularJS component
|
||||||
* available inside Angular templates.
|
* available inside Angular templates.
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region="ng1-hero-wrapper"}
|
* {@example upgrade/static/ts/full/module.ts region="ng1-hero-wrapper"}
|
||||||
*
|
*
|
||||||
* In this example you can see that we must derive from the `UpgradeComponent`
|
* In this example you can see that we must derive from the `UpgradeComponent`
|
||||||
* base class but also provide an {@link Directive `@Directive`} decorator. This is
|
* base class but also provide an {@link Directive `@Directive`} decorator. This is
|
||||||
|
@ -95,7 +95,7 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
|
||||||
* Instead you should derive a new class from this one and call the super constructor
|
* Instead you should derive a new class from this one and call the super constructor
|
||||||
* from the base class.
|
* from the base class.
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region="ng1-hero-wrapper" }
|
* {@example upgrade/static/ts/full/module.ts region="ng1-hero-wrapper" }
|
||||||
*
|
*
|
||||||
* * The `name` parameter should be the name of the AngularJS directive.
|
* * The `name` parameter should be the name of the AngularJS directive.
|
||||||
* * The `elementRef` and `injector` parameters should be acquired from Angular by dependency
|
* * The `elementRef` and `injector` parameters should be acquired from Angular by dependency
|
||||||
|
|
|
@ -107,17 +107,17 @@ import {NgAdapterInjector} from './util';
|
||||||
*
|
*
|
||||||
* Import the `UpgradeModule` into your top level {@link NgModule Angular `NgModule`}.
|
* Import the `UpgradeModule` into your top level {@link NgModule Angular `NgModule`}.
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region='ng2-module'}
|
* {@example upgrade/static/ts/full/module.ts region='ng2-module'}
|
||||||
*
|
*
|
||||||
* Then inject `UpgradeModule` into your Angular `NgModule` and use it to bootstrap the top level
|
* Then inject `UpgradeModule` into your Angular `NgModule` and use it to bootstrap the top level
|
||||||
* [AngularJS module](https://docs.angularjs.org/api/ng/type/angular.Module) in the
|
* [AngularJS module](https://docs.angularjs.org/api/ng/type/angular.Module) in the
|
||||||
* `ngDoBootstrap()` method.
|
* `ngDoBootstrap()` method.
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region='bootstrap-ng1'}
|
* {@example upgrade/static/ts/full/module.ts region='bootstrap-ng1'}
|
||||||
*
|
*
|
||||||
* Finally, kick off the whole process, by bootstraping your top level Angular `NgModule`.
|
* Finally, kick off the whole process, by bootstraping your top level Angular `NgModule`.
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region='bootstrap-ng2'}
|
* {@example upgrade/static/ts/full/module.ts region='bootstrap-ng2'}
|
||||||
*
|
*
|
||||||
* {@a upgrading-an-angular-1-service}
|
* {@a upgrading-an-angular-1-service}
|
||||||
* ### Upgrading an AngularJS service
|
* ### Upgrading an AngularJS service
|
||||||
|
@ -127,17 +127,17 @@ import {NgAdapterInjector} from './util';
|
||||||
*
|
*
|
||||||
* Let's say you have an AngularJS service:
|
* Let's say you have an AngularJS service:
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region="ng1-title-case-service"}
|
* {@example upgrade/static/ts/full/module.ts region="ng1-title-case-service"}
|
||||||
*
|
*
|
||||||
* Then you should define an Angular provider to be included in your `NgModule` `providers`
|
* Then you should define an Angular provider to be included in your `NgModule` `providers`
|
||||||
* property.
|
* property.
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region="upgrade-ng1-service"}
|
* {@example upgrade/static/ts/full/module.ts region="upgrade-ng1-service"}
|
||||||
*
|
*
|
||||||
* Then you can use the "upgraded" AngularJS service by injecting it into an Angular component
|
* Then you can use the "upgraded" AngularJS service by injecting it into an Angular component
|
||||||
* or service.
|
* or service.
|
||||||
*
|
*
|
||||||
* {@example upgrade/static/ts/module.ts region="use-ng1-upgraded-service"}
|
* {@example upgrade/static/ts/full/module.ts region="use-ng1-upgraded-service"}
|
||||||
*
|
*
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue