docs(upgrade): update to latest release + tweaks (#2460)

This commit is contained in:
Jesús Rodríguez 2016-09-27 09:22:38 +01:00 committed by Ward Bell
parent e82f28b844
commit 66c630df99
83 changed files with 903 additions and 749 deletions

View File

@ -1,18 +1,29 @@
declare var angular: any;
// #docregion ngmodule
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
imports: [ BrowserModule ]
})
export class AppModule {}
// #enddocregion ngmodule
angular.module('heroApp', [])
.controller('MainCtrl', function() {
this.message = 'Hello world';
});
// #docregion bootstrap
import { UpgradeAdapter } from '@angular/upgrade';
// #enddocregion bootstrap
angular.module('heroApp', [])
.controller('MainCtrl', function() {
this.message = 'Hello world';
});
// This blank is expected to trigger the docplaster
// #docregion bootstrap
const upgradeAdapter = new UpgradeAdapter();
const upgradeAdapter = new UpgradeAdapter(AppModule);
upgradeAdapter.bootstrap(document.body, ['heroApp'], {strictDi: true});
// #enddocregion bootstrap

View File

@ -1,3 +1,16 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
imports: [ BrowserModule ]
})
export class AppModule {}
angular.module('heroApp', [])
.controller('MainCtrl', function() {
this.message = 'Hello world';
});
// #docregion
import { UpgradeAdapter } from '@angular/upgrade';
export const upgradeAdapter = new UpgradeAdapter();
export const upgradeAdapter = new UpgradeAdapter(AppModule);

View File

@ -1,10 +1,18 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UpgradeAdapter } from '@angular/upgrade';
import { MainController } from './main.controller';
import { HeroDetailComponent } from './hero-detail.component';
@NgModule({
imports: [ BrowserModule ],
declarations: [ HeroDetailComponent ]
})
export class AppModule {}
declare var angular: any;
const upgradeAdapter = new UpgradeAdapter();
const upgradeAdapter = new UpgradeAdapter(AppModule);
angular.module('heroApp', [])
.controller('MainController', MainController)

View File

@ -1,3 +1,19 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HeroDetailComponent } from './hero-detail.component';
@NgModule({
imports: [ BrowserModule ],
declarations: [ HeroDetailComponent ]
})
export class AppModule {}
angular.module('heroApp', [])
.controller('MainCtrl', function() {
this.message = 'Hello world';
});
// #docregion
import { UpgradeAdapter } from '@angular/upgrade';
export const upgradeAdapter = new UpgradeAdapter();
export const upgradeAdapter = new UpgradeAdapter(AppModule);

View File

@ -5,8 +5,6 @@ import { upgradeAdapter } from './upgrade_adapter';
declare var angular: any;
// #docregion register
upgradeAdapter.addProvider(Heroes);
angular.module('heroApp', [])
.factory('heroes', upgradeAdapter.downgradeNg2Provider(Heroes))
.component('heroDetail', heroDetailComponent);

View File

@ -1,3 +1,19 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
// #docregion ngmodule
import { Heroes } from './heroes';
@NgModule({
imports: [ BrowserModule ],
providers: [ Heroes ]
})
export class AppModule {}
// #enddocregion ngmodule
angular.module('heroApp', [])
.controller('MainCtrl', function() {
this.message = 'Hello world';
});
// #docregion
import { UpgradeAdapter } from '@angular/upgrade';
export const upgradeAdapter = new UpgradeAdapter();
export const upgradeAdapter = new UpgradeAdapter(AppModule);

View File

@ -1,10 +1,7 @@
// #docregion
import { Component } from '@angular/core';
import { upgradeAdapter } from './upgrade_adapter';
import { Hero } from '../hero';
const HeroDetail = upgradeAdapter.upgradeNg1Component('heroDetail');
@Component({
selector: 'my-container',
template: `
@ -12,8 +9,7 @@ const HeroDetail = upgradeAdapter.upgradeNg1Component('heroDetail');
<!-- Everything here will get transcluded -->
<p>{{hero.description}}</p>
</hero-detail>
`,
directives: [HeroDetail]
`
})
export class ContainerComponent {
hero = new Hero(1, 'Windstorm', 'Specific powers of controlling winds');

View File

@ -1,3 +1,20 @@
// #docregion
import { UpgradeAdapter } from '@angular/upgrade';
export const upgradeAdapter = new UpgradeAdapter();
import { NgModule, forwardRef } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ContainerComponent } from './container.component';
export const upgradeAdapter = new UpgradeAdapter(forwardRef(() => AppModule));
const HeroDetail = upgradeAdapter.upgradeNg1Component('heroDetail');
@NgModule({
imports: [ BrowserModule ],
declarations: [ ContainerComponent, HeroDetail ]
})
export class AppModule {}
angular.module('heroApp', [])
.controller('MainCtrl', function() {
this.message = 'Hello world';
});

View File

@ -1,11 +1,21 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MainController } from './main.controller';
// #docregion downgradecomponent
import { HeroDetailComponent } from './hero-detail.component';
// #enddocregion downgradecomponent
@NgModule({
imports: [ BrowserModule ],
declarations: [ HeroDetailComponent ]
})
export class AppModule {}
import { UpgradeAdapter } from '@angular/upgrade';
const upgradeAdapter = new UpgradeAdapter();
const upgradeAdapter = new UpgradeAdapter(AppModule);
// #docregion downgradecomponent

View File

@ -1,10 +1,19 @@
// #docregion downgradecomponent
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
// #docregion downgradecomponent, ngmodule
import { HeroDetailComponent } from './hero-detail.component';
// #enddocregion downgradecomponent
@NgModule({
imports: [ BrowserModule ],
declarations: [ HeroDetailComponent ]
})
export class AppModule {}
// #enddocregion ngmodule
import { UpgradeAdapter } from '@angular/upgrade';
const upgradeAdapter = new UpgradeAdapter();
const upgradeAdapter = new UpgradeAdapter(AppModule);
// #docregion downgradecomponent

View File

@ -1,10 +1,7 @@
// #docregion
import { Component } from '@angular/core';
import { upgradeAdapter } from './upgrade_adapter';
import { Hero } from '../hero';
const HeroDetail = upgradeAdapter.upgradeNg1Component('heroDetail');
@Component({
selector: 'my-container',
template: `
@ -12,8 +9,7 @@ const HeroDetail = upgradeAdapter.upgradeNg1Component('heroDetail');
<hero-detail [hero]="hero"
(deleted)="heroDeleted($event)">
</hero-detail>
`,
directives: [HeroDetail]
`
})
export class ContainerComponent {
hero = new Hero(1, 'Windstorm');

View File

@ -1,3 +1,20 @@
// #docregion
import { UpgradeAdapter } from '@angular/upgrade';
export const upgradeAdapter = new UpgradeAdapter();
import { NgModule, forwardRef } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ContainerComponent } from './container.component';
export const upgradeAdapter = new UpgradeAdapter(forwardRef(() => AppModule));
const HeroDetail = upgradeAdapter.upgradeNg1Component('heroDetail');
@NgModule({
imports: [ BrowserModule ],
declarations: [ ContainerComponent, HeroDetail ]
})
export class AppModule {}
angular.module('heroApp', [])
.controller('MainCtrl', function() {
this.message = 'Hello world';
});

View File

@ -1,16 +1,12 @@
// #docregion
import { Component } from '@angular/core';
import { upgradeAdapter } from './upgrade_adapter';
const HeroDetail = upgradeAdapter.upgradeNg1Component('heroDetail');
@Component({
selector: 'my-container',
template: `
<h1>Tour of Heroes</h1>
<hero-detail></hero-detail>
`,
directives: [HeroDetail]
`
})
export class ContainerComponent {

View File

@ -1,3 +1,23 @@
// #docregion
import { UpgradeAdapter } from '@angular/upgrade';
export const upgradeAdapter = new UpgradeAdapter();
import { NgModule, forwardRef } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ContainerComponent } from './container.component';
export const upgradeAdapter = new UpgradeAdapter(forwardRef(() => AppModule));
// #docregion heroupgrade
const HeroDetail = upgradeAdapter.upgradeNg1Component('heroDetail');
@NgModule({
imports: [ BrowserModule ],
declarations: [ ContainerComponent, HeroDetail ]
})
export class AppModule {}
// #enddocregion heroupgrade
angular.module('heroApp', [])
.controller('MainCtrl', function() {
this.message = 'Hello world';
});

View File

@ -16,9 +16,6 @@
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
<script>
System.import('app/1-2-hybrid-bootstrap/app.module')
.then(null, console.error.bind(console));

View File

@ -16,9 +16,6 @@
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
<script>
System.import('app/1-2-hybrid-shared-adapter-bootstrap/app.module')
.then(null, console.error.bind(console));

View File

@ -16,9 +16,6 @@
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
<script>
System.import('app/1-to-2-projection/app.module')
.then(null, console.error.bind(console));

View File

@ -16,9 +16,6 @@
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
<script>
System.import('app/1-to-2-providers/app.module')
.then(null, console.error.bind(console));

View File

@ -16,9 +16,6 @@
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
<script>
System.import('app/2-to-1-providers/app.module')
.then(null, console.error.bind(console));

View File

@ -16,9 +16,6 @@
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
<script>
System.import('app/2-to-1-transclusion/app.module')
.then(null, console.error.bind(console));

View File

@ -16,9 +16,6 @@
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
<script>
System.import('app/downgrade-io/app.module')
.then(null, console.error.bind(console));

View File

@ -16,9 +16,6 @@
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
<script>
System.import('app/downgrade-static/app.module')
.then(null, console.error.bind(console));

View File

@ -16,9 +16,6 @@
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
<script>
System.import('app/upgrade-io/app.module')
.then(null, console.error.bind(console));

View File

@ -16,9 +16,6 @@
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
<script>
System.import('app/upgrade-static/app.module')
.then(null, console.error.bind(console));

View File

@ -0,0 +1,11 @@
// #docregion
'use strict';
// Define the `phonecatApp` Angular 1 module
angular.module('phonecatApp', [
'ngAnimate',
'ngRoute',
'core',
'phoneDetail',
'phoneList',
]);

View File

@ -1,10 +1,51 @@
'use strict';
// #docplaster
// #docregion bare
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
// #enddocregion bare
// #docregion httpmodule
import { HttpModule } from '@angular/http';
// #enddocregion httpmodule
// #docregion phonelist
import { FormsModule } from '@angular/forms';
// #enddocregion phonelist
// #docregion phone
import { Phone } from './core/phone/phone.service';
// #enddocregion phone
// #docregion checkmarkpipe
import { CheckmarkPipe } from './core/checkmark/checkmark.pipe';
// #enddocregion checkmarkpipe
// #docregion phonelist
import { PhoneListComponent } from './phone-list/phone-list.component';
// #enddocregion phonelist
// #docregion phonedetail
import { PhoneDetailComponent } from './phone-detail/phone-detail.component';
// #enddocregion phonedetail
// Define the `phonecatApp` module
angular.module('phonecatApp', [
'ngAnimate',
'ngRoute',
'core',
'phoneDetail',
'phoneList',
]);
// #docregion bare, httpmodule, phone, phonelist, phonedetail, checkmarkpipe
@NgModule({
imports: [
BrowserModule,
// #enddocregion bare
HttpModule,
// #enddocregion httpmodule, phone
FormsModule,
// #docregion bare, httpmodule, phone
],
// #enddocregion bare, httpmodule, phone
declarations: [
PhoneListComponent,
// #enddocregion phonelist
PhoneDetailComponent,
// #enddocregion phonedetail
CheckmarkPipe
// #docregion phonelist, phonedetail
],
// #docregion phone
providers: [ Phone ]
// #docregion bare, httpmodule, phonelist
})
export class AppModule {}
// #enddocregion httpmodule, phone, phonelist, phonedetail, checkmarkpipe
// #enddocregion bare

View File

@ -1,19 +1,11 @@
// #docregion
import {
beforeEachProviders,
inject
} from '@angular/core/testing';
import { CheckmarkPipe } from './checkmark.pipe';
describe('CheckmarkPipe', function() {
beforeEachProviders(() => [CheckmarkPipe]);
it('should convert boolean values to unicode checkmark or cross',
inject([CheckmarkPipe], function(checkmarkPipe: CheckmarkPipe) {
expect(checkmarkPipe.transform(true)).toBe('\u2713');
expect(checkmarkPipe.transform(false)).toBe('\u2718');
})
);
it('should convert boolean values to unicode checkmark or cross', function () {
const checkmarkPipe = new CheckmarkPipe();
expect(checkmarkPipe.transform(true)).toBe('\u2713');
expect(checkmarkPipe.transform(false)).toBe('\u2718');
});
});

View File

@ -3,9 +3,7 @@ import { Pipe, PipeTransform } from '@angular/core';
@Pipe({name: 'checkmark'})
export class CheckmarkPipe implements PipeTransform {
transform(input: boolean) {
return input ? '\u2713' : '\u2718';
}
}

View File

@ -1,11 +1,5 @@
// #docregion
import {
describe,
beforeEach,
beforeEachProviders,
it,
inject
} from '@angular/core/testing';
import { inject, TestBed } from '@angular/core/testing';
import {
Http,
BaseRequestOptions,
@ -24,29 +18,28 @@ describe('Phone', function() {
];
let mockBackend: MockBackend;
beforeEachProviders(() => [
Phone,
MockBackend,
BaseRequestOptions,
{ provide: Http,
useFactory: (backend: MockBackend, options: BaseRequestOptions) =>
new Http(backend, options),
deps: [MockBackend, BaseRequestOptions]
}
]);
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
Phone,
MockBackend,
BaseRequestOptions,
{ provide: Http,
useFactory: (backend: MockBackend, options: BaseRequestOptions) => new Http(backend, options),
deps: [MockBackend, BaseRequestOptions]
}
]
});
});
beforeEach(inject([MockBackend, Phone],
(_mockBackend_: MockBackend, _phone_: Phone) => {
beforeEach(inject([MockBackend, Phone], (_mockBackend_: MockBackend, _phone_: Phone) => {
mockBackend = _mockBackend_;
phone = _phone_;
}));
it('should fetch the phones data from `/phones/phones.json`',
(done: () => void) => {
it('should fetch the phones data from `/phones/phones.json`', (done: () => void) => {
mockBackend.connections.subscribe((conn: MockConnection) => {
conn.mockRespond(new Response(new ResponseOptions({
body: JSON.stringify(phonesData)
})));
conn.mockRespond(new Response(new ResponseOptions({body: JSON.stringify(phonesData)})));
});
phone.query().subscribe(result => {
expect(result).toEqual(phonesData);
@ -55,3 +48,4 @@ describe('Phone', function() {
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,9 +1,8 @@
// #docregion import-adapter
import { UpgradeAdapter } from '@angular/upgrade';
import { AppModule } from './app.module';
// #enddocregion import-adapter
// #docregion import-http
import { HTTP_PROVIDERS } from '@angular/http';
// #enddocregion import-http
// #docregion phone-service
import { Phone } from './core/phone/phone.service';
@ -17,17 +16,9 @@ import { PhoneDetailComponent } from './phone-detail/phone-detail.component';
// #enddocregion phone-detail
// #docregion init-adapter
let upgradeAdapter = new UpgradeAdapter();
let upgradeAdapter = new UpgradeAdapter(AppModule);
// #enddocregion init-adapter
// #docregion add-http
upgradeAdapter.addProvider(HTTP_PROVIDERS);
// #enddocregion add-http
// #docregion phone-service
upgradeAdapter.addProvider(Phone);
// #enddocregion phone-service
// #docregion routeparams
upgradeAdapter.upgradeNg1Provider('$routeParams');
// #enddocregion routeparams
@ -42,8 +33,7 @@ angular.module('core.phone')
angular.module('phoneList')
.directive(
'phoneList',
<angular.IDirectiveFactory>
upgradeAdapter.downgradeNg2Component(PhoneListComponent)
upgradeAdapter.downgradeNg2Component(PhoneListComponent) as angular.IDirectiveFactory
);
// #enddocregion phone-list
// #docregion phone-detail
@ -51,8 +41,7 @@ angular.module('phoneList')
angular.module('phoneDetail')
.directive(
'phoneDetail',
<angular.IDirectiveFactory>
upgradeAdapter.downgradeNg2Component(PhoneDetailComponent)
upgradeAdapter.downgradeNg2Component(PhoneDetailComponent) as angular.IDirectiveFactory
);
// #enddocregion phone-detail

View File

@ -1,21 +1,15 @@
// #docregion
import { HTTP_PROVIDERS } from '@angular/http';
// #docregion activatedroute
import { ActivatedRoute } from '@angular/router';
// #enddocregion activatedroute
import { Observable } from 'rxjs/Rx';
import {
describe,
beforeEachProviders,
inject,
it,
expect
} from '@angular/core/testing';
import {
TestComponentBuilder,
ComponentFixture
} from '@angular/compiler/testing';
import { async, TestBed } from '@angular/core/testing';
import { PhoneDetailComponent } from './phone-detail.component';
import { Phone, PhoneData } from '../core/phone/phone.service';
import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe';
function xyzPhoneData(): PhoneData {
return {
@ -25,28 +19,41 @@ function xyzPhoneData(): PhoneData {
};
}
class MockPhone extends Phone {
class MockPhone {
get(id: string): Observable<PhoneData> {
return Observable.of(xyzPhoneData());
}
}
// #docregion activatedroute
class ActivatedRouteMock {
constructor(public snapshot: any) {}
}
// #enddocregion activatedroute
describe('PhoneDetailComponent', () => {
beforeEachProviders(() => [
{ provide: Phone, useClass: MockPhone },
{ provide: '$routeParams', useValue: {phoneId: 'xyz'}},
HTTP_PROVIDERS
]);
// #docregion activatedroute
it('should fetch phone detail',
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(PhoneDetailComponent)
.then((fixture: ComponentFixture<PhoneDetailComponent>) => {
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1')).toHaveText(xyzPhoneData().name);
});
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CheckmarkPipe, PhoneDetailComponent ],
providers: [
{ provide: Phone, useClass: MockPhone },
{ provide: ActivatedRoute, useValue: new ActivatedRouteMock({ params: { 'phoneId': 1 } }) }
]
})
.compileComponents();
}));
// #enddocregion activatedroute
it('should fetch phone detail', () => {
const fixture = TestBed.createComponent(PhoneDetailComponent);
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain(xyzPhoneData().name);
});
});

View File

@ -1,10 +1,10 @@
// #docplaster
// #docregion initialclass
import { Component, Inject } from '@angular/core';
import { Phone, PhoneData } from '../core/phone/phone.service';
// #enddocregion initialclass
// #docregion checkmark-pipe
import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe';
// #docregion initialclass
@Component({
@ -12,7 +12,6 @@ import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe';
selector: 'phone-detail',
templateUrl: 'phone-detail.template.html',
// #enddocregion initialclass
pipes: [ CheckmarkPipe ]
// #docregion initialclass
})
// #enddocregion checkmark-pipe

View File

@ -1,66 +1,66 @@
/* tslint:disable */
// #docregion
import { HTTP_PROVIDERS } from '@angular/http';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs/Rx';
import {
describe,
beforeEachProviders,
inject,
it,
expect
} from '@angular/core/testing';
import {
TestComponentBuilder,
ComponentFixture
} from '@angular/compiler/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SpyLocation } from '@angular/common/testing';
import { PhoneListComponent } from './phone-list.component';
import { Phone, PhoneData } from '../core/phone/phone.service';
class MockPhone extends Phone {
class ActivatedRouteMock {
constructor(public snapshot: any) {}
}
class MockPhone {
query(): Observable<PhoneData[]> {
console.log('mocking here');
return Observable.of(
[
{name: 'Nexus S', snippet: '', images: []},
{name: 'Motorola DROID', snippet: '', images: []}
]
);
return Observable.of([
{name: 'Nexus S', snippet: '', images: []},
{name: 'Motorola DROID', snippet: '', images: []}
]);
}
}
let fixture: ComponentFixture<PhoneListComponent>;
describe('PhoneList', () => {
beforeEachProviders(() => [
{ provide: Phone, useClass: MockPhone },
HTTP_PROVIDERS
]);
it('should create "phones" model with 2 phones fetched from xhr',
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(PhoneListComponent)
.then((fixture: ComponentFixture<PhoneListComponent>) => {
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelectorAll('.phone-list-item').length).toBe(2);
expect(
compiled.querySelector('.phone-list-item:nth-child(1)').textContent
).toContain('Motorola DROID');
expect(
compiled.querySelector('.phone-list-item:nth-child(2)').textContent
).toContain('Nexus S');
});
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PhoneListComponent ],
providers: [
{ provide: ActivatedRoute, useValue: new ActivatedRouteMock({ params: { 'phoneId': 1 } }) },
{ provide: Location, useClass: SpyLocation },
{ provide: Phone, useClass: MockPhone },
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents();
}));
it('should set the default value of orderProp model',
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(PhoneListComponent)
.then((fixture: ComponentFixture<PhoneListComponent>) => {
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(
compiled.querySelector('select option:last-child').selected
).toBe(true);
});
}));
beforeEach(() => {
fixture = TestBed.createComponent(PhoneListComponent);
});
it('should create "phones" model with 2 phones fetched from xhr', () => {
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelectorAll('.phone-list-item').length).toBe(2);
expect(
compiled.querySelector('.phone-list-item:nth-child(1)').textContent
).toContain('Motorola DROID');
expect(
compiled.querySelector('.phone-list-item:nth-child(2)').textContent
).toContain('Nexus S');
});
xit('should set the default value of orderProp model', () => {
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(
compiled.querySelector('select option:last-child').selected
).toBe(true);
});
});

View File

@ -38,20 +38,20 @@ export class PhoneListComponent {
}
private sortPhones(phones: PhoneData[]) {
if (phones && this.orderProp) {
return phones
.slice(0) // Make a copy
.sort((a, b) => {
if (a[this.orderProp] < b[this.orderProp]) {
return -1;
} else if ([b[this.orderProp] < a[this.orderProp]]) {
return 1;
} else {
return 0;
}
});
}
return phones;
if (phones && this.orderProp) {
return phones
.slice(0) // Make a copy
.sort((a, b) => {
if (a[this.orderProp] < b[this.orderProp]) {
return -1;
} else if ([b[this.orderProp] < a[this.orderProp]]) {
return 1;
} else {
return 0;
}
});
}
return phones;
}
// #enddocregion getphones
// #docregion initialclass

View File

@ -17,7 +17,7 @@
<script src="https://code.angularjs.org/1.5.5/angular-resource.js"></script>
<script src="https://code.angularjs.org/1.5.5/angular-route.js"></script>
<script src="app.module.js"></script>
<script src="app.module.ng1.js"></script>
<script src="app.config.js"></script>
<script src="app.animations.js"></script>
<script src="core/core.module.js"></script>

View File

@ -1,21 +1,25 @@
// #docregion
// /*global jasmine, __karma__, window*/
Error.stackTraceLimit = Infinity;
Error.stackTraceLimit = 0; // "No stacktrace"" is usually best for app testing.
// Uncomment to get full stacktrace output. Sometimes helpful, usually not.
// Error.stackTraceLimit = Infinity; //
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
__karma__.loaded = function () {
};
var builtPath = '/base/app/';
__karma__.loaded = function () { };
function isJsFile(path) {
return path.slice(-3) == '.js';
}
function isSpecFile(path) {
return /\.spec\.js$/.test(path);
return /\.spec\.(.*\.)?js$/.test(path);
}
function isBuiltFile(path) {
var builtPath = '/base/app/';
return isJsFile(path) && (path.substr(0, builtPath.length) == builtPath);
}
@ -25,27 +29,61 @@ var allSpecFiles = Object.keys(window.__karma__.files)
System.config({
baseURL: '/base',
packageWithIndex: true // sadly, we can't use umd packages (yet?)
// Extend usual application package list with test folder
packages: { 'testing': { main: 'index.js', defaultExtension: 'js' } },
// Assume npm: is set in `paths` in systemjs.config
// Map the angular testing umd bundles
map: {
'@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
'@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
'@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
'@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
'@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
'@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
'@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
'@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js',
},
});
System.import('systemjs.config.js')
.then(() => Promise.all([
System.import('@angular/core/testing'),
System.import('@angular/platform-browser-dynamic/testing')
]))
.then((providers) => {
var coreTesting = providers[0];
.then(importSystemJsExtras)
.then(initTestBed)
.then(initTesting);
/** Optional SystemJS configuration extras. Keep going w/o it */
function importSystemJsExtras(){
return System.import('systemjs.config.extras.js')
.catch(function(reason) {
console.log(
'Warning: System.import could not load the optional "systemjs.config.extras.js". Did you omit it by accident? Continuing without it.'
);
console.log(reason);
});
}
function initTestBed(){
return Promise.all([
System.import('@angular/core/testing'),
System.import('@angular/platform-browser-dynamic/testing')
])
.then(function (providers) {
var coreTesting = providers[0];
var browserTesting = providers[1];
coreTesting.TestBed.initTestEnvironment(
browserTesting.BrowserDynamicTestingModule,
browserTesting.platformBrowserDynamicTesting());
})
.then(function () {
// Finally, load all spec files.
// This will run the tests directly.
return Promise.all(
allSpecFiles.map(function (moduleName) {
return System.import(moduleName);
}));
})
}
// Import all spec files and start karma
function initTesting () {
return Promise.all(
allSpecFiles.map(function (moduleName) {
return System.import(moduleName);
})
)
.then(__karma__.start, __karma__.error);
}

View File

@ -63,11 +63,10 @@ module.exports = function(config) {
frameworks: ['jasmine'],
browsers: ['Chrome', 'Firefox'],
browsers: ['Chrome'],
plugins: [
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-jasmine'
]

View File

@ -1,59 +1,47 @@
/**
* System configuration for Angular 2 samples
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*/
(function(global) {
// map tells the System loader where to look for things
(function (global) {
// #docregion paths
var map = {
'app': '/app', // 'dist',
'@angular': '/node_modules/@angular',
'angular-in-memory-web-api': '/node_modules/angular-in-memory-web-api',
'rxjs': '/node_modules/rxjs'
};
var packages = {
'/app': { main: 'main.js', defaultExtension: 'js' },
'rxjs': { defaultExtension: 'js' },
'angular-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
};
// #enddocregion paths
var ngPackageNames = [
'common',
'compiler',
'core',
'forms',
'http',
'platform-browser',
'platform-browser-dynamic',
'router',
'router-deprecated',
'upgrade',
];
// Individual files (~300 requests):
function packIndex(pkgName) {
packages['@angular/'+pkgName] = { main: 'index.js', defaultExtension: 'js' };
}
// Bundled (~40 requests):
function packUmd(pkgName) {
packages['@angular/'+pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };
}
var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;
// Add package entries for angular packages
ngPackageNames.forEach(setPackageConfig);
var config = {
map: map,
packages: packages
}
System.config(config);
System.config({
paths: {
// paths serve as alias
'npm:': '/node_modules/'
},
map: {
app: '/app',
// #enddocregion paths
// angular bundles
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
// other libraries
'rxjs': 'npm:rxjs',
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
// #docregion paths
},
// #enddocregion paths
// packages tells the System loader how to load when no filename and/or no extension
packages: {
'app': {
main: './main.js',
defaultExtension: 'js'
},
rxjs: {
defaultExtension: 'js'
},
'angular-in-memory-web-api': {
main: './index.js',
defaultExtension: 'js'
}
}
});
})(this);

View File

@ -0,0 +1,23 @@
// #docregion
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { APP_BASE_HREF, HashLocationStrategy, LocationStrategy } from '@angular/common';
import { PhoneDetailComponent } from './phone-detail/phone-detail.component';
import { PhoneListComponent } from './phone-list/phone-list.component';
const routes: Routes = [
{ path: '', redirectTo: 'phones', pathMatch: 'full' },
{ path: 'phones', component: PhoneListComponent },
{ path: 'phones/:phoneId', component: PhoneDetailComponent }
];
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ],
providers: [
{ provide: APP_BASE_HREF, useValue: '!' },
{ provide: LocationStrategy, useClass: HashLocationStrategy },
]
})
export class AppRoutingModule {}

View File

@ -1,18 +1,9 @@
// #docregion
import { Component } from '@angular/core';
import { RouteConfig, ROUTER_DIRECTIVES } from '@angular/router-deprecated';
import { PhoneListComponent } from './phone-list/phone-list.component';
import { PhoneDetailComponent } from './phone-detail/phone-detail.component';
@RouteConfig([
{path: '/phones', name: 'Phones', component: PhoneListComponent},
{path: '/phones/:phoneId', name: 'Phone', component: PhoneDetailComponent},
{path: '/', redirectTo: ['Phones']}
])
@Component({
selector: 'phonecat-app',
template: '<router-outlet></router-outlet>',
directives: [ROUTER_DIRECTIVES]
template: '<router-outlet></router-outlet>'
})
export class AppComponent {
}

View File

@ -0,0 +1,34 @@
// #docregion
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { CheckmarkPipe } from './core/checkmark/checkmark.pipe';
import { Phone } from './core/phone/phone.service';
import { PhoneDetailComponent } from './phone-detail/phone-detail.component';
import { PhoneListComponent } from './phone-list/phone-list.component';
@NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule,
AppRoutingModule
],
declarations: [
AppComponent,
PhoneListComponent,
CheckmarkPipe,
PhoneDetailComponent
],
providers: [
Phone,
],
// #docregion bootstrap
bootstrap: [ AppComponent ]
// #enddocregion bootstrap
})
export class AppModule {}

View File

@ -1,21 +1,10 @@
import {
describe,
beforeEachProviders,
it,
inject,
expect
} from '@angular/core/testing';
import { CheckmarkPipe } from './checkmark.pipe';
describe('CheckmarkPipe', function() {
beforeEachProviders(() => [CheckmarkPipe]);
it('should convert boolean values to unicode checkmark or cross',
inject([CheckmarkPipe], function(checkmarkPipe: CheckmarkPipe) {
expect(checkmarkPipe.transform(true)).toBe('\u2713');
expect(checkmarkPipe.transform(false)).toBe('\u2718');
})
);
it('should convert boolean values to unicode checkmark or cross', function () {
const checkmarkPipe = new CheckmarkPipe();
expect(checkmarkPipe.transform(true)).toBe('\u2713');
expect(checkmarkPipe.transform(false)).toBe('\u2718');
});
});

View File

@ -1,10 +1,4 @@
import {
describe,
beforeEach,
beforeEachProviders,
it,
inject
} from '@angular/core/testing';
import { inject, TestBed } from '@angular/core/testing';
import {
Http,
BaseRequestOptions,
@ -23,15 +17,19 @@ describe('Phone', function() {
];
let mockBackend: MockBackend;
beforeEachProviders(() => [
Phone,
MockBackend,
BaseRequestOptions,
{ provide: Http,
useFactory: (backend: MockBackend, options: BaseRequestOptions) => new Http(backend, options),
deps: [MockBackend, BaseRequestOptions]
}
]);
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
Phone,
MockBackend,
BaseRequestOptions,
{ provide: Http,
useFactory: (backend: MockBackend, options: BaseRequestOptions) => new Http(backend, options),
deps: [MockBackend, BaseRequestOptions]
}
]
});
});
beforeEach(inject([MockBackend, Phone], (_mockBackend_: MockBackend, _phone_: Phone) => {
mockBackend = _mockBackend_;

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,27 +1,10 @@
// #docregion
// #docregion imports
import {
LocationStrategy,
HashLocationStrategy,
APP_BASE_HREF
} from '@angular/common';
import { bootstrap } from '@angular/platform-browser-dynamic';
import { FormsModule } from '@angular/forms';
import { HTTP_PROVIDERS } from '@angular/http';
import { ROUTER_PROVIDERS } from '@angular/router-deprecated';
import { Phone } from './core/phone/phone.service';
import { AppComponent } from './app.component';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
// #enddocregion imports
// #docregion bootstrap
bootstrap(AppComponent, {
imports: [FormsModule],
providers: [
HTTP_PROVIDERS,
ROUTER_PROVIDERS,
{ provide: APP_BASE_HREF, useValue: '!' },
{ provide: LocationStrategy, useClass: HashLocationStrategy },
Phone
]
});
platformBrowserDynamic().bootstrapModule(AppModule);
// #enddocregion bootstrap

View File

@ -1,25 +1,15 @@
// #docregion
import { HTTP_PROVIDERS } from '@angular/http';
// #docregion routeparams
import { RouteParams } from '@angular/router-deprecated';
// #docregion activatedroute
import { ActivatedRoute } from '@angular/router';
// #enddocregion routeparams
// #enddocregion activatedroute
import { Observable } from 'rxjs/Rx';
import {
describe,
beforeEachProviders,
inject,
it,
expect
} from '@angular/core/testing';
import {
TestComponentBuilder,
ComponentFixture
} from '@angular/compiler/testing';
import { async, TestBed } from '@angular/core/testing';
import { PhoneDetailComponent } from './phone-detail.component';
import { Phone, PhoneData } from '../core/phone/phone.service';
import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe';
function xyzPhoneData(): PhoneData {
return {
@ -29,31 +19,41 @@ function xyzPhoneData(): PhoneData {
};
}
class MockPhone extends Phone {
class MockPhone {
get(id: string): Observable<PhoneData> {
return Observable.of(xyzPhoneData());
}
}
// #docregion activatedroute
class ActivatedRouteMock {
constructor(public snapshot: any) {}
}
// #enddocregion activatedroute
describe('PhoneDetailComponent', () => {
// #docregion routeparams
// #docregion activatedroute
beforeEachProviders(() => [
{ provide: Phone, useClass: MockPhone },
{ provide: RouteParams, useValue: new RouteParams({phoneId: 'xyz'})},
HTTP_PROVIDERS
]);
// #enddocregion routeparams
it('should fetch phone detail',
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(PhoneDetailComponent)
.then((fixture: ComponentFixture<PhoneDetailComponent>) => {
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1')).toHaveText(xyzPhoneData().name);
});
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CheckmarkPipe, PhoneDetailComponent ],
providers: [
{ provide: Phone, useClass: MockPhone },
{ provide: ActivatedRoute, useValue: new ActivatedRouteMock({ params: { 'phoneId': 1 } }) }
]
})
.compileComponents();
}));
// #enddocregion activatedroute
it('should fetch phone detail', () => {
const fixture = TestBed.createComponent(PhoneDetailComponent);
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain(xyzPhoneData().name);
});
});

View File

@ -1,25 +1,25 @@
// #docplaster
// #docregion
import { Component } from '@angular/core';
import { RouteParams } from '@angular/router-deprecated';
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Phone, PhoneData } from '../core/phone/phone.service';
import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe';
@Component({
moduleId: module.id,
selector: 'phone-detail',
templateUrl: 'phone-detail.template.html',
pipes: [ CheckmarkPipe ]
templateUrl: 'phone-detail.template.html'
})
export class PhoneDetailComponent {
phone: PhoneData;
mainImageUrl: string;
constructor(routeParams: RouteParams, phone: Phone) {
phone.get(routeParams.get('phoneId')).subscribe(phone => {
this.phone = phone;
this.setImage(phone.images[0]);
});
constructor(activatedRoute: ActivatedRoute, phone: Phone) {
phone.get(activatedRoute.snapshot.params['phoneId'])
.subscribe((p: PhoneData) => {
this.phone = p;
this.setImage(p.images[0]);
});
}
setImage(imageUrl: string) {

View File

@ -1,42 +1,21 @@
/* tslint:disable */
// #docregion routestuff
import { Directive } from '@angular/core';
import { HTTP_PROVIDERS } from '@angular/http';
import {
Router,
RouterLink,
RootRouter,
RouteRegistry,
ROUTER_PRIMARY_COMPONENT
} from '@angular/router-deprecated';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs/Rx';
import {
describe,
addProviders,
inject,
it,
expect,
// MockApplicationRef
} from '@angular/core/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SpyLocation } from '@angular/common/testing';
import {
TestComponentBuilder,
ComponentFixture
} from '@angular/core/testing';
import { AppComponent } from '../app.component';
import { PhoneListComponent } from './phone-list.component';
import { Phone, PhoneData } from '../core/phone/phone.service';
// #enddocregion routestuff
@Directive({
selector: '[routerLink]',
inputs: ['routeParams: routerLink', 'target: target']
})
class RouterLinkMock {}
class ActivatedRouteMock {
constructor(public snapshot: any) {}
}
class MockPhone extends Phone {
class MockPhone {
query(): Observable<PhoneData[]> {
return Observable.of([
{name: 'Nexus S', snippet: '', images: []},
@ -45,52 +24,48 @@ class MockPhone extends Phone {
}
}
let fixture: ComponentFixture<PhoneListComponent>;
describe('PhoneList', () => {
// #docregion routestuff
addProviders([
RouteRegistry,
{ provide: Router, useClass: RootRouter },
{ provide: ROUTER_PRIMARY_COMPONENT, useValue: AppComponent },
{ provide: Location, useClass: SpyLocation},
{ provide: Phone, useClass: MockPhone},
HTTP_PROVIDERS
]);
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PhoneListComponent ],
providers: [
{ provide: ActivatedRoute, useValue: new ActivatedRouteMock({ params: { 'phoneId': 1 } }) },
{ provide: Location, useClass: SpyLocation },
{ provide: Phone, useClass: MockPhone },
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PhoneListComponent);
});
// #enddocregion routestuff
it('should create "phones" model with 2 phones fetched from xhr',
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb
.overrideDirective(AppComponent, RouterLink, RouterLinkMock)
.overrideDirective(PhoneListComponent, RouterLink, RouterLinkMock)
.createAsync(PhoneListComponent)
.then((fixture: ComponentFixture<PhoneListComponent>) => {
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelectorAll('.phone-list-item').length).toBe(2);
expect(
compiled.querySelector('.phone-list-item:nth-child(1)').textContent
).toContain('Motorola DROID');
expect(
compiled.querySelector('.phone-list-item:nth-child(2)').textContent
).toContain('Nexus S');
});
}));
it('should create "phones" model with 2 phones fetched from xhr', () => {
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelectorAll('.phone-list-item').length).toBe(2);
expect(
compiled.querySelector('.phone-list-item:nth-child(1)').textContent
).toContain('Motorola DROID');
expect(
compiled.querySelector('.phone-list-item:nth-child(2)').textContent
).toContain('Nexus S');
});
it('should set the default value of orderProp model',
inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb
.overrideDirective(AppComponent, RouterLink, RouterLinkMock)
.overrideDirective(PhoneListComponent, RouterLink, RouterLinkMock)
.createAsync(PhoneListComponent)
.then((fixture: ComponentFixture<PhoneListComponent>) => {
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(
compiled.querySelector('select option:last-child').selected
).toBe(true);
});
}));
xit('should set the default value of orderProp model', () => {
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(
compiled.querySelector('select option:last-child').selected
).toBe(true);
});
});

View File

@ -1,14 +1,13 @@
// #docplaster
// #docregion top
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router-deprecated';
import { Phone, PhoneData } from '../core/phone/phone.service';
@Component({
moduleId: module.id,
selector: 'phone-list',
templateUrl: 'phone-list.template.html',
directives: [ RouterLink ]
})
// #enddocregion top
export class PhoneListComponent {
@ -41,20 +40,20 @@ export class PhoneListComponent {
}
private sortPhones(phones: PhoneData[]) {
if (phones && this.orderProp) {
return phones
.slice(0) // Make a copy
.sort((a, b) => {
if (a[this.orderProp] < b[this.orderProp]) {
return -1;
} else if ([b[this.orderProp] < a[this.orderProp]]) {
return 1;
} else {
return 0;
}
});
}
return phones;
if (phones && this.orderProp) {
return phones
.slice(0) // Make a copy
.sort((a, b) => {
if (a[this.orderProp] < b[this.orderProp]) {
return -1;
} else if ([b[this.orderProp] < a[this.orderProp]]) {
return 1;
} else {
return 0;
}
});
}
return phones;
}
// #enddocregion getphones
// #docregion initialclass

View File

@ -26,10 +26,10 @@
<ul class="phones">
<li *ngFor="let phone of getPhones()"
class="thumbnail phone-list-item">
<a [routerLink]="['/Phone', {phoneId: phone.id}]" class="thumb">
<a [routerLink]="['/phones', phone.id]" class="thumb">
<img [src]="phone.imageUrl" [alt]="phone.name" />
</a>
<a [routerLink]="['/Phone', {phoneId: phone.id}]" class="name">{{phone.name}}</a>
<a [routerLink]="['/phones', phone.id]" class="name">{{phone.name}}</a>
<p>{{phone.snippet}}</p>
</li>
</ul>

View File

@ -63,11 +63,10 @@ module.exports = function(config) {
frameworks: ['jasmine'],
browsers: ['Chrome', 'Firefox'],
browsers: ['Chrome'],
plugins: [
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-jasmine'
]

View File

@ -1,59 +1,47 @@
/**
* System configuration for Angular 2 samples
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*/
(function(global) {
// map tells the System loader where to look for things
(function (global) {
// #docregion paths
var map = {
'app': '/app', // 'dist',
'@angular': '/node_modules/@angular',
'angular-in-memory-web-api': '/node_modules/angular-in-memory-web-api',
'rxjs': '/node_modules/rxjs'
};
var packages = {
'/app': { main: 'main.js', defaultExtension: 'js' },
'rxjs': { defaultExtension: 'js' },
'angular-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
};
// #enddocregion paths
var ngPackageNames = [
'common',
'compiler',
'core',
'forms',
'http',
'platform-browser',
'platform-browser-dynamic',
'router',
'router-deprecated',
'upgrade',
];
// Individual files (~300 requests):
function packIndex(pkgName) {
packages['@angular/'+pkgName] = { main: 'index.js', defaultExtension: 'js' };
}
// Bundled (~40 requests):
function packUmd(pkgName) {
packages['@angular/'+pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };
}
var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;
// Add package entries for angular packages
ngPackageNames.forEach(setPackageConfig);
var config = {
map: map,
packages: packages
}
System.config(config);
System.config({
paths: {
// paths serve as alias
'npm:': '/node_modules/'
},
map: {
app: '/app',
// #enddocregion paths
// angular bundles
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
// other libraries
'rxjs': 'npm:rxjs',
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',
// #docregion paths
},
// #enddocregion paths
// packages tells the System loader how to load when no filename and/or no extension
packages: {
'app': {
main: './main.js',
defaultExtension: 'js'
},
rxjs: {
defaultExtension: 'js'
},
'angular-in-memory-web-api': {
main: './index.js',
defaultExtension: 'js'
}
}
});
})(this);

View File

@ -1,10 +1,5 @@
include ../_util-fns
.alert.is-important
:marked
This guide is still in the process of being updated to RC5 and it's samples
may not work correctly.
:marked
Having an existing Angular 1 application doesn't mean that we can't
begin enjoying everything Angular 2 has to offer. That's because Angular 2
@ -28,13 +23,13 @@ include ../_util-fns
make incremental upgrading seamless.
1. [Preparation](#preparation)
1. [Following The Angular Style Guide](#following-the-angular-style-guide)
1. [Follow the Angular Style Guide](#follow-the-angular-style-guide)
2. [Using a Module Loader](#using-a-module-loader)
3. [Migrating to TypeScript](#migrating-to-typescript)
4. [Using Component Directives](#using-component-directives)
2. [Upgrading with The Upgrade Adapter](#upgrading-with-the-upgrade-adapter)
1. [How The Upgrade Adapter Works](#how-the-upgrade-adapter-works)
2. [Bootstrapping Hybrid Angular 1+2 Applications](#bootstrapping-hybrid-angular-1-2-applications)
2. [Bootstrapping hybrid Angular 1+2 Applications](#bootstrapping-hybrid-angular-1-2-applications)
3. [Using Angular 2 Components from Angular 1 Code](#using-angular-2-components-from-angular-1-code)
4. [Using Angular 1 Component Directives from Angular 2 Code](#using-angular-1-component-directives-from-angular-2-code)
5. [Projecting Angular 1 Content into Angular 2 Components](#projecting-angular-1-content-into-angular-2-components)
@ -44,7 +39,7 @@ include ../_util-fns
3. [PhoneCat Upgrade Tutorial](#phonecat-upgrade-tutorial)
1. [Switching to TypeScript](#switching-to-typescript)
2. [Installing Angular 2](#installing-angular-2)
3. [Bootstrapping A Hybrid 1+2 PhoneCat](#bootstrapping-a-hybrid-1-2-phonecat)
3. [Bootstrapping a hybrid 1+2 PhoneCat](#bootstrapping-a-hybrid-1-2-phonecat)
4. [Upgrading the Phone service](#upgrading-the-phone-service)
5. [Upgrading Components](#upgrading-components)
6. [Switching To The Angular 2 Router And Bootstrap](#switching-to-the-angular-2-router-and-bootstrap)
@ -61,7 +56,7 @@ include ../_util-fns
and patterns that we can apply to future proof our apps even before we
begin the migration.
## Following The Angular Style Guide
## Follow the Angular Style Guide
The [Angular 1 Style Guide](https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#single-responsibility)
collects patterns and practices that have been proven to result in
@ -84,6 +79,7 @@ include ../_util-fns
components easy to navigate and find, but will also allow us to migrate
them between languages and frameworks one at a time. In this example application,
each controller, component, service, and filter is in its own source file.
* The [Folders-by-Feature Structure](https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#folders-by-feature-structure)
and [Modularity](https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#modularity)
rules define similar principles on a higher level of abstraction: Different parts of the
@ -390,6 +386,26 @@ figure.image-display
as regular Angular 2 inputs and set onto the scope (or controller) when
they change.
## Using the Upgrade Adapter with Angular 2 _NgModules_
Both Angular 1 and Angular 2 have their own concept of modules
to help organize an application into cohesive blocks of funcionality.
Their details are quite different in architecture and implementation.
In Angular 1, you add Angular assets to the `angular.module` property.
In Angular 2, you create one or more classes adorned with an `NgModule` decorator
that describes Angular assets in metadata. The differences blossom from there.
In a hybrid application we run both versions of Angular at the same time.
That means that we need at least one module each from both Angular 1 and Angular 2.
We will give the Angular 2 module to the `UpgradeAdapter` while we use the
Angular 1 module for bootstrapping. Let's see how.
.l-sub-section
:marked
Learn more about Angular 2 modules at the [NgModule guide](ngmodule.html).
:marked
## Bootstrapping Hybrid Angular 1+2 Applications
The first step to upgrading an application using the `UpgradeAdapter` is
@ -416,28 +432,30 @@ figure.image-display
+makeExample('upgrade-adapter/ts/app/1-bootstrap/app.module.ts', 'bootstrap')
:marked
To then switch the application into hybrid mode, we must first
install Angular 2 to the project. Follow the instructions in
[the QuickStart](../quickstart.html) for some pointers on this.
When we have Angular 2 installed, we can import and instantiate
the `UpgradeAdapter`, and then call its `bootstrap` method. It
is designed to take the exact same arguments as
[angular.bootstrap](https://docs.angularjs.org/api/ng/function/angular.bootstrap)
so that it is easy to make the switch:
Now introduce Angular 2 to the project. Inspired by instructions in
[the QuickStart](../quickstart.html), you can selectively copy in material from the
<a href="https://github.com/angular/quickstart" target="_blank">QuickStart github repository</a>.
Next, create an `app.module.ts` file and add the following `NgModule` class:
+makeExample('upgrade-adapter/ts/app/1-2-hybrid-bootstrap/app.module.ts', 'ngmodule')
:marked
This bare minimum `NgModule` imports `BrowserModule`, the module every Angular browser-based app must have.
Import and instantiate the `UpgradeAdapter` with the new `AppModule` and call its `bootstrap` method.
That method takes the exact same arguments as [angular.bootstrap](https://docs.angularjs.org/api/ng/function/angular.bootstrap):
+makeExample('upgrade-adapter/ts/app/1-2-hybrid-bootstrap/app.module.ts', 'bootstrap')
:marked
At this point we'll be running a hybrid Angular 1+2 application! All the
existing Angular 1 code will work as it always did, but we are now ready
to run Angular 2 code as well.
Congratulations! You're running a hybrid Angular 1+2 application! The
existing Angular 1 code works as before _and_ you're ready to run Angular 2 code.
.alert.is-helpful
:marked
One notable difference between `angular.bootstrap` and
`upgradeAdapter.bootstrap` is that the latter works *asynchronously*.
This means that we cannot assume that the application has been instantiated
immediately after the bootstrap call returns.
Note that, unlike `angular.bootstrap`, the `upgradeAdapter.bootstrap` runs *asynchronously*.
The application is not launched immediately. Some time must pass after the bootstrap call returns.
:marked
As we begin to migrate components to Angular 2, we'll be using the
@ -476,16 +494,25 @@ figure
+makeExample('upgrade-adapter/ts/app/downgrade-static/app.module.ts', 'downgradecomponent')
:marked
What we have here is an Angular 1 directive called `heroDetail`, which we can
Because `HeroDetailComponent` is an Angular 2 component, we must also add it to the `declarations` in the `AppModule`.
+makeExample('upgrade-adapter/ts/app/downgrade-static/app.module.ts', 'ngmodule')
.l-sub-section
:marked
All Angular 2 components, directives and pipes must be declared in an NgModule.
:marked
The net resulit is an Angular 1 directive called `heroDetail`, that we can
use like any other directive in our Angular 1 templates.
+makeExample('upgrade-adapter/ts/index-downgrade-static.html', 'usecomponent')
.alert.is-helpful
:marked
Note that since Angular 1 directives are matched based on their name,
*the selector metadata of the Angular 2 component is not used in Angular 1*.
It is matched as an element directive (`restrict: 'E'`) called `heroDetail`.
Note that this Angular 1 is an element directive (`restrict: 'E'`) called `heroDetail`.
An Angular 1 element directive is matched based on its _name_.
*The `selector` metadata of the downgraded Angular 2 component is ignored.*
:marked
Most components are not quite this simple, of course. Many of them
@ -558,11 +585,10 @@ figure
:marked
We can *upgrade* this component to Angular 2 using the `UpgradeAdapter`'s
`upgradeNg1Component` method. It takes the name of an Angular 1 component
directive and returns an Angular 2 **component class**. When we then
want to use it from an Angular 2 component, we list it the in the `directives`
metadata of the component and then just use it in the Angular 2 template:
directive and returns an Angular 2 **component class**.
Declare it in an `NgModule` as with other Angular 2 components:
+makeExample('upgrade-adapter/ts/app/upgrade-static/container.component.ts', null, 'container.component.ts')
+makeExample('upgrade-adapter/ts/app/upgrade-static/upgrade_adapter.ts', 'heroupgrade', 'app.module.ts')
.alert.is-helpful
:marked
@ -731,17 +757,14 @@ figure
+makeExample('upgrade-adapter/ts/app/2-to-1-providers/heroes.ts', null, 'heroes.ts')
:marked
We can again use the `UpgradeAdapter` for this, but first we need to register `Heroes`
to the Angular 2 injector itself. In a pure Angular 2 application we would do this
when we bootstrap the app, as described in the [dependency injection guide](dependency-injection.html#!#providers).
But since hybrid applications are bootstrapped using the `UpgradeAdapter`, we also
need to register our Angular 2 providers using `UpgradeAdapter`. It has a method
called `addProvider` for this purpose.
Again, as with Angular 2 components, register the provider with the `NgModule` by adding it to the module's `providers` list.
Once we've registered the Angular 2 provider, we can turn `Heroes` into an *Angular 1
factory function* using `upgradeAdapter.downgradeNg2Provider()`. We can
then plug the factory into an Angular 1 module, at which point we also choose what the
name of the dependency will be in Angular 1:
+makeExample('upgrade-adapter/ts/app/2-to-1-providers/upgrade_adapter.ts', 'ngmodule', 'app.module.ts')
:marked
Now wrap the Angular 2 `Heroes` in an *Angular 1 factory function* using `upgradeAdapter.downgradeNg2Provider()`.
and plug the factory into an Angular 1 module.
The name of the Angular 1 dependency is up to you:
+makeExample('upgrade-adapter/ts/app/2-to-1-providers/app.module.ts', 'register', 'app.module.ts')
@ -1033,7 +1056,7 @@ code-example(format="").
development server root path in `package.json` to also point to the project root
instead of `app`:
+makeJson('upgrade-phonecat-2-hybrid/ts/package.ng1.json', {paths: 'scripts.start'}, 'package.json')
+makeJson('upgrade-phonecat-2-hybrid/ts/package.ng1.json', {paths: 'scripts.start'}, 'package.json (start script)')
:marked
Now we're able to serve everything from the project root to the web browser. But we do *not*
@ -1058,7 +1081,22 @@ code-example(format="").
+makeExample('upgrade-phonecat-2-hybrid/ts/systemjs.config.1.js', 'paths', 'systemjs.config.js')
:marked
## Bootstrapping A Hybrid 1+2 PhoneCat
## Creating the _AppModule_
Now create the root `NgModule` class called `AppModule`.
There is already a file named `app.module.ts` that holds the Angular 1 module.
Rename it to `app.module.ng1.ts` and update the corresponding script name in the `index.html` as well.
The file contents remain:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/app.module.ng1.ts', null, 'app.module.ng1.ts')
:marked
Now create a new `app.module.ts` with the minimum `NgModule` class:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/app.module.ts', 'bare', 'app.module.ts')
:marked
## Bootstrapping a hybrid 1+2 PhoneCat
What we'll do next is bootstrap the application as a *hybrid application*
that supports both Angular 1 and Angular 2 components. Once we've done that
@ -1109,32 +1147,19 @@ code-example(format="").
* For loading the details of a single phone into the phone detail component.
We can replace this implementation with an Angular 2 service class, while
keeping our controllers in Angular 1 land. In the new version we'll just use
the `Http` service from Angular 2 instead of ngResource.
keeping our controllers in Angular 1 land.
Before the `Http` service is available for injection, we need to register
it into our application's dependency injector. We should import the `HTTP_PROVIDERS`
constant in `main.ts`:
In the new version, we import the Angular 2 HTTP module and call its `Http` service instead of `ngResource`.
+makeExample('upgrade-phonecat-2-hybrid/ts/app/main.ts', 'import-http')
Re-open the `app.module.ts` file, import and add `HttpModule` to the `imports` array of the `AppModule`:
:marked
In a regular Angular 2 application we would now pass `HTTP_PROVIDERS` into
the application bootstrap function. But we can't do that in a hybrid
application such as the one we're working on. That's because the `bootstrap`
method of `UpgradeAdapter` expects Angular 1 modules as dependencies,
not Angular 2 providers.
What we must do instead is register `HTTP_PROVIDERS` into the `UpgradeAdapter`
separately. It has a method called `addProvider` for that purpose:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/main.ts', 'add-http')
+makeExample('upgrade-phonecat-2-hybrid/ts/app/app.module.ts', 'httpmodule', 'app.module.ts')
:marked
Now we're ready to upgrade the Phone service itself. We replace the ngResource-based
service in `phone.service.ts` with a TypeScript class decorated as `@Injectable`:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.service.ts', 'classdef', 'app/core/phone/phone.service.ts')
+makeExample('upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.service.ts', 'classdef', 'app/core/phone/phone.service.ts (skeleton)')(format='.')
:marked
The `@Injectable` decorator will attach some dependency injection metadata
@ -1154,7 +1179,7 @@ code-example(format="").
The methods now return Observables of type `PhoneData` and `PhoneData[]`. This is
a type we don't have yet, so let's add a simple interface for it:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.service.ts', 'phonedata-interface', 'app/core/phone/phone.service.ts')
+makeExample('upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.service.ts', 'phonedata-interface', 'app/core/phone/phone.service.ts (interface)')(format='.')
:marked
Here's the full, final code for the service:
@ -1166,24 +1191,17 @@ code-example(format="").
We need to do this for all RxJS operators that we want to use, since Angular 2
does not load all of them by default.
The new `Phone` service now has the same features that the original, ngResource based
service did. Now we just need to register the new service into the application, so that
our Angular 1 components will be able to use it.
The new `Phone` service has the same features as the original, `ngResource`-based service.
Because it's an Angular 2 service, we register it with the `NgModule` providers:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/app.module.ts', 'phone', 'app.module.ts')
:marked
`UpgradeAdapter` has a `downgradeNg2Provider` method for the purpose of making
Angular 2 services available to Angular 1 code. We can use it to plug in our
`Phone` service:
Angular 2 services available to Angular 1 code. Use it to plug in the `Phone` service:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/main.ts', 'phone-service', 'app/main.ts')
+makeExample('upgrade-phonecat-2-hybrid/ts/app/main.ts', 'phone-service', 'app/main.ts (excerpt)')(format='.')
:marked
Note that we actually needed to do two registrations here:
1. Register `Phone` as an **Angular 2 provider** with the `addProvider`
method. That's the same method that we used earlier for `HTTP_PROVIDERS`.
2. Register an **Angular 1 factory** called `phone`, which will be a *downgraded*
version of the `Phone` Angular 2 service.
Now that we are loading `phone.service.ts` through an import that is resolved
by SystemJS, we should **remove the &lt;script&gt; tag** for the service from `index.html`.
This is something we'll do to all our components as we upgrade them. Simultaneously
@ -1234,69 +1252,72 @@ code-example(format="").
just like the Angular 1 version did.
We now also need to convert the template of this component into Angular 2 syntax.
In the search controls we need to use Angular 2 syntax for the two `ngModel`s.
We should also no longer use the `$ctrl` prefix in expressions:
The search controls replace the Angular 1 `$ctrl` expressions
with Angular 2's two-way `[(ngModel)]` binding syntax:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.template.html', 'controls', 'app/phone-list/phone-list.template.html')
+makeExample('upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.template.html', 'controls', 'app/phone-list/phone-list.template.html (search controls)')(format='.')
:marked
In the list we need to replace the `ng-repeat` with an `*ngFor` and the
`let var of iterable` syntax, which is [described in our
Template Syntax guide](../guide/template-syntax.html#directives).
For the images, we can replace `ng-src` with a binding to the standard `src` property.
Replace the list's `ng-repeat` with an `*ngFor` as
[described in the Template Syntax page](../guide/template-syntax.html#directives).
Replace the image tag's `ng-src` with a binding to the native `src` property.
+makeExample('upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.template.html', 'list', 'app/phone-list/phone-list.template.html')
+makeExample('upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.template.html', 'list', 'app/phone-list/phone-list.template.html (phones)')(format='.')
:marked
Another thing that we've done here is that we've removed the use of `filter` and `orderBy` filters,
and replaced them with a call to the `getPhones()` controller method.
The built-in Angular filters `filter` and `orderBy` do not exist in Angular 2,
so we need to do the filtering and sorting ourselves. We could define our own Angular 2
pipes for this purpose, but in this case it is more convenient to just implement the filtering
and ordering logic in the component itself. We expect the `getPhones()` method to return a collection
where the current filtering and ordering has been applied.
### No Angular 2 _filter_ or _orderBy_ filters
The built-in Angular 1 `filter` and `orderBy` filters do not exist in Angular 2,
so we need to do the filtering and sorting ourselves.
We replaced the `filter` and `orderBy` filters with bindings to the `getPhones()` controller method,
which implements the filtering and ordering logic inside the component itself.
+makeExample('upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.ts', 'getphones', 'app/phone-list/phone-list.component.ts')
:marked
In the entrypoint file `main.ts` we're going to plug this component into our application. Instead
of registering a component, we register a `phoneList` *directive*.
The directive is a downgraded version of our Angular 2 component, and the `UpgradeAdapter`
handles the bridging between the two:
The new `PhoneListComponent` uses the Angular 2 `ngModel` directive, located in the `FormsModule`.
Add the `FormsModule` to `NgModule` imports and declare the new `PhoneListComponent` :
+makeExample('upgrade-phonecat-2-hybrid/ts/app/main.ts', 'phone-list', 'app/main.ts')
+makeExample('upgrade-phonecat-2-hybrid/ts/app/app.module.ts', 'phonelist', 'app.module.ts')
:marked
The `<angular.IDirectiveFactory>` type annotation here is to let the TypeScript compiler
know that the return value of the downgrade method call will be something that can be
used as a directive factory.
In the entrypoint file `main.ts` we'll plug this component into the Angular 1 module.
At this point, also remove the &lt;script&gt; tag for the phone list component from `index.html`.
Instead of registering a component, we register a `phoneList` *directive*, a downgraded version of the Angular 2 component.
The `UpgradeAdapter` creates the bridge between the two:
Now we can start looking at our other component, which is the one for
the phone details. Set the contents of `phone-detail.component.ts` as follows:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/main.ts', 'phone-list', 'app/main.ts (excerpt)')(format='.')
:marked
The `as angular.IDirectiveFactory` cast tells the TypeScript compiler
that the return value of the downgrade method is a directive factory.
Remove the &lt;script&gt; tag for the phone list component from `index.html`.
Now set the remaining `phone-detail.component.ts` as follows:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.ts', 'initialclass', 'app/phone-detail/phone-detail.component.ts')
:marked
This is pretty similar to what we did with the phone list. The one new change
here is the use of `@Inject` for the `$routeParams` dependency. It tells the
Angular 2 injector what this dependency should map to. We have a dependency called
`$routeParams` in the Angular 1 injector, where it is provided by the Angular 1 router.
That is what we were already using when `PhoneDetails` was still an Angular 1 controller.
This is similar to the phone list component.
The new wrinkle is the `@Inject` decorator that identifies the `$routeParams` dependency.
The things is though, Angular 1 dependencies are not made automatically available to
Angular 2 components, so if we were to run this now, it would not work. We need to explicitly
tell the `UpgradeAdapter` to upgrade `$routeParams` so that it is available for injection in
Angular 2. We can do it in `main.ts`:
The Angular 1 injector has an Angular 1 router dependency called `$routeParams`.
which was injected into `PhoneDetails` when it was still an Angular 1 controller.
We intend to inject it into the new `PhoneDetailsComponent`.
+makeExample('upgrade-phonecat-2-hybrid/ts/app/main.ts', 'routeparams', 'app/main.ts')
Unfortunately, Angular 1 dependencies are not automatically available to Angular 2 components.
We must use the `UpgradeAdapter` to make the `$routeParams` an Angular 2 provider.
Do that in `main.ts`:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/main.ts', 'routeparams', 'app/main.ts ($routeParms)')(format='.')
.l-sub-section
:marked
Do not register an upgraded Angular 1 provider in the `NgModule`.
:marked
We now also need to convert the template of this component into Angular 2 syntax.
Here is the new template in its entirety:
Convert the phone detail component template into Angular 2 syntax as follows:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.template.html', null, 'app/phone-detail/phone-detail.template.html')
@ -1320,118 +1341,135 @@ code-example(format="").
when we try to refer to properties on undefined objects. We need to be explicit
about cases where this is expected.
Add this component to the `NgModule` _declarations_:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/app.module.ts', 'phonedetail', 'app.module.ts')
:marked
In `main.ts` we'll now register a `phoneDetail` directive instead of a
component. The directive is a downgraded version of the `PhoneDetail` Angular 2
component.
+makeExample('upgrade-phonecat-2-hybrid/ts/app/main.ts', 'phone-detail', 'app/main.ts')
+makeExample('upgrade-phonecat-2-hybrid/ts/app/main.ts', 'phone-detail', 'app/main.ts (excerpt)')(format='.')
:marked
We should now also remove the phone detail component &lt;script&gt; tag from `index.html`.
There's one additional step we need to take, which is to upgrade the
`checkmark` filter that the template is using. We need an Angular 2
pipe instead of an Angular 1 filter.
### Add the _CheckmarkPipe_
While there is no upgrade method in the upgrade adapter for filters, we
can just turn the filter function into a class that fulfills
the contract for Angular 2 Pipes. The implementation is the same as before.
It just comes in a different kind of packaging. While changing it, also
rename the file to `checkmark.pipe.ts`:
The Angular 1 directive had a `checkmark` _filter_.
Turn that into an Angular 2 **pipe**.
+makeExample('upgrade-phonecat-2-hybrid/ts/app/core/checkmark/checkmark.pipe.ts', null, 'app/core/checkmark/checkmark.pipe.ts')
There is no upgrade adapter method to convert filters into pipes.
You won't miss it.
It's easy to turn the filter function into an equivalent Pipe class.
The implementation is the same as before, repackaged in the `transform` method.
Rename the file to `checkmark.pipe.ts` to conform with Angular 2 conventions:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/core/checkmark/checkmark.pipe.ts', null, 'app/core/checkmark/checkmark.pipe.ts')(format='.')
:marked
In the component we should now import and declare our newly created pipe (as well as
remove the filter &lt;script&gt; tag from `index.html`):
Now import and declare the newly created pipe and
remove the filter &lt;script&gt; tag from `index.html`:
+makeExample('upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.ts', 'checkmark-pipe', 'app/phone-detail/phone-detail.component.ts')
+makeExample('upgrade-phonecat-2-hybrid/ts/app/app.module.ts', 'checkmarkpipe', 'app.module.ts')
:marked
## Switching To The Angular 2 Router And Bootstrap
At this point we've replaced all our Angular 1 application components with
their Angular 2 counterparts. The application is still bootstrapped as a hybrid,
but there isn't really any need for that anymore, and we can begin to
pull out the last remnants of Angular 1.
At this point we've replaced all Angular 1 application components with
their Angular 2 counterparts.
There are just two more things to do: We need to switch the router to
the Angular 2 one, and then bootstrap the app as a pure Angular 2 app.
The application is still bootstrapped as a hybrid app.
There's no need for that anymore.
It's time to remove the last remnants of Angular 1 in two final steps:
1. Switch to the Angular 2 router.
1. Bootstrap as a pure Angular 2 app.
Let's do the routing part first. Angular 2 comes with an [all-new router](router.html)
that we can use for this.
### Switch to the Angular 2 router
Angular 2 has an [all-new router](router.html).
Angular 2 applications all have a *root component*, which, among other
things, is where we should plug in the router. We don't yet have such a root
component, because our app is still managed as an Angular 1 app.
Let's change this now and add an `AppComponent` class into a new file
`app.component.ts`:
Like all routers, it needs a place in the UI to display routed views.
The Angular 2 that's the `<router-outlet>` and it belongs in a *root component*
at the top of the applications component tree.
+makeExample('upgrade-phonecat-3-final/ts/app/app.component.ts', null, 'app/app.component.ts')
We don't yet have such a root component, because the app is still managed as an Angular 1 app.
Create a new `app.component.ts` file with the following `AppComponent` class:
+makeExample('upgrade-phonecat-3-final/ts/app/app.component.ts', null, 'app/app.component.ts')(format='.')
:marked
This is a component that plugs in to an `<phonecat-app>` element on the page,
and has a simple template that only includes the router outlet component
of the Angular router. This means that the component just renders the contents
of the current route and nothing else. The `@RouteConfig` decorator defines
the Angular 2 counterparts of our two routes. They refer directly to the
two components.
It has a simple template that only includes the `<router-outlet>`.
This component just renders the contents of the active route and nothing else.
We should put this `<phonecat-app>` element in the HTML so that the root component
has something to attach to. It replaces the old Angular 1 `ng-view` directive:
The selector tells Angular 2 to plug this root component into the `<phonecat-app>`
element on the host web page when the application launches.
+makeExample('upgrade-phonecat-3-final/ts/index.html', 'appcomponent', 'index.html')
Add this `<phonecat-app>` element to the `index.html`.
It replaces the old Angular 1 `ng-view` directive:
+makeExample('upgrade-phonecat-3-final/ts/index.html', 'appcomponent', 'index.html (body)')(format='.')
:marked
In the `PhoneDetail` component we now need to change how the phone id parameter
is received. There will be no more `$routeParams` injection available, because
that comes from the Angular 1 router. Instead, what we have is a `RouteParams`
object provided by the Angular 2 router. We use it to obtain the `phoneId` from
the params:
### Create the _Routing Module_
A router needs configuration whether it's the Angular 1 or Angular 2 or any other router.
The details of Angular 2 router configuration are best left to the [Routing](../router.html) documentation
which recommends that you create a `NgModule` dedicated to router configuration
(called a _Routing Module_):
+makeExample('upgrade-phonecat-3-final/ts/app/app-routing.module.ts', null, 'app/app-routing.module.ts')
:marked
This module defines a `routes` object with two routes to the two phone components
and a default route for the empty path.
It passes the `routes` to the `RouterModule.forRoot` method which does the rest.
A couple of extra providers enable routing with "hash" URLs such as `#!/phones` instead of the default "push state" strategy.
Now update the `AppModule` to import this `AppRoutingModule` and also the
declare the root `AppComponent`:
+makeExample('upgrade-phonecat-3-final/ts/app/app.module.ts', null, 'app/app.module.ts')
:marked
The Angular 2 router passes route parameters differently.
Correct the `PhoneDetail` component constructor to expect an injected `ActivatedRoute` object.
Extract the `phoneId` from the `ActivatedRoute.snapshot.params` and fetch the phone data as before:
+makeExample('upgrade-phonecat-3-final/ts/app/phone-detail/phone-detail.component.ts', null, 'app/phone-detail/phone-detail.component.ts')
:marked
### Generate links for each phone
We no longer have to hardcode the links to phone details in the phone list.
We can generate them data binding each phone's `id` to the `routerLink` directive
and let that directive construct the appropriate URL to the `PhoneDetailComponent`:
+makeExample('upgrade-phonecat-3-final/ts/app/phone-list/phone-list.template.html', 'list', 'app/phone-list/phone-list.template.html (list with links)')(format='.')
.l-sub-section
:marked
See the [Routing](../router.html) page for details.
:marked
With that, we're ready to switch the bootstrap method of the application from that
of the `UpgradeAdapter` to the main Angular 2 `bootstrap`. Let's import it together
with the router, the new app component, and everything else in `main.ts`
### Bootstrap as an Angular 2 app
+makeExample('upgrade-phonecat-3-final/ts/app/main.ts', 'imports')
You may have noticed one extra `bootstrap` metadata property added to the `AppModule`
+makeExample('upgrade-phonecat-3-final/ts/app/app.module.ts', 'bootstrap', 'app/app.module.ts (bootstrap)')(format='.')
:marked
That tells Angular 2 that it should bootstrap the app with the _root_ `AppComponent` and
insert it's view into the host web page.
Now switch the bootstrap method of the application from the `UpgradeAdapter`
to the Angular 2 way.
Because this is a browser application, compiled with the Just-in-Time (JiT) compiler,
use the `platformBrowserDynamic` function to bootstrap the `AppModule`:
+makeExample('upgrade-phonecat-3-final/ts/app/main.ts', null, 'main.ts')
:marked
We'll now use the regular Angular 2 `bootstrap` function to bootstrap the app
instead of using `UpgradeAdapter`. The first argument to `bootstrap` is the
application's root component `AppComponent`, and the second
is an array of the Angular 2 providers that we want to make available for
injection. In that array we include all the things we have been registering
with `upgradeAdapter.addProvider` until now, as well as the providers and
directives of the router:
You are now running a pure Angular 2 application!
+makeExample('upgrade-phonecat-3-final/ts/app/main.ts', 'bootstrap')
:marked
We also configure a couple of things for the router here so that the application
URL paths match exactly those we had in the Angular 1 app: We want the
hash location strategy with the `!` prefix: `#!/phones`.
At this point we are running a pure Angular 2 application!
But there's actually one more cool thing we can do with the new router.
We no longer have to hardcode the links to phone details from the phone
list, because the Angular 2 router is able to generate them for us with
its `routerLink` directive. We just need to refer to the route names we
used in the `@RouteConfig`:
+makeExample('upgrade-phonecat-3-final/ts/app/phone-list/phone-list.template.html', 'list', 'app/phone-list/phone-list.template.html')
:marked
For this to work the directive just needs to be declared in the component:
+makeExample('upgrade-phonecat-3-final/ts/app/phone-list/phone-list.component.ts', 'top')
:marked
## Saying Goodbye to Angular 1
## Say Goodbye to Angular 1
It is time to take off the training wheels and let our application begin
its new life as a pure, shiny Angular 2 app. The remaining tasks all have to
@ -1578,24 +1616,12 @@ code-example(format="").
that use WebDriver's generic URL APIs instead. The first of these is
the redirection spec:
.alert.is-important
:marked
This guide is still in the process of being updated to RC5 and it's samples
may not work correctly.
//- +makeExample('upgrade-phonecat-3-final/e2e-spec.ts', 'redirect', 'e2e-tests/scenarios.ts')
+makeExample('upgrade-phonecat-3-final/e2e-spec.ts', 'redirect', 'e2e-tests/scenarios.ts')
:marked
And the second is the phone links spec:
.alert.is-important
:marked
This guide is still in the process of being updated to RC5 and it's samples
may not work correctly.
//- +makeExample('upgrade-phonecat-3-final/e2e-spec.ts', 'links', 'e2e-tests/scenarios.ts')
+makeExample('upgrade-phonecat-3-final/e2e-spec.ts', 'links', 'e2e-tests/scenarios.ts')
:marked
## Unit Tests
@ -1666,10 +1692,10 @@ code-example(format="").
:marked
Finally, we need to revisit both of the component tests when we switch to the Angular 2
router. For the details component we need to provide an Angular 2 `RouteParams` object
router. For the details component we need to provide a mock of Angular 2 `ActivatedRoute` object
instead of using the Angular 1 `$routeParams`.
+makeExample('upgrade-phonecat-3-final/ts/app/phone-detail/phone-detail.component.spec.ts', 'routeparams', 'app/phone-detail/phone-detail.component.spec.ts')
+makeExample('upgrade-phonecat-3-final/ts/app/phone-detail/phone-detail.component.spec.ts', 'activatedroute', 'app/phone-detail/phone-detail.component.spec.ts')
:marked
And for the phone list component we need to set up a few things for the router itself so that