New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Document API for upgrade/static #12717
Changes from all commits
2e3fd38
4c651aa
acda98f
19db797
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/** | ||
* @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 {browser, by, element} from 'protractor'; | ||
import {verifyNoBrowserErrors} from '../../../../_common/e2e_util'; | ||
|
||
function loadPage(url: string) { | ||
browser.ng12Hybrid = true; | ||
browser.rootEl = 'example-app'; | ||
browser.get(url); | ||
} | ||
|
||
describe('upgrade(static)', () => { | ||
beforeEach(() => { loadPage('/upgrade/static/ts/'); }); | ||
afterEach(verifyNoBrowserErrors); | ||
|
||
it('should render the `ng2-heroes` component', () => { | ||
expect(element(by.css('h1')).getText()).toEqual('Heroes'); | ||
expect(element.all(by.css('p')).get(0).getText()).toEqual('There are 3 heroes.'); | ||
}); | ||
|
||
it('should render 3 ng1-hero components', () => { | ||
const heroComponents = element.all(by.css('ng1-hero')); | ||
expect(heroComponents.count()).toEqual(3); | ||
}); | ||
|
||
it('should add a new hero when the "Add Hero" button is pressed', () => { | ||
const addHeroButton = element.all(by.css('button')).last(); | ||
expect(addHeroButton.getText()).toEqual('Add Hero'); | ||
addHeroButton.click(); | ||
const heroComponents = element.all(by.css('ng1-hero')); | ||
expect(heroComponents.last().element(by.css('h2')).getText()).toEqual('Kamala Khan'); | ||
}); | ||
|
||
it('should remove a hero when the "Remove" button is pressed', () => { | ||
let firstHero = element.all(by.css('ng1-hero')).get(0); | ||
expect(firstHero.element(by.css('h2')).getText()).toEqual('Superman'); | ||
|
||
const removeHeroButton = firstHero.element(by.css('button')); | ||
expect(removeHeroButton.getText()).toEqual('Remove'); | ||
removeHeroButton.click(); | ||
|
||
const heroComponents = element.all(by.css('ng1-hero')); | ||
expect(heroComponents.count()).toEqual(2); | ||
|
||
firstHero = element.all(by.css('ng1-hero')).get(0); | ||
expect(firstHero.element(by.css('h2')).getText()).toEqual('Wonder Woman'); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
/** | ||
* @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 {Component, Directive, DoCheck, ElementRef, EventEmitter, Inject, Injectable, Injector, Input, NgModule, OnChanges, OnDestroy, OnInit, Output, SimpleChanges} from '@angular/core'; | ||
import {BrowserModule} from '@angular/platform-browser'; | ||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; | ||
import {UpgradeComponent, UpgradeModule, downgradeComponent, downgradeInjectable} from '@angular/upgrade/static'; | ||
|
||
interface Hero { | ||
name: string; | ||
description: string; | ||
} | ||
|
||
// #docregion Angular 2 Stuff | ||
// #docregion ng2-heroes | ||
// This Angular 2 component will be "downgraded" to be used in Angular 1 | ||
@Component({ | ||
selector: 'ng2-heroes', | ||
// This template uses the upgraded `ng1-hero` component | ||
// Note that because its element is compiled by Angular 2+ we must use camelCased attribute names | ||
template: `<h1>Heroes</h1> | ||
<p><ng-content></ng-content></p> | ||
<div *ngFor="let hero of heroes"> | ||
<ng1-hero [hero]="hero" (onRemove)="removeHero.emit(hero)"><strong>Super Hero</strong></ng1-hero> | ||
</div> | ||
<button (click)="addHero.emit()">Add Hero</button>`, | ||
}) | ||
class Ng2HeroesComponent { | ||
@Input() heroes: Hero[]; | ||
@Output() addHero = new EventEmitter(); | ||
@Output() removeHero = new EventEmitter(); | ||
} | ||
// #enddocregion | ||
|
||
// #docregion ng2-heroes-service | ||
// This Angular 2 service will be "downgraded" to be used in Angular 1 | ||
@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'} | ||
]; | ||
|
||
// #docregion use-ng1-upgraded-service | ||
constructor(@Inject('titleCase') titleCase: (v: string) => string) { | ||
// Change all the hero names to title case, using the "upgraded" Angular 1 service | ||
this.heroes.forEach((hero: Hero) => hero.name = titleCase(hero.name)); | ||
} | ||
// #enddocregion | ||
|
||
addHero() { | ||
this.heroes = | ||
this.heroes.concat([{name: 'Kamala Khan', description: 'Epic shape-shifting healer'}]); | ||
} | ||
|
||
removeHero(hero: Hero) { this.heroes = this.heroes.filter((item: Hero) => item !== hero); } | ||
} | ||
// #enddocregion | ||
|
||
// #docregion ng1-hero-wrapper | ||
// This Angular 2 directive will act as an interface to the "upgraded" Angular 1 component | ||
@Directive({selector: 'ng1-hero'}) | ||
class Ng1HeroComponentWrapper extends UpgradeComponent implements OnInit, OnChanges, DoCheck, | ||
OnDestroy { | ||
// The names of the input and output properties here must match the names of the | ||
// `<` and `&` bindings in the Angular 1 component that is being wrapped | ||
@Input() hero: Hero; | ||
@Output() onRemove: EventEmitter<void>; | ||
constructor(@Inject(ElementRef) elementRef: ElementRef, @Inject(Injector) injector: Injector) { | ||
// We must pass the name of the directive as used by Angular 1 to the super | ||
super('ng1Hero', elementRef, injector); | ||
} | ||
|
||
// For this class to work when compiled with AoT, we must implement these lifecycle hooks | ||
// because the AoT compiler will not realise that the super class implements them | ||
ngOnInit() { super.ngOnInit(); } | ||
|
||
ngOnChanges(changes: SimpleChanges) { super.ngOnChanges(changes); } | ||
|
||
ngDoCheck() { super.ngDoCheck(); } | ||
|
||
ngOnDestroy() { super.ngOnDestroy(); } | ||
} | ||
// #enddocregion | ||
|
||
// #docregion ng2-module | ||
// This NgModule represents the Angular 2 pieces of the application | ||
@NgModule({ | ||
declarations: [Ng2HeroesComponent, Ng1HeroComponentWrapper], | ||
providers: [ | ||
HeroesService, | ||
// #docregion upgrade-ng1-service | ||
// Register an Angular 2+ provider whose value is the "upgraded" Angular 1 service | ||
{provide: 'titleCase', useFactory: (i: any) => i.get('titleCase'), deps: ['$injector']} | ||
// #enddocregion | ||
], | ||
// All components that are to be "downgraded" must be declared as `entryComponents` | ||
entryComponents: [Ng2HeroesComponent], | ||
// We must import `UpgradeModule` to get access to the Angular 1 core services | ||
imports: [BrowserModule, UpgradeModule] | ||
}) | ||
class Ng2AppModule { | ||
ngDoBootstrap() { /* this is a placeholder to stop the boostrapper from complaining */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move comment into the body? Maybe use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand. It is in the body already There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean that it should be split over multiple lines There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think he means in the next line (; |
||
} | ||
} | ||
// #enddocregion | ||
// #enddocregion | ||
|
||
|
||
// #docregion Angular 1 Stuff | ||
// #docregion ng1-module | ||
// This Angular 1 module represents the Angular 1 pieces of the application | ||
const ng1AppModule = angular.module('ng1AppModule', []); | ||
// #enddocregion | ||
|
||
// #docregion ng1-hero | ||
// This Angular 1 component will be "upgraded" to be used in Angular 2+ | ||
ng1AppModule.component('ng1Hero', { | ||
bindings: {hero: '<', onRemove: '&'}, | ||
transclude: true, | ||
template: `<div class="title" ng-transclude></div> | ||
<h2>{{ $ctrl.hero.name }}</h2> | ||
<p>{{ $ctrl.hero.description }}</p> | ||
<button ng-click="$ctrl.onRemove()">Remove</button>` | ||
}); | ||
// #enddocregion | ||
|
||
// #docregion ng1-title-case-service | ||
// This Angular 1 service will be "upgraded" to be used in Angular 2+ | ||
ng1AppModule.factory( | ||
'titleCase', | ||
() => (value: string) => value.replace(/((^|\s)[a-z])/g, (_, c) => c.toUpperCase())); | ||
// #enddocregion | ||
|
||
// #docregion downgrade-ng2-heroes-service | ||
// Register an Angular 1 service, whose value is the "downgraded" Angular 2+ injectable. | ||
ng1AppModule.factory('heroesService', downgradeInjectable(HeroesService)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add explanation comments. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again the explanation is in the API docs themselves. Does it need to be in the code too? |
||
// #enddocregion | ||
|
||
// #docregion ng2-heroes-wrapper | ||
// This is directive will act as the interface to the "downgraded" Angular 2+ component | ||
ng1AppModule.directive( | ||
'ng2Heroes', | ||
downgradeComponent( | ||
// The inputs and outputs here must match the relevant names of the properties on the | ||
// "downgraded" component | ||
{component: Ng2HeroesComponent, inputs: ['heroes'], outputs: ['addHero', 'removeHero']})); | ||
// #enddocregion | ||
|
||
// #docregion example-app | ||
// This is our top level application component | ||
ng1AppModule.component('exampleApp', { | ||
// We inject the "downgraded" HeroesService into this Angular 1 component | ||
// (We don't need the `HeroesService` type for Angular 1 DI - it just helps with TypeScript | ||
// compilation) | ||
controller: [ | ||
'heroesService', function(heroesService: HeroesService) { this.heroesService = heroesService; } | ||
], | ||
// This template make use of the downgraded `ng2-heroes` component | ||
// Note that because its element is compiled by Angular 1 we must use kebab-case attributes for | ||
// inputs and outputs | ||
template: `<link rel="stylesheet" href="./styles.css"> | ||
<ng2-heroes [heroes]="$ctrl.heroesService.heroes" (add-hero)="$ctrl.heroesService.addHero()" (remove-hero)="$ctrl.heroesService.removeHero($event)"> | ||
There are {{ $ctrl.heroesService.heroes.length }} heroes. | ||
</ng2-heroes>` | ||
}); | ||
// #enddocregion | ||
// #enddocregion | ||
|
||
|
||
// #docregion bootstrap | ||
// First we bootstrap the Angular 2 HybridModule | ||
// (We are using the dynamic browser platform as this example has not been compiled AoT) | ||
platformBrowserDynamic().bootstrapModule(Ng2AppModule).then(ref => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add a comment about why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we consider using ngc when building the |
||
// Once Angular 2 bootstrap is complete then we bootstrap the Angular 1 module | ||
const upgrade = ref.injector.get(UpgradeModule) as UpgradeModule; | ||
upgrade.bootstrap(document.body, [ng1AppModule.name]); | ||
}); | ||
// #enddocregion |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add docs explaining the reasons why each line is there and what it does?
For example
why
ng1Hero` string is present in the call to super.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do mention this information in the API doc itself, which refers to this code sample