parent
009acd2a6c
commit
e8921365b7
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"description": "Contact NgModule v.1",
|
||||
"files": [
|
||||
"src/app/app.component.1b.ts",
|
||||
"src/app/app.module.1b.ts",
|
||||
"src/app/highlight.directive.ts",
|
||||
"src/app/title.component.html",
|
||||
"src/app/title.component.ts",
|
||||
"src/app/user.service.ts",
|
||||
|
||||
"src/app/contact/awesome.pipe.ts",
|
||||
"src/app/contact/contact.component.css",
|
||||
"src/app/contact/contact.component.html",
|
||||
"src/app/contact/contact.component.3.ts",
|
||||
"src/app/contact/contact.service.ts",
|
||||
"src/app/contact/contact-highlight.directive.ts",
|
||||
|
||||
"src/main.1b.ts",
|
||||
"src/styles.css",
|
||||
"src/index.1b.html"
|
||||
],
|
||||
"main": "src/index.1b.html",
|
||||
"tags": ["NgModule"]
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"description": "Contact NgModule v.2",
|
||||
"files": [
|
||||
"src/app/app.component.2.ts",
|
||||
"src/app/app.module.2.ts",
|
||||
"src/app/highlight.directive.ts",
|
||||
"src/app/title.component.html",
|
||||
"src/app/title.component.ts",
|
||||
"src/app/user.service.ts",
|
||||
|
||||
"src/app/contact/contact.component.css",
|
||||
"src/app/contact/contact.component.html",
|
||||
"src/app/contact/contact.service.ts",
|
||||
|
||||
"src/app/contact/awesome.pipe.ts",
|
||||
"src/app/contact/contact.component.3.ts",
|
||||
"src/app/contact/contact.module.2.ts",
|
||||
"src/app/contact/contact-highlight.directive.ts",
|
||||
|
||||
"src/main.2.ts",
|
||||
"src/styles.css",
|
||||
"src/index.2.html"
|
||||
],
|
||||
"main": "src/index.2.html",
|
||||
"tags": ["NgModule"]
|
||||
}
|
|
@ -1,223 +0,0 @@
|
|||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('NgModule', function () {
|
||||
|
||||
// helpers
|
||||
const gold = 'rgba(255, 215, 0, 1)';
|
||||
const powderblue = 'rgba(176, 224, 230, 1)';
|
||||
const lightgray = 'rgba(211, 211, 211, 1)';
|
||||
const white = 'rgba(0, 0, 0, 0)';
|
||||
|
||||
function getCommonsSectionStruct() {
|
||||
const buttons = element.all(by.css('nav a'));
|
||||
|
||||
return {
|
||||
title: element.all(by.tagName('h1')).get(0),
|
||||
welcome: element.all(by.css('app-title p i')).get(0),
|
||||
contactButton: buttons.get(0),
|
||||
crisisButton: buttons.get(1),
|
||||
heroesButton: buttons.get(2)
|
||||
};
|
||||
}
|
||||
|
||||
function getContactSectionStruct() {
|
||||
const buttons = element.all(by.css('app-contact form button'));
|
||||
|
||||
return {
|
||||
header: element.all(by.css('app-contact h2')).get(0),
|
||||
popupMessage: element.all(by.css('app-contact div')).get(0),
|
||||
contactNameHeader: element.all(by.css('app-contact form h3')).get(0),
|
||||
input: element.all(by.css('app-contact form input')).get(0),
|
||||
validationError: element.all(by.css('app-contact form .alert')).get(0),
|
||||
saveButton: buttons.get(0), // can't be tested
|
||||
nextContactButton: buttons.get(1),
|
||||
newContactButton: buttons.get(2)
|
||||
};
|
||||
}
|
||||
|
||||
function getCrisisSectionStruct() {
|
||||
return {
|
||||
title: element.all(by.css('ng-component h3')).get(0),
|
||||
items: element.all(by.css('ng-component a')),
|
||||
itemId: element.all(by.css('ng-component div')).get(0),
|
||||
listLink: element.all(by.css('ng-component a')).get(0),
|
||||
};
|
||||
}
|
||||
|
||||
function getHeroesSectionStruct() {
|
||||
return {
|
||||
header: element.all(by.css('ng-component h2')).get(0),
|
||||
title: element.all(by.css('ng-component h3')).get(0),
|
||||
items: element.all(by.css('ng-component a')),
|
||||
itemId: element.all(by.css('ng-component ng-component div div')).get(0),
|
||||
itemInput: element.all(by.css('ng-component ng-component input')).get(0),
|
||||
listLink: element.all(by.css('ng-component ng-component a')).get(0),
|
||||
};
|
||||
}
|
||||
|
||||
// tests
|
||||
function appTitleTests(color: string, name?: string) {
|
||||
return function() {
|
||||
it('should have a gray header', function() {
|
||||
const commons = getCommonsSectionStruct();
|
||||
expect(commons.title.getCssValue('backgroundColor')).toBe(color);
|
||||
});
|
||||
|
||||
it('should welcome us', function () {
|
||||
const commons = getCommonsSectionStruct();
|
||||
expect(commons.welcome.getText()).toBe('Welcome, ' + (name || 'Sherlock Holmes'));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function contactTests(color: string, name?: string) {
|
||||
return function() {
|
||||
it('shows the contact\'s owner', function() {
|
||||
const contacts = getContactSectionStruct();
|
||||
expect(contacts.header.getText()).toBe('Contact of ' + (name || 'Sherlock Holmes'));
|
||||
});
|
||||
|
||||
it('can cycle between contacts', function () {
|
||||
const contacts = getContactSectionStruct();
|
||||
const nextButton = contacts.nextContactButton;
|
||||
expect(contacts.contactNameHeader.getText()).toBe('Awesome Sam Spade');
|
||||
expect(contacts.contactNameHeader.getCssValue('backgroundColor')).toBe(color);
|
||||
nextButton.click().then(function () {
|
||||
expect(contacts.contactNameHeader.getText()).toBe('Awesome Nick Danger');
|
||||
return nextButton.click();
|
||||
}).then(function () {
|
||||
expect(contacts.contactNameHeader.getText()).toBe('Awesome Nancy Drew');
|
||||
});
|
||||
});
|
||||
|
||||
it('can change an existing contact', function () {
|
||||
const contacts = getContactSectionStruct();
|
||||
contacts.input.sendKeys('a');
|
||||
expect(contacts.input.getCssValue('backgroundColor')).toBe(color);
|
||||
expect(contacts.contactNameHeader.getText()).toBe('Awesome Sam Spadea');
|
||||
});
|
||||
|
||||
it('can create a new contact', function () {
|
||||
const contacts = getContactSectionStruct();
|
||||
const newContactButton = contacts.newContactButton;
|
||||
newContactButton.click().then(function () {
|
||||
expect(contacts.validationError.getText()).toBe('Name is required');
|
||||
contacts.input.sendKeys('John Doe');
|
||||
expect(contacts.contactNameHeader.getText()).toBe('Awesome John Doe');
|
||||
expect(contacts.validationError.getText()).toBe('');
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
describe('index.html', function () {
|
||||
beforeEach(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
describe('app-title', appTitleTests(white, 'Miss Marple'));
|
||||
|
||||
describe('contact', contactTests(lightgray, 'Miss Marple'));
|
||||
|
||||
describe('crisis center', function () {
|
||||
beforeEach(function () {
|
||||
getCommonsSectionStruct().crisisButton.click();
|
||||
});
|
||||
|
||||
it('shows a list of crisis', function () {
|
||||
const crisis = getCrisisSectionStruct();
|
||||
expect(crisis.title.getText()).toBe('Crisis List');
|
||||
expect(crisis.items.count()).toBe(4);
|
||||
expect(crisis.items.get(0).getText()).toBe('1 - Dragon Burning Cities');
|
||||
});
|
||||
|
||||
it('can navigate to one crisis details', function () {
|
||||
const crisis = getCrisisSectionStruct();
|
||||
crisis.items.get(0).click().then(function() {
|
||||
expect(crisis.itemId.getText()).toBe('Crisis id: 1');
|
||||
return crisis.listLink.click();
|
||||
}).then(function () {
|
||||
// We are back to the list
|
||||
expect(crisis.items.count()).toBe(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('heroes', function () {
|
||||
beforeEach(function () {
|
||||
getCommonsSectionStruct().heroesButton.click();
|
||||
});
|
||||
|
||||
it('shows a list of heroes', function() {
|
||||
const heroes = getHeroesSectionStruct();
|
||||
expect(heroes.header.getText()).toBe('Heroes of Miss Marple');
|
||||
expect(heroes.title.getText()).toBe('Hero List');
|
||||
expect(heroes.items.count()).toBe(6);
|
||||
expect(heroes.items.get(0).getText()).toBe('11 - Mr. Nice');
|
||||
});
|
||||
|
||||
it('can navigate and edit one hero details', function () {
|
||||
const heroes = getHeroesSectionStruct();
|
||||
heroes.items.get(0).click().then(function () {
|
||||
expect(heroes.itemId.getText()).toBe('Id: 11');
|
||||
heroes.itemInput.sendKeys(' try');
|
||||
return heroes.listLink.click();
|
||||
}).then(function () {
|
||||
// We are back to the list
|
||||
expect(heroes.items.count()).toBe(6);
|
||||
expect(heroes.items.get(0).getText()).toBe('11 - Mr. Nice try');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// describe('index.0.html', function() {
|
||||
// beforeEach(function () {
|
||||
// browser.get('index.0.html');
|
||||
// });
|
||||
|
||||
// it('has a title', function () {
|
||||
// const title = element.all(by.tagName('h1')).get(0);
|
||||
// expect(title.getText()).toBe('Minimal NgModule');
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('index.1.html', function () {
|
||||
// beforeEach(function () {
|
||||
// browser.get('index.1.html');
|
||||
// });
|
||||
|
||||
// describe('app-title', appTitleTests(powderblue));
|
||||
// });
|
||||
|
||||
// describe('index.1b.html', function () {
|
||||
// beforeEach(function () {
|
||||
// browser.get('index.1b.html');
|
||||
// });
|
||||
|
||||
// describe('app-title', appTitleTests(powderblue));
|
||||
|
||||
// describe('contact', contactTests(powderblue));
|
||||
// });
|
||||
|
||||
// describe('index.2.html', function () {
|
||||
// beforeEach(function () {
|
||||
// browser.get('index.2.html');
|
||||
// });
|
||||
|
||||
// describe('app-title', appTitleTests(gold));
|
||||
|
||||
// describe('contact', contactTests(powderblue));
|
||||
// });
|
||||
|
||||
// describe('index.3.html', function () {
|
||||
// beforeEach(function () {
|
||||
// browser.get('index.3.html');
|
||||
// });
|
||||
|
||||
// describe('app-title', appTitleTests(gold));
|
||||
// });
|
||||
|
||||
});
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"description": "Minimal NgModule",
|
||||
"files": [
|
||||
"src/app/app.component.0.ts",
|
||||
"src/app/app.module.0.ts",
|
||||
"src/main.0.ts",
|
||||
"src/styles.css",
|
||||
"src/index.0.html"
|
||||
],
|
||||
"main": "src/index.0.html",
|
||||
"tags": ["NgModule"]
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"description": "NgModule v.3",
|
||||
"files": [
|
||||
"src/app/app.component.3.ts",
|
||||
"src/app/app.module.3.ts",
|
||||
"src/app/app-routing.module.3.ts",
|
||||
|
||||
"src/app/highlight.directive.ts",
|
||||
"src/app/title.component.html",
|
||||
"src/app/title.component.ts",
|
||||
"src/app/user.service.ts",
|
||||
|
||||
"src/app/contact/contact.component.css",
|
||||
"src/app/contact/contact.component.html",
|
||||
"src/app/contact/contact.service.ts",
|
||||
|
||||
"src/app/contact/awesome.pipe.ts",
|
||||
"src/app/contact/contact.component.3.ts",
|
||||
"src/app/contact/contact.module.3.ts",
|
||||
"src/app/contact/contact-routing.module.3.ts",
|
||||
"src/app/contact/contact-highlight.directive.ts",
|
||||
|
||||
"src/app/crisis/*.ts",
|
||||
|
||||
"src/app/hero/hero-detail.component.ts",
|
||||
"src/app/hero/hero-list.component.ts",
|
||||
"src/app/hero/hero.service.ts",
|
||||
|
||||
"src/app/hero/hero.component.3.ts",
|
||||
"src/app/hero/hero.module.3.ts",
|
||||
"src/app/hero/hero-routing.module.3.ts",
|
||||
"src/app/hero/highlight.directive.ts",
|
||||
|
||||
"src/main.3.ts",
|
||||
"src/styles.css",
|
||||
"src/index.3.html"
|
||||
],
|
||||
"main": "src/index.3.html",
|
||||
"tags": ["NgModule"]
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { ContactModule } from './contact/contact.module.3';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'contact', pathMatch: 'full'},
|
||||
{ path: 'crisis', loadChildren: './crisis/crisis.module#CrisisModule' },
|
||||
{ path: 'heroes', loadChildren: './hero/hero.module.3#HeroModule' }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
ContactModule,
|
||||
RouterModule.forRoot(routes)
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule {}
|
|
@ -1,30 +0,0 @@
|
|||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { ContactModule } from './contact/contact.module';
|
||||
|
||||
// #docregion routes
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'contact', pathMatch: 'full'},
|
||||
// #docregion lazy-routes
|
||||
{ path: 'crisis', loadChildren: './crisis/crisis.module#CrisisModule' },
|
||||
{ path: 'heroes', loadChildren: './hero/hero.module#HeroModule' }
|
||||
// #enddocregion lazy-routes
|
||||
];
|
||||
// #enddocregion routes
|
||||
|
||||
@NgModule({
|
||||
// #docregion imports
|
||||
imports: [
|
||||
ContactModule,
|
||||
// #docregion forRoot
|
||||
RouterModule.forRoot(routes),
|
||||
// #enddocregion forRoot
|
||||
],
|
||||
// #enddocregion imports
|
||||
// #docregion exports
|
||||
exports: [RouterModule]
|
||||
// #enddocregion exports
|
||||
})
|
||||
export class AppRoutingModule {}
|
|
@ -1,10 +0,0 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: '<h1>{{title}}</h1>',
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'Angular Modules';
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
// #enddocregion
|
||||
/*
|
||||
// #docregion template
|
||||
template: '<h1 highlight>{{title}}</h1>'
|
||||
// #enddocregion template
|
||||
*/
|
||||
// #docregion
|
||||
template: '<app-title></app-title>'
|
||||
})
|
||||
export class AppComponent {}
|
||||
// #enddocregion
|
|
@ -1,13 +0,0 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
// #docregion template
|
||||
template: `
|
||||
<app-title></app-title>
|
||||
<app-contact></app-contact>
|
||||
`
|
||||
// #enddocregion template
|
||||
})
|
||||
export class AppComponent {}
|
|
@ -1,10 +0,0 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<app-title></app-title>
|
||||
<app-contact></app-contact>
|
||||
`
|
||||
})
|
||||
export class AppComponent {}
|
|
@ -1,17 +0,0 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
// #docregion template
|
||||
template: `
|
||||
<app-title></app-title>
|
||||
<nav>
|
||||
<a routerLink="contact" routerLinkActive="active">Contact</a>
|
||||
<a routerLink="crisis" routerLinkActive="active">Crisis Center</a>
|
||||
<a routerLink="heroes" routerLinkActive="active">Heroes</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
`
|
||||
// #enddocregion template
|
||||
})
|
||||
export class AppComponent {}
|
|
@ -1,17 +0,0 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<app-title></app-title>
|
||||
<nav>
|
||||
<a routerLink="contact" routerLinkActive="active">Contact</a>
|
||||
<a routerLink="crisis" routerLinkActive="active">Crisis Center</a>
|
||||
<a routerLink="heroes" routerLinkActive="active">Heroes</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
`
|
||||
})
|
||||
export class AppComponent {}
|
|
@ -1,13 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component.0';
|
||||
|
||||
@NgModule({
|
||||
// #docregion imports
|
||||
imports: [ BrowserModule ],
|
||||
// #enddocregion imports
|
||||
declarations: [ AppComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
|
@ -1,52 +0,0 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
/* Angular Imports */
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
/* App Imports */
|
||||
// #enddocregion
|
||||
import { AppComponent } from './app.component.1';
|
||||
/*
|
||||
// #docregion
|
||||
import { AppComponent } from './app.component';
|
||||
// #enddocregion
|
||||
*/
|
||||
// #docregion
|
||||
import { HighlightDirective } from './highlight.directive';
|
||||
import { TitleComponent } from './title.component';
|
||||
import { UserService } from './user.service';
|
||||
|
||||
/* Contact Related Imports */
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { AwesomePipe } from './contact/awesome.pipe';
|
||||
import { ContactComponent } from './contact/contact.component.3';
|
||||
import {
|
||||
ContactHighlightDirective as ContactHighlightDirective
|
||||
} from './contact/contact-highlight.directive';
|
||||
|
||||
@NgModule({
|
||||
// #docregion imports
|
||||
imports: [ BrowserModule, FormsModule ],
|
||||
// #enddocregion imports
|
||||
// #docregion declarations, directive, component
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HighlightDirective,
|
||||
// #enddocregion directive
|
||||
TitleComponent,
|
||||
// #enddocregion component
|
||||
|
||||
AwesomePipe,
|
||||
ContactComponent,
|
||||
ContactHighlightDirective
|
||||
// #docregion directive, component
|
||||
],
|
||||
// #enddocregion declarations, directive, component
|
||||
// #docregion providers
|
||||
providers: [ UserService ],
|
||||
// #enddocregion providers
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
|
@ -1,46 +0,0 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
/* Angular Imports */
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
/* App Imports */
|
||||
// #enddocregion
|
||||
import { AppComponent } from './app.component.1b';
|
||||
/*
|
||||
// #docregion
|
||||
import { AppComponent } from './app.component';
|
||||
// #enddocregion
|
||||
*/
|
||||
// #docregion
|
||||
import { HighlightDirective } from './highlight.directive';
|
||||
import { TitleComponent } from './title.component';
|
||||
import { UserService } from './user.service';
|
||||
|
||||
/* Contact Imports */
|
||||
// #enddocregion
|
||||
import { ContactComponent } from './contact/contact.component.3';
|
||||
/*
|
||||
// #docregion
|
||||
import { ContactComponent } from './contact/contact.component';
|
||||
// #enddocregion
|
||||
*/
|
||||
// #docregion
|
||||
import { AwesomePipe } from './contact/awesome.pipe';
|
||||
import { ContactService } from './contact/contact.service';
|
||||
import { ContactHighlightDirective } from './contact/contact-highlight.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [ BrowserModule, FormsModule ],
|
||||
// #docregion declarations
|
||||
declarations: [
|
||||
AppComponent, HighlightDirective, TitleComponent,
|
||||
AwesomePipe, ContactComponent, ContactHighlightDirective
|
||||
],
|
||||
// #docregion providers
|
||||
providers: [ ContactService, UserService ],
|
||||
// #enddocregion providers
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
|
@ -1,36 +0,0 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
/* Angular Imports */
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
/* App Imports */
|
||||
// #enddocregion
|
||||
import { AppComponent } from './app.component.2';
|
||||
/*
|
||||
// #docregion
|
||||
import { AppComponent } from './app.component';
|
||||
// #enddocregion
|
||||
*/
|
||||
// #docregion
|
||||
import { HighlightDirective } from './highlight.directive';
|
||||
import { TitleComponent } from './title.component';
|
||||
import { UserService } from './user.service';
|
||||
|
||||
/* Contact Imports */
|
||||
// #enddocregion
|
||||
import { ContactModule } from './contact/contact.module.2';
|
||||
/*
|
||||
// #docregion
|
||||
import { ContactModule } from './contact/contact.module';
|
||||
// #enddocregion
|
||||
*/
|
||||
// #docregion
|
||||
|
||||
@NgModule({
|
||||
imports: [ BrowserModule, ContactModule ],
|
||||
declarations: [ AppComponent, HighlightDirective, TitleComponent ],
|
||||
providers: [ UserService ],
|
||||
bootstrap: [ AppComponent ],
|
||||
})
|
||||
export class AppModule { }
|
|
@ -1,39 +0,0 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
// #docregion v4
|
||||
/* Angular Imports */
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
/* App Imports */
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
/* Core Modules */
|
||||
import { CoreModule } from './core/core.module';
|
||||
|
||||
/* Routing Module */
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
||||
@NgModule({
|
||||
// #docregion import-for-root
|
||||
imports: [
|
||||
BrowserModule,
|
||||
// #enddocregion v4
|
||||
// #enddocregion import-for-root
|
||||
/*
|
||||
// #docregion v4
|
||||
CoreModule,
|
||||
// #enddocregion v4
|
||||
*/
|
||||
// #docregion import-for-root
|
||||
CoreModule.forRoot({userName: 'Miss Marple'}),
|
||||
// #docregion v4
|
||||
AppRoutingModule
|
||||
],
|
||||
// #enddocregion import-for-root
|
||||
declarations: [ AppComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
// #enddocregion v4
|
||||
// #enddocregion
|
|
@ -1,14 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { ContactComponent } from './contact.component.3';
|
||||
|
||||
const routes = [
|
||||
{ path: 'contact', component: ContactComponent}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [ RouterModule.forChild(routes) ],
|
||||
exports: [ RouterModule ]
|
||||
})
|
||||
export class ContactRoutingModule {}
|
|
@ -1,16 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { ContactComponent } from './contact.component';
|
||||
|
||||
// #docregion routing
|
||||
const routes = [
|
||||
{ path: 'contact', component: ContactComponent}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [ RouterModule.forChild(routes) ],
|
||||
exports: [ RouterModule ]
|
||||
})
|
||||
export class ContactRoutingModule {}
|
||||
// #enddocregion
|
|
@ -1,53 +0,0 @@
|
|||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { Contact, ContactService } from './contact.service';
|
||||
import { UserService } from '../user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-contact',
|
||||
templateUrl: './contact.component.html',
|
||||
styleUrls: [ './contact.component.css' ]
|
||||
})
|
||||
export class ContactComponent implements OnInit {
|
||||
contact: Contact;
|
||||
contacts: Contact[];
|
||||
|
||||
msg = 'Loading contacts ...';
|
||||
userName = '';
|
||||
|
||||
constructor(private contactService: ContactService, userService: UserService) {
|
||||
this.userName = userService.userName;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.contactService.getContacts().subscribe(contacts => {
|
||||
this.msg = '';
|
||||
this.contacts = contacts;
|
||||
this.contact = contacts[0];
|
||||
});
|
||||
}
|
||||
|
||||
next() {
|
||||
let ix = 1 + this.contacts.indexOf(this.contact);
|
||||
if (ix >= this.contacts.length) { ix = 0; }
|
||||
this.contact = this.contacts[ix];
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
// POST-DEMO TODO: do something like save it
|
||||
this.displayMessage('Saved ' + this.contact.name);
|
||||
}
|
||||
|
||||
newContact() {
|
||||
this.displayMessage('New contact');
|
||||
this.contact = {id: 42, name: ''};
|
||||
this.contacts.push(this.contact);
|
||||
}
|
||||
|
||||
/** Display a message briefly, then remove it. */
|
||||
displayMessage(msg: string) {
|
||||
this.msg = msg;
|
||||
setTimeout(() => this.msg = '', 1500);
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/* #docregion */
|
||||
.ng-valid[required] {
|
||||
border-left: 5px solid #42A948; /* green */
|
||||
}
|
||||
|
||||
.ng-invalid {
|
||||
border-left: 5px solid #a94442; /* red */
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 15px;
|
||||
margin: 8px 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.alert-danger {
|
||||
color: #a94442;
|
||||
background-color: #f2dede;
|
||||
border-color: #ebccd1;
|
||||
}
|
||||
|
||||
.msg {
|
||||
color: blue;
|
||||
background-color: whitesmoke;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
padding-top: 12px;
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
<!-- #docregion -->
|
||||
<h2>Contact of {{userName}}</h2>
|
||||
<div *ngIf="msg" class="msg">{{msg}}</div>
|
||||
|
||||
<form *ngIf="contacts" (ngSubmit)="onSubmit()" #contactForm="ngForm">
|
||||
<!-- #docregion awesome -->
|
||||
<h3 highlight>{{ contact.name | awesome }}</h3>
|
||||
<!-- #enddocregion awesome -->
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
|
||||
<!-- #docregion ngModel -->
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="contact.name"
|
||||
name="name" #name="ngModel" >
|
||||
<!-- #enddocregion ngModel -->
|
||||
|
||||
<div [hidden]="name.valid" class="alert alert-danger">
|
||||
Name is required
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button type="submit" class="btn btn-default"
|
||||
[disabled]="!contactForm.form.valid">
|
||||
Save</button>
|
||||
|
||||
<button type="button" class="btn" (click)="next()"
|
||||
[disabled]="!contactForm.form.valid">
|
||||
Next Contact</button>
|
||||
|
||||
<button type="button" class="btn" (click)="newContact()">
|
||||
New Contact</button>
|
||||
</div>
|
||||
</form>
|
||||
<!-- #enddocregion -->
|
|
@ -1,54 +0,0 @@
|
|||
// Exact copy except import UserService from core
|
||||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { Contact, ContactService } from './contact.service';
|
||||
import { UserService } from '../core/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-contact',
|
||||
templateUrl: './contact.component.html',
|
||||
styleUrls: [ './contact.component.css' ]
|
||||
})
|
||||
export class ContactComponent implements OnInit {
|
||||
contact: Contact;
|
||||
contacts: Contact[];
|
||||
|
||||
msg = 'Loading contacts ...';
|
||||
userName = '';
|
||||
|
||||
constructor(private contactService: ContactService, userService: UserService) {
|
||||
this.userName = userService.userName;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.contactService.getContacts().subscribe(contacts => {
|
||||
this.msg = '';
|
||||
this.contacts = contacts;
|
||||
this.contact = contacts[0];
|
||||
});
|
||||
}
|
||||
|
||||
next() {
|
||||
let ix = 1 + this.contacts.indexOf(this.contact);
|
||||
if (ix >= this.contacts.length) { ix = 0; }
|
||||
this.contact = this.contacts[ix];
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
// POST-DEMO TODO: do something like save it
|
||||
this.displayMessage('Saved ' + this.contact.name);
|
||||
}
|
||||
|
||||
newContact() {
|
||||
this.displayMessage('New contact');
|
||||
this.contact = {id: 42, name: ''};
|
||||
this.contacts.push(this.contact);
|
||||
}
|
||||
|
||||
/** Display a message briefly, then remove it. */
|
||||
displayMessage(msg: string) {
|
||||
this.msg = msg;
|
||||
setTimeout(() => this.msg = '', 1500);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule
|
||||
],
|
||||
declarations: []
|
||||
})
|
||||
export class ContactModule { }
|
|
@ -1,37 +0,0 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { AwesomePipe } from './awesome.pipe';
|
||||
// #enddocregion
|
||||
import { ContactComponent } from './contact.component.3';
|
||||
/*
|
||||
// #docregion
|
||||
import { ContactComponent } from './contact.component';
|
||||
// #enddocregion
|
||||
*/
|
||||
// #docregion
|
||||
import { ContactHighlightDirective } from './contact-highlight.directive';
|
||||
import { ContactService } from './contact.service';
|
||||
|
||||
// #docregion class
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule
|
||||
],
|
||||
declarations: [
|
||||
AwesomePipe,
|
||||
ContactComponent,
|
||||
ContactHighlightDirective
|
||||
],
|
||||
// #docregion exports
|
||||
exports: [ ContactComponent ],
|
||||
// #enddocregion exports
|
||||
providers: [ ContactService ]
|
||||
})
|
||||
export class ContactModule { }
|
||||
// #enddocregion class
|
||||
// #enddocregion
|
|
@ -1,44 +0,0 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { AwesomePipe } from './awesome.pipe';
|
||||
// #enddocregion
|
||||
import { ContactComponent } from './contact.component.3';
|
||||
/*
|
||||
// #docregion
|
||||
import { ContactComponent } from './contact.component';
|
||||
// #enddocregion
|
||||
*/
|
||||
// #docregion
|
||||
import { ContactHighlightDirective } from './contact-highlight.directive';
|
||||
import { ContactService } from './contact.service';
|
||||
|
||||
// #enddocregion
|
||||
import { ContactRoutingModule } from './contact-routing.module.3';
|
||||
/*
|
||||
// #docregion
|
||||
import { ContactRoutingModule } from './contact-routing.module';
|
||||
// #enddocregion
|
||||
*/
|
||||
// #docregion
|
||||
|
||||
// #docregion class
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ContactRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
AwesomePipe,
|
||||
ContactComponent,
|
||||
ContactHighlightDirective
|
||||
],
|
||||
providers: [ ContactService ]
|
||||
})
|
||||
export class ContactModule { }
|
||||
// #enddocregion class
|
||||
// #enddocregion
|
|
@ -1,19 +0,0 @@
|
|||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
import { ContactComponent } from './contact.component';
|
||||
import { ContactService } from './contact.service';
|
||||
import { ContactRoutingModule } from './contact-routing.module';
|
||||
|
||||
// #docregion class
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
ContactRoutingModule
|
||||
],
|
||||
declarations: [ ContactComponent ],
|
||||
providers: [ ContactService ]
|
||||
})
|
||||
export class ContactModule { }
|
||||
// #enddocregion class
|
|
@ -1,37 +0,0 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
|
||||
export class Contact {
|
||||
constructor(public id: number, public name: string) { }
|
||||
}
|
||||
|
||||
const CONTACTS: Contact[] = [
|
||||
new Contact(21, 'Sam Spade'),
|
||||
new Contact(22, 'Nick Danger'),
|
||||
new Contact(23, 'Nancy Drew')
|
||||
];
|
||||
|
||||
const FETCH_LATENCY = 500;
|
||||
|
||||
/** Simulate a data service that retrieves contacts from a server */
|
||||
@Injectable()
|
||||
export class ContactService implements OnDestroy {
|
||||
// #enddocregion
|
||||
constructor() { console.log('ContactService instance created.'); }
|
||||
ngOnDestroy() { console.log('ContactService instance destroyed.'); }
|
||||
|
||||
// #docregion
|
||||
getContacts(): Observable<Contact[]> {
|
||||
return of(CONTACTS).pipe(delay(FETCH_LATENCY));
|
||||
}
|
||||
|
||||
getContact(id: number | string): Observable<Contact> {
|
||||
return of(CONTACTS.find(contact => contact.id === +id))
|
||||
.pipe(delay(FETCH_LATENCY));
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
|
@ -1,48 +0,0 @@
|
|||
/* tslint:disable:member-ordering no-unused-variable */
|
||||
// #docplaster
|
||||
// #docregion
|
||||
// #docregion v4
|
||||
import {
|
||||
ModuleWithProviders, NgModule,
|
||||
Optional, SkipSelf } from '@angular/core';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { TitleComponent } from './title.component';
|
||||
import { UserService } from './user.service';
|
||||
// #enddocregion
|
||||
import { UserServiceConfig } from './user.service';
|
||||
|
||||
// #docregion v4
|
||||
@NgModule({
|
||||
imports: [ CommonModule ],
|
||||
declarations: [ TitleComponent ],
|
||||
exports: [ TitleComponent ],
|
||||
providers: [ UserService ]
|
||||
})
|
||||
export class CoreModule {
|
||||
// #enddocregion v4
|
||||
|
||||
// #docregion ctor
|
||||
constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
|
||||
if (parentModule) {
|
||||
throw new Error(
|
||||
'CoreModule is already loaded. Import it in the AppModule only');
|
||||
}
|
||||
}
|
||||
// #enddocregion ctor
|
||||
|
||||
// #docregion for-root
|
||||
static forRoot(config: UserServiceConfig): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: CoreModule,
|
||||
providers: [
|
||||
{provide: UserServiceConfig, useValue: config }
|
||||
]
|
||||
};
|
||||
}
|
||||
// #enddocregion for-root
|
||||
// #docregion v4
|
||||
}
|
||||
// #enddocregion v4
|
||||
// #enddocregion
|
|
@ -1,6 +0,0 @@
|
|||
<!-- Exact copy from earlier app.component.html -->
|
||||
<h1 highlight>{{title}}</h1>
|
||||
<p *ngIf="user">
|
||||
<i>Welcome, {{user}}</i>
|
||||
<p>
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
// Exact copy of app/title.component.ts except import UserService from shared
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { UserService } from '../core/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-title',
|
||||
templateUrl: './title.component.html',
|
||||
})
|
||||
export class TitleComponent {
|
||||
title = 'Angular Modules';
|
||||
user = '';
|
||||
|
||||
constructor(userService: UserService) {
|
||||
this.user = userService.userName;
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Crazy copy of the app/user.service
|
||||
// Proves that UserService is an app-wide singleton and only instantiated once
|
||||
// IFF shared.module follows the `forRoot` pattern
|
||||
//
|
||||
// If it didn't, a new instance of UserService would be created
|
||||
// after each lazy load and the userName would double up.
|
||||
|
||||
import { Injectable, Optional } from '@angular/core';
|
||||
|
||||
let nextId = 1;
|
||||
|
||||
export class UserServiceConfig {
|
||||
userName = 'Philip Marlowe';
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
id = nextId++;
|
||||
private _userName = 'Sherlock Holmes';
|
||||
|
||||
// #docregion ctor
|
||||
constructor(@Optional() config: UserServiceConfig) {
|
||||
if (config) { this._userName = config.userName; }
|
||||
}
|
||||
// #enddocregion ctor
|
||||
|
||||
get userName() {
|
||||
// Demo: add a suffix if this service has been created more than once
|
||||
const suffix = this.id > 1 ? ` times ${this.id}` : '';
|
||||
return this._userName + suffix;
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h3 highlight>Crisis Detail</h3>
|
||||
<div>Crisis id: {{id}}</div>
|
||||
<br>
|
||||
<a routerLink="../list">Crisis List</a>
|
||||
`
|
||||
})
|
||||
export class CrisisDetailComponent implements OnInit {
|
||||
id: number;
|
||||
constructor(private route: ActivatedRoute) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.id = parseInt(this.route.snapshot.paramMap.get('id'), 10);
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { Crisis,
|
||||
CrisisService } from './crisis.service';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h3 highlight>Crisis List</h3>
|
||||
<div *ngFor='let crisis of crises | async'>
|
||||
<a routerLink="{{'../' + crisis.id}}">{{crisis.id}} - {{crisis.name}}</a>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class CrisisListComponent {
|
||||
crises: Observable<Crisis[]>;
|
||||
|
||||
constructor(private crisisService: CrisisService) {
|
||||
this.crises = this.crisisService.getCrises();
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { Routes,
|
||||
RouterModule } from '@angular/router';
|
||||
|
||||
import { CrisisListComponent } from './crisis-list.component';
|
||||
import { CrisisDetailComponent } from './crisis-detail.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'list', pathMatch: 'full'},
|
||||
{ path: 'list', component: CrisisListComponent },
|
||||
{ path: ':id', component: CrisisDetailComponent }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class CrisisRoutingModule {}
|
|
@ -1,14 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { CrisisListComponent } from './crisis-list.component';
|
||||
import { CrisisDetailComponent } from './crisis-detail.component';
|
||||
import { CrisisService } from './crisis.service';
|
||||
import { CrisisRoutingModule } from './crisis-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, CrisisRoutingModule ],
|
||||
declarations: [ CrisisDetailComponent, CrisisListComponent ],
|
||||
providers: [ CrisisService ]
|
||||
})
|
||||
export class CrisisModule {}
|
|
@ -1,33 +0,0 @@
|
|||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
|
||||
export class Crisis {
|
||||
constructor(public id: number, public name: string) { }
|
||||
}
|
||||
|
||||
const CRISES: Crisis[] = [
|
||||
new Crisis(1, 'Dragon Burning Cities'),
|
||||
new Crisis(2, 'Sky Rains Great White Sharks'),
|
||||
new Crisis(3, 'Giant Asteroid Heading For Earth'),
|
||||
new Crisis(4, 'Procrastinators Meeting Delayed Again'),
|
||||
];
|
||||
|
||||
const FETCH_LATENCY = 500;
|
||||
|
||||
/** Simulate a data service that retrieves crises from a server */
|
||||
@Injectable()
|
||||
export class CrisisService implements OnDestroy {
|
||||
constructor() { console.log('CrisisService instance created.'); }
|
||||
ngOnDestroy() { console.log('CrisisService instance destroyed.'); }
|
||||
|
||||
getCrises(): Observable<Crisis[]> {
|
||||
return of(CRISES).pipe(delay(FETCH_LATENCY));
|
||||
}
|
||||
|
||||
getCrisis(id: number | string): Observable<Crisis> {
|
||||
return of(CRISES.find(crisis => crisis.id === +id))
|
||||
.pipe(delay(FETCH_LATENCY));
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { Hero,
|
||||
HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h3 highlight>Hero Detail</h3>
|
||||
<div *ngIf="hero">
|
||||
<div>Id: {{hero.id}}</div><br>
|
||||
<label>Name:
|
||||
<input [(ngModel)]="hero.name">
|
||||
</label>
|
||||
</div>
|
||||
<br>
|
||||
<a routerLink="../">Hero List</a>
|
||||
`
|
||||
})
|
||||
export class HeroDetailComponent implements OnInit {
|
||||
hero: Hero;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private heroService: HeroService) { }
|
||||
|
||||
ngOnInit() {
|
||||
let id = parseInt(this.route.snapshot.paramMap.get('id'), 10);
|
||||
this.heroService.getHero(id).subscribe(hero => this.hero = hero);
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { Hero,
|
||||
HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h3 highlight>Hero List</h3>
|
||||
<div *ngFor='let hero of heroes | async'>
|
||||
<a routerLink="{{hero.id}}">{{hero.id}} - {{hero.name}}</a>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class HeroListComponent {
|
||||
heroes: Observable<Hero[]>;
|
||||
constructor(private heroService: HeroService) {
|
||||
this.heroes = this.heroService.getHeroes();
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { Routes,
|
||||
RouterModule } from '@angular/router';
|
||||
|
||||
import { HeroComponent } from './hero.component.3';
|
||||
import { HeroListComponent } from './hero-list.component';
|
||||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '',
|
||||
component: HeroComponent,
|
||||
children: [
|
||||
{ path: '', component: HeroListComponent },
|
||||
{ path: ':id', component: HeroDetailComponent }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class HeroRoutingModule {}
|
|
@ -1,23 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { Routes,
|
||||
RouterModule } from '@angular/router';
|
||||
|
||||
import { HeroComponent } from './hero.component';
|
||||
import { HeroListComponent } from './hero-list.component';
|
||||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '',
|
||||
component: HeroComponent,
|
||||
children: [
|
||||
{ path: '', component: HeroListComponent },
|
||||
{ path: ':id', component: HeroDetailComponent }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class HeroRoutingModule {}
|
|
@ -1,18 +0,0 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
import { HeroService } from './hero.service';
|
||||
import { UserService } from '../user.service';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h2>Heroes of {{userName}}</h2>
|
||||
<router-outlet></router-outlet>
|
||||
`,
|
||||
providers: [ HeroService ]
|
||||
})
|
||||
export class HeroComponent {
|
||||
userName = '';
|
||||
constructor(userService: UserService) {
|
||||
this.userName = userService.userName;
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Exact copy except import UserService from core
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { HeroService } from './hero.service';
|
||||
import { UserService } from '../core/user.service';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h2>Heroes of {{userName}}</h2>
|
||||
<router-outlet></router-outlet>
|
||||
`,
|
||||
providers: [ HeroService ]
|
||||
})
|
||||
export class HeroComponent {
|
||||
userName = '';
|
||||
constructor(userService: UserService) {
|
||||
this.userName = userService.userName;
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { HeroComponent } from './hero.component.3';
|
||||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
import { HeroListComponent } from './hero-list.component';
|
||||
import { HeroRoutingModule } from './hero-routing.module.3';
|
||||
|
||||
import { HighlightDirective } from './highlight.directive';
|
||||
|
||||
// #docregion class
|
||||
@NgModule({
|
||||
imports: [ CommonModule, FormsModule, HeroRoutingModule ],
|
||||
declarations: [
|
||||
HeroComponent, HeroDetailComponent, HeroListComponent,
|
||||
HighlightDirective
|
||||
]
|
||||
})
|
||||
export class HeroModule { }
|
||||
// #enddocregion class
|
|
@ -1,16 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
import { HeroComponent } from './hero.component';
|
||||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
import { HeroListComponent } from './hero-list.component';
|
||||
import { HeroRoutingModule } from './hero-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [ SharedModule, HeroRoutingModule ],
|
||||
declarations: [
|
||||
HeroComponent, HeroDetailComponent, HeroListComponent,
|
||||
]
|
||||
})
|
||||
export class HeroModule { }
|
|
@ -1,36 +0,0 @@
|
|||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
|
||||
export class Hero {
|
||||
constructor(public id: number, public name: string) { }
|
||||
}
|
||||
|
||||
const HEROES: Hero[] = [
|
||||
new Hero(11, 'Mr. Nice'),
|
||||
new Hero(12, 'Narco'),
|
||||
new Hero(13, 'Bombasto'),
|
||||
new Hero(14, 'Celeritas'),
|
||||
new Hero(15, 'Magneta'),
|
||||
new Hero(16, 'RubberMan')
|
||||
];
|
||||
|
||||
const FETCH_LATENCY = 500;
|
||||
|
||||
/** Simulate a data service that retrieves heroes from a server */
|
||||
@Injectable()
|
||||
export class HeroService implements OnDestroy {
|
||||
|
||||
constructor() { console.log('HeroService instance created.'); }
|
||||
ngOnDestroy() { console.log('HeroService instance destroyed.'); }
|
||||
|
||||
getHeroes(): Observable<Hero[]> {
|
||||
return of(HEROES).pipe(delay(FETCH_LATENCY));
|
||||
}
|
||||
|
||||
getHero(id: number | string): Observable<Hero> {
|
||||
return of(HEROES.find(hero => hero.id === +id))
|
||||
.pipe(delay(FETCH_LATENCY));
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
// Exact copy of contact.awesome.pipe
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({ name: 'awesome' })
|
||||
/** Precede the input string with the word "Awesome " */
|
||||
export class AwesomePipe implements PipeTransform {
|
||||
transform(phrase: string) {
|
||||
return phrase ? 'Awesome ' + phrase : '';
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
// Exact copy of contact/highlight.directive except for color and message
|
||||
import { Directive, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive({ selector: '[highlight], input' })
|
||||
// Highlight the host element or any InputElement in gray
|
||||
export class HighlightDirective {
|
||||
constructor(el: ElementRef) {
|
||||
el.nativeElement.style.backgroundColor = 'lightgray';
|
||||
console.log(
|
||||
`* Shared highlight called for ${el.nativeElement.tagName}`);
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { AwesomePipe } from './awesome.pipe';
|
||||
import { HighlightDirective } from './highlight.directive';
|
||||
|
||||
// #docregion module
|
||||
@NgModule({
|
||||
imports: [ CommonModule ],
|
||||
declarations: [ AwesomePipe, HighlightDirective ],
|
||||
exports: [ AwesomePipe, HighlightDirective,
|
||||
CommonModule, FormsModule ]
|
||||
})
|
||||
export class SharedModule { }
|
||||
// #enddocregion module
|
||||
// #enddocregion
|
|
@ -1,8 +0,0 @@
|
|||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
/** Dummy version of an authenticated user service */
|
||||
export class UserService {
|
||||
userName = 'Sherlock Holmes';
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<base href="/">
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="/">
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="/">
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="/">
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="/">
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="/">
|
||||
<title>NgModule Deluxe</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||
// #docplaster
|
||||
/*
|
||||
// #docregion
|
||||
// The browser platform without a compiler
|
||||
import { platformBrowser } from '@angular/platform-browser';
|
||||
|
||||
// The app module factory produced by the static offline compiler
|
||||
import { AppModuleNgFactory } from './app/app.module.ngfactory';
|
||||
|
||||
// Launch with the app module factory.
|
||||
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
|
||||
// #enddocregion
|
||||
*/
|
|
@ -1,11 +0,0 @@
|
|||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module.0';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
|
@ -1,11 +0,0 @@
|
|||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module.1';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
|
@ -1,11 +0,0 @@
|
|||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module.1b';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
|
@ -1,11 +0,0 @@
|
|||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module.2';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
|
@ -1,11 +0,0 @@
|
|||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module.3';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
|
@ -1,12 +0,0 @@
|
|||
// #docregion
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"description": "NgModule Final",
|
||||
"files": [
|
||||
"src/app/app.component.ts",
|
||||
"src/app/app.module.ts",
|
||||
"src/app/app-routing.module.ts",
|
||||
|
||||
"src/app/contact/contact.component.css",
|
||||
"src/app/contact/contact.component.html",
|
||||
"src/app/contact/contact.service.ts",
|
||||
|
||||
"src/app/contact/contact.component.ts",
|
||||
"src/app/contact/contact.module.ts",
|
||||
"src/app/contact/contact-routing.module.ts",
|
||||
|
||||
"src/app/crisis/*.ts",
|
||||
|
||||
"src/app/hero/hero-detail.component.ts",
|
||||
"src/app/hero/hero-list.component.ts",
|
||||
"src/app/hero/hero.service.ts",
|
||||
|
||||
"src/app/hero/hero.component.ts",
|
||||
"src/app/hero/hero.module.ts",
|
||||
"src/app/hero/hero-routing.module.ts",
|
||||
|
||||
"src/app/core/*.css",
|
||||
"src/app/core/*.html",
|
||||
"src/app/core/*.ts",
|
||||
|
||||
"src/app/shared/*.css",
|
||||
"src/app/shared/*.html",
|
||||
"src/app/shared/*.ts",
|
||||
|
||||
"src/main.ts",
|
||||
"src/styles.css",
|
||||
"src/index.html"
|
||||
],
|
||||
"main": "src/index.html",
|
||||
"tags": ["NgModule"]
|
||||
}
|
|
@ -5,8 +5,6 @@ import { browser, element, by } from 'protractor';
|
|||
describe('NgModule-example', function () {
|
||||
|
||||
// helpers
|
||||
const gold = 'rgba(255, 215, 0, 1)';
|
||||
const powderblue = 'rgba(176, 224, 230, 1)';
|
||||
const lightgray = 'rgba(239, 238, 237, 1)';
|
||||
const white = 'rgba(0, 0, 0, 0)';
|
||||
|
||||
|
@ -15,7 +13,7 @@ describe('NgModule-example', function () {
|
|||
|
||||
return {
|
||||
title: element.all(by.tagName('h1')).get(0),
|
||||
subtitle: element.all(by.css('app-title p i')).get(0),
|
||||
subtitle: element.all(by.css('app-root p i')).get(0),
|
||||
contactButton: buttons.get(0),
|
||||
itemButton: buttons.get(1),
|
||||
customersButton: buttons.get(2)
|
||||
|
@ -67,7 +65,7 @@ describe('NgModule-example', function () {
|
|||
|
||||
it('should welcome us', function () {
|
||||
const commons = getCommonsSectionStruct();
|
||||
expect(commons.subtitle.getText()).toBe('Welcome, ' + (name || 'Sherlock Holmes'));
|
||||
expect(commons.subtitle.getText()).toBe('Welcome, ' + (name || 'Miss Marple'));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -76,7 +74,7 @@ describe('NgModule-example', function () {
|
|||
return function() {
|
||||
it('shows the contact\'s owner', function() {
|
||||
const contacts = getContactSectionStruct();
|
||||
expect(contacts.header.getText()).toBe('Contact of ' + (name || 'Sherlock Holmes'));
|
||||
expect(contacts.header.getText()).toBe((name || 'Miss Marple') + '\'s Contacts');
|
||||
});
|
||||
|
||||
it('can cycle between contacts', function () {
|
||||
|
@ -92,21 +90,22 @@ describe('NgModule-example', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('can change an existing contact', function () {
|
||||
const contacts = getContactSectionStruct();
|
||||
contacts.input.sendKeys('a');
|
||||
expect(contacts.input.getCssValue('backgroundColor')).toBe(color);
|
||||
expect(contacts.contactNameHeader.getText()).toBe('Awesome Yashaa');
|
||||
});
|
||||
|
||||
it('can create a new contact', function () {
|
||||
const contacts = getContactSectionStruct();
|
||||
const newContactButton = contacts.newContactButton;
|
||||
const nextButton = contacts.nextContactButton;
|
||||
const input = contacts.input;
|
||||
const saveButton = contacts.saveButton;
|
||||
|
||||
newContactButton.click().then(function () {
|
||||
expect(contacts.validationError.getText()).toBe('Name is required');
|
||||
contacts.input.sendKeys('John Doe');
|
||||
expect(contacts.contactNameHeader.getText()).toBe('Awesome John Doe');
|
||||
expect(contacts.validationError.getText()).toBe('');
|
||||
input.click();
|
||||
nextButton.click()
|
||||
expect(contacts.validationError.getText()).toBe('Name is required.');
|
||||
input.click();
|
||||
contacts.input.sendKeys('Watson');
|
||||
saveButton.click()
|
||||
expect(contacts.contactNameHeader.getText()).toBe('Awesome Watson');
|
||||
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Component } from '@angular/core';
|
|||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<app-title></app-title>
|
||||
<app-greeting></app-greeting>
|
||||
<nav>
|
||||
<a routerLink="contact" routerLinkActive="active">Contact</a>
|
||||
<a routerLink="items" routerLinkActive="active">Items</a>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// #docplaster
|
||||
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
|
@ -7,28 +9,30 @@ import { AppComponent } from './app.component';
|
|||
/* Feature Modules */
|
||||
import { ContactModule } from './contact/contact.module';
|
||||
// #docregion import-for-root
|
||||
import { CoreModule } from './core/core.module';
|
||||
import { GreetingModule } from './greeting/greeting.module';
|
||||
// #enddocregion import-for-root
|
||||
|
||||
/* Routing Module */
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
||||
|
||||
// #docregion import-for-root
|
||||
@NgModule({
|
||||
imports: [
|
||||
// #enddocregion import-for-root
|
||||
BrowserModule,
|
||||
ContactModule,
|
||||
CoreModule.forRoot({userName: 'Miss Marple'}),
|
||||
// #docregion import-for-root
|
||||
GreetingModule.forRoot({userName: 'Miss Marple'}),
|
||||
// #enddocregion import-for-root
|
||||
AppRoutingModule
|
||||
// #docregion import-for-root
|
||||
],
|
||||
// #enddocregion import-for-root
|
||||
providers: [],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
// #docregion import-for-root
|
||||
})
|
||||
export class AppModule { }
|
||||
// #enddocregion import-for-root
|
||||
export class AppModule { }
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
button {
|
||||
margin: 1rem 1rem 0 0;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: .5rem;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.alert {
|
||||
color: red;
|
||||
font-style: italic;
|
||||
}
|
|
@ -1,32 +1,30 @@
|
|||
|
||||
<h2>Contact of {{userName}}</h2>
|
||||
<h2>{{userName}}'s Contacts</h2>
|
||||
<div *ngIf="msg" class="msg">{{msg}}</div>
|
||||
|
||||
<form *ngIf="contacts" (ngSubmit)="onSubmit()" #contactForm="ngForm">
|
||||
<form *ngIf="contacts" (ngSubmit)="onSubmit()" [formGroup]="contactForm">
|
||||
<h3 highlight>{{ contact.name | awesome }}</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
|
||||
<input type="text" class="form-control" required
|
||||
[(ngModel)]="contact.name"
|
||||
name="name" #name="ngModel" >
|
||||
|
||||
<div [hidden]="name.valid" class="alert alert-danger">
|
||||
Name is required
|
||||
<input type="text" formControlName="name" required>
|
||||
<div *ngIf="!contactForm.controls['name'].valid && contactForm.controls['name'].touched" class="alert">
|
||||
Name is required.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button type="submit" class="btn btn-default"
|
||||
[disabled]="!contactForm.form.valid">
|
||||
[disabled]="!contactForm.valid">
|
||||
Save</button>
|
||||
|
||||
<button type="button" class="btn" (click)="next()"
|
||||
[disabled]="!contactForm.form.valid">
|
||||
[disabled]="!contactForm.valid && contactForm.touched">
|
||||
Next Contact</button>
|
||||
|
||||
<button type="button" class="btn" (click)="newContact()">
|
||||
New Contact</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
// Exact copy except import UserService from core
|
||||
// Exact copy except import UserService from greeting
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, Validators } from '@angular/forms';
|
||||
|
||||
import { Contact, ContactService } from './contact.service';
|
||||
import { UserService } from '../core/user.service';
|
||||
import { UserService } from '../greeting/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-contact',
|
||||
|
@ -16,15 +17,24 @@ export class ContactComponent implements OnInit {
|
|||
msg = 'Loading contacts ...';
|
||||
userName = '';
|
||||
|
||||
constructor(private contactService: ContactService, userService: UserService) {
|
||||
contactForm = this.fb.group({
|
||||
name: ['', Validators.required]
|
||||
});
|
||||
|
||||
constructor(private contactService: ContactService, userService: UserService, private fb: FormBuilder) {
|
||||
this.userName = userService.userName;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.setupForm();
|
||||
}
|
||||
|
||||
setupForm() {
|
||||
this.contactService.getContacts().subscribe(contacts => {
|
||||
this.msg = '';
|
||||
this.contacts = contacts;
|
||||
this.contact = contacts[0];
|
||||
this.contactForm.get('name').setValue(this.contact.name);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -32,15 +42,18 @@ export class ContactComponent implements OnInit {
|
|||
let ix = 1 + this.contacts.indexOf(this.contact);
|
||||
if (ix >= this.contacts.length) { ix = 0; }
|
||||
this.contact = this.contacts[ix];
|
||||
console.log(this.contacts[ix]);
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
// POST-DEMO TODO: do something like save it
|
||||
this.displayMessage('Saved ' + this.contact.name);
|
||||
let newName = this.contactForm.get('name').value;
|
||||
this.displayMessage('Saved ' + newName);
|
||||
this.contact.name = newName;
|
||||
}
|
||||
|
||||
newContact() {
|
||||
this.displayMessage('New contact');
|
||||
this.contactForm.get('name').setValue('');
|
||||
this.contact = {id: 42, name: ''};
|
||||
this.contacts.push(this.contact);
|
||||
}
|
||||
|
@ -51,4 +64,3 @@ export class ContactComponent implements OnInit {
|
|||
setTimeout(() => this.msg = '', 1500);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { ContactComponent } from './contact.component';
|
||||
import { ContactService } from './contact.service';
|
||||
|
@ -8,7 +9,8 @@ import { ContactRoutingModule } from './contact-routing.module';
|
|||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
ContactRoutingModule
|
||||
ContactRoutingModule,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
declarations: [ ContactComponent ],
|
||||
providers: [ ContactService ]
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
// #docregion whole-core-module
|
||||
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { TitleComponent } from './title.component';
|
||||
// #docregion user-service
|
||||
import { UserService } from './user.service';
|
||||
// #enddocregion user-service
|
||||
import { UserServiceConfig } from './user.service';
|
||||
|
||||
|
||||
// #docregion user-service
|
||||
@NgModule({
|
||||
// #enddocregion user-service
|
||||
imports: [ CommonModule ],
|
||||
declarations: [ TitleComponent ],
|
||||
exports: [ TitleComponent ],
|
||||
// #docregion user-service
|
||||
providers: [ UserService ]
|
||||
})
|
||||
export class CoreModule {
|
||||
// #enddocregion user-service
|
||||
// #docregion ctor
|
||||
constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
|
||||
if (parentModule) {
|
||||
throw new Error(
|
||||
'CoreModule is already loaded. Import it in the AppModule only');
|
||||
}
|
||||
}
|
||||
// #enddocregion ctor
|
||||
|
||||
// #docregion for-root
|
||||
static forRoot(config: UserServiceConfig): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: CoreModule,
|
||||
providers: [
|
||||
{provide: UserServiceConfig, useValue: config }
|
||||
]
|
||||
};
|
||||
}
|
||||
// #enddocregion for-root
|
||||
// #docregion user-service
|
||||
}
|
||||
// #enddocregion user-service
|
||||
// #enddocregion whole-core-module
|
|
@ -1,7 +1,7 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
import { CustomersService } from './customers.service';
|
||||
import { UserService } from '../core/user.service';
|
||||
import { UserService } from '../greeting/user.service';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Component, Input } from '@angular/core';
|
||||
import { UserService } from '../core/user.service';
|
||||
import { UserService } from '../greeting/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-title',
|
||||
templateUrl: './title.component.html',
|
||||
selector: 'app-greeting',
|
||||
templateUrl: './greeting.component.html',
|
||||
})
|
||||
export class TitleComponent {
|
||||
export class GreetingComponent {
|
||||
title = 'NgModules';
|
||||
user = '';
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// #docregion whole-greeting-module
|
||||
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { GreetingComponent } from './greeting.component';
|
||||
import { UserServiceConfig } from './user.service';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule ],
|
||||
declarations: [ GreetingComponent ],
|
||||
exports: [ GreetingComponent ]
|
||||
})
|
||||
export class GreetingModule {
|
||||
// #docregion ctor
|
||||
constructor (@Optional() @SkipSelf() parentModule: GreetingModule) {
|
||||
if (parentModule) {
|
||||
throw new Error(
|
||||
'GreetingModule is already loaded. Import it in the AppModule only');
|
||||
}
|
||||
}
|
||||
// #enddocregion ctor
|
||||
|
||||
// #docregion for-root
|
||||
static forRoot(config: UserServiceConfig): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: GreetingModule,
|
||||
providers: [
|
||||
{provide: UserServiceConfig, useValue: config }
|
||||
]
|
||||
};
|
||||
}
|
||||
// #enddocregion for-root
|
||||
}
|
||||
// #enddocregion whole-greeting-module
|
|
@ -1,8 +1,3 @@
|
|||
// Proves that UserService is an app-wide singleton and only instantiated once
|
||||
// IFF shared.module follows the `forRoot` pattern.
|
||||
//
|
||||
// If it didn't, a new instance of UserService would be created
|
||||
// after each lazy load and the userName would double up.
|
||||
|
||||
import { Injectable, Optional } from '@angular/core';
|
||||
|
||||
|
@ -12,7 +7,9 @@ export class UserServiceConfig {
|
|||
userName = 'Philip Marlowe';
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserService {
|
||||
id = nextId++;
|
||||
private _userName = 'Sherlock Holmes';
|
|
@ -6,16 +6,16 @@ which that injector uses to provide the concrete, runtime version of a dependenc
|
|||
The injector relies on the provider configuration to create instances of the dependencies
|
||||
that it injects into components, directives, pipes, and other services.
|
||||
|
||||
You must configure an injector with a provider, or it won't know how to create the dependency.
|
||||
The most obvious way for an injector to create an instance of a service class is with the class itself.
|
||||
If you specify the service class itself as the provider token, the default behavior is for the injector to instantiate that class with `new`.
|
||||
You must configure an injector with a provider, or it won't know how to create the dependency.
|
||||
The most obvious way for an injector to create an instance of a service class is with the class itself.
|
||||
If you specify the service class itself as the provider token, the default behavior is for the injector to instantiate that class with `new`.
|
||||
|
||||
In the following typical example, the `Logger` class itself provides a `Logger` instance.
|
||||
In the following typical example, the `Logger` class itself provides a `Logger` instance.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-logger">
|
||||
</code-example>
|
||||
|
||||
You can, however, configure an injector with an alternative provider,
|
||||
You can, however, configure an injector with an alternative provider,
|
||||
in order to deliver some other object that provides the needed logging functionality.
|
||||
For instance:
|
||||
* You can provide a substitute class.
|
||||
|
@ -43,10 +43,10 @@ The expanded provider configuration is an object literal with two properties.
|
|||
* The `provide` property holds the [token](guide/dependency-injection#token)
|
||||
that serves as the key for both locating a dependency value and configuring the injector.
|
||||
|
||||
* The second property is a provider definition object, which tells the injector how to create the dependency value.
|
||||
* The second property is a provider definition object, which tells the injector how to create the dependency value.
|
||||
The provider-definition key can be `useClass`, as in the example.
|
||||
It can also be `useExisting`, `useValue`, or `useFactory`.
|
||||
Each of these keys provides a different type of dependency, as discussed below.
|
||||
Each of these keys provides a different type of dependency, as discussed below.
|
||||
|
||||
|
||||
{@a class-provider}
|
||||
|
@ -82,7 +82,7 @@ The injector needs providers for both this new logging service and its dependent
|
|||
|
||||
Suppose an old component depends upon the `OldLogger` class.
|
||||
`OldLogger` has the same interface as `NewLogger`, but for some reason
|
||||
you can't update the old component to use it.
|
||||
you can't update the old component to use it.
|
||||
|
||||
When the old component logs a message with `OldLogger`,
|
||||
you want the singleton instance of `NewLogger` to handle it instead.
|
||||
|
@ -90,7 +90,7 @@ In this case, the dependency injector should inject that singleton instance
|
|||
when a component asks for either the new or the old logger.
|
||||
`OldLogger` should be an *alias* for `NewLogger`.
|
||||
|
||||
If you try to alias `OldLogger` to `NewLogger` with `useClass`, you end up with two different `NewLogger` instances in your app.
|
||||
If you try to alias `OldLogger` to `NewLogger` with `useClass`, you end up with two different `NewLogger` instances in your app.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-6a" linenums="false">
|
||||
</code-example>
|
||||
|
@ -104,8 +104,8 @@ To make sure there is only one instance of `NewLogger`, alias `OldLogger` with t
|
|||
|
||||
## Value providers
|
||||
|
||||
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
||||
To inject an object you have already created,
|
||||
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
||||
To inject an object you have already created,
|
||||
configure the injector with the `useValue` option
|
||||
|
||||
The following code defines a variable that creates such an object to play the logger role.
|
||||
|
@ -137,9 +137,9 @@ They can be object literals, as shown in the following example.
|
|||
|
||||
**TypeScript interfaces are not valid tokens**
|
||||
|
||||
The `HERO_DI_CONFIG` constant conforms to the `AppConfig` interface.
|
||||
The `HERO_DI_CONFIG` constant conforms to the `AppConfig` interface.
|
||||
Unfortunately, you cannot use a TypeScript interface as a token.
|
||||
In TypeScript, an interface is a design-time artifact, and doesn't have a runtime representation (token) that the DI framework can use.
|
||||
In TypeScript, an interface is a design-time artifact, and doesn't have a runtime representation (token) that the DI framework can use.
|
||||
|
||||
<code-example path="dependency-injection/src/app/providers.component.ts" region="providers-9-interface" linenums="false">
|
||||
</code-example>
|
||||
|
@ -194,7 +194,7 @@ it supports typing of the configuration object within the class.
|
|||
## Factory providers
|
||||
|
||||
Sometimes you need to create a dependent value dynamically,
|
||||
based on information you won't have until run time.
|
||||
based on information you won't have until run time.
|
||||
For example, you might need information that changes repeatedly in the course of the browser session.
|
||||
Also, your injectable service might not have independent access to the source of the information.
|
||||
|
||||
|
@ -240,7 +240,7 @@ The injector resolves these tokens and injects the corresponding services into t
|
|||
|
||||
Notice that you captured the factory provider in an exported variable, `heroServiceProvider`.
|
||||
This extra step makes the factory provider reusable.
|
||||
You can configure a provider of `HeroService` with this variable wherever you need it.
|
||||
You can configure a provider of `HeroService` with this variable wherever you need it.
|
||||
In this sample, you need it only in `HeroesComponent`,
|
||||
where `heroServiceProvider` replaces `HeroService` in the metadata `providers` array.
|
||||
|
||||
|
@ -258,10 +258,10 @@ The following shows the new and the old implementations side-by-side.
|
|||
|
||||
## Predefined tokens and multiple providers
|
||||
|
||||
Angular provides a number of built-in injection-token constants that you can use to customize the behavior of
|
||||
various systems.
|
||||
Angular provides a number of built-in injection-token constants that you can use to customize the behavior of
|
||||
various systems.
|
||||
|
||||
For example, you can use the following built-in tokens as hooks into the framework’s bootstrapping and initialization process.
|
||||
For example, you can use the following built-in tokens as hooks into the framework’s bootstrapping and initialization process.
|
||||
A provider object can associate any of these injection tokens with one or more callback functions that take app-specific initialization actions.
|
||||
|
||||
* [PLATFORM_INITIALIZER](api/core/PLATFORM_INITIALIZER): Callback is invoked when a platform is initialized.
|
||||
|
@ -283,19 +283,19 @@ export const APP_TOKENS = [
|
|||
];
|
||||
```
|
||||
|
||||
Multiple providers can be associated with a single token in other areas as well.
|
||||
For example, you can register a custom form validator using the built-in [NG_VALIDATORS](api/forms/NG_VALIDATORS) token,
|
||||
Multiple providers can be associated with a single token in other areas as well.
|
||||
For example, you can register a custom form validator using the built-in [NG_VALIDATORS](api/forms/NG_VALIDATORS) token,
|
||||
and provide multiple instances of a given validator provider by using the `multi: true` property in the provider object.
|
||||
Angular adds your custom validators to the existing collection.
|
||||
|
||||
The Router also makes use of multiple providers associated with a single token.
|
||||
The Router also makes use of multiple providers associated with a single token.
|
||||
When you provide multiple sets of routes using [RouterModule.forRoot](api/router/RouterModule#forroot)
|
||||
and [RouterModule.forChild](api/router/RouterModule#forchild) in a single module,
|
||||
the [ROUTES](api/router/ROUTES) token combines all the different provided sets of routes into a single value.
|
||||
|
||||
<div class="alert is-helpful>
|
||||
|
||||
Search for [Constants in API documentation](api?type=const) to find more built-in tokens.
|
||||
Search for [Constants in API documentation](api?type=const) to find more built-in tokens.
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -304,19 +304,19 @@ Search for [Constants in API documentation](api?type=const) to find more built-i
|
|||
|
||||
## Tree-shakable providers
|
||||
|
||||
Tree shaking refers to a compiler option that removes code from the final bundle if that code not referenced in an application.
|
||||
Tree shaking refers to a compiler option that removes code from the final bundle if the app doesn't reference that code.
|
||||
When providers are tree-shakable, the Angular compiler removes the associated
|
||||
services from the final output when it determines that they are not used in your application.
|
||||
services from the final output when it determines that your application doesn't use those services.
|
||||
This significantly reduces the size of your bundles.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Ideally, if an application isn't injecting a service, it shouldn't be included in the final output.
|
||||
However, Angular has to be able to identify at build time whether the service will be required or not.
|
||||
Because it's always possible to inject a service directly using `injector.get(Service)`,
|
||||
Angular can't identify all of the places in your code where this injection could happen,
|
||||
so it has no choice but to include the service in the injector.
|
||||
Thus, services provided at the NgModule or component level are not tree-shakable.
|
||||
Ideally, if an application isn't injecting a service, Angular shouldn't include it in the final output.
|
||||
However, Angular has to be able to identify at build time whether the app will require the service or not.
|
||||
Because it's always possible to inject a service directly using `injector.get(Service)`,
|
||||
Angular can't identify all of the places in your code where this injection could happen,
|
||||
so it has no choice but to include the service in the injector.
|
||||
Thus, services in the NgModule `providers` array or at component level are not tree-shakable.
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -324,9 +324,9 @@ The following example of non-tree-shakable providers in Angular configures a ser
|
|||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service-and-module.ts" header="src/app/tree-shaking/service-and-modules.ts" linenums="false"> </code-example>
|
||||
|
||||
This module can then be imported into your application module
|
||||
to make the service available for injection in your app,
|
||||
as shown in the following example.
|
||||
You can then import this module into your application module
|
||||
to make the service available for injection in your app,
|
||||
as in the following example.
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/app.module.ts" header="src/app/tree-shaking/app.modules.ts" linenums="false"> </code-example>
|
||||
|
||||
|
@ -350,4 +350,4 @@ The service can be instantiated by configuring a factory function, as in the fol
|
|||
|
||||
To override a tree-shakable provider, configure the injector of a specific NgModule or component with another provider, using the `providers: []` array syntax of the `@NgModule()` or `@Component()` decorator.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -217,6 +217,7 @@ knows that the route list is only responsible for providing additional routes an
|
|||
|
||||
`forRoot()` contains injector configuration which is global; such as configuring the Router. `forChild()` has no injector configuration, only directives such as `RouterOutlet` and `RouterLink`.
|
||||
|
||||
For more information, see the [`forRoot()` deep dive](guide/singleton-services#forRoot) section of the [Singleton Services](guide/singleton-services) guide.
|
||||
|
||||
<hr>
|
||||
|
||||
|
|
|
@ -204,20 +204,18 @@ Apps pass a `Routes` object to `RouterModule.forRoot()` in order to configure th
|
|||
`RouterModule.forRoot()` returns a [ModuleWithProviders](api/core/ModuleWithProviders).
|
||||
You add that result to the `imports` list of the root `AppModule`.
|
||||
|
||||
Only call and import a `.forRoot()` result in the root application module, `AppModule`.
|
||||
Importing it in any other module, particularly in a lazy-loaded module,
|
||||
is contrary to the intent and will likely produce a runtime error.
|
||||
For more information, see [Singleton Services](guide/singleton-services).
|
||||
Only call and import a `forRoot()` result in the root application module, `AppModule`.
|
||||
Avoid importing it in any other module, particularly in a lazy-loaded module. For more
|
||||
information on `forRoot()` see [the `forRoot()` pattern](guide/singleton-services#the-forroot-pattern) section of the [Singleton Services](guide/singleton-services) guide.
|
||||
|
||||
For a service, instead of using `forRoot()`, specify `providedIn: 'root'` on the service's `@Injectable()` decorator, which
|
||||
For a service, instead of using `forRoot()`, specify `providedIn: 'root'` on the service's `@Injectable()` decorator, which
|
||||
makes the service automatically available to the whole application and thus singleton by default.
|
||||
|
||||
`RouterModule` also offers a `forChild` static method for configuring the routes of lazy-loaded modules.
|
||||
`RouterModule` also offers a `forChild()` static method for configuring the routes of lazy-loaded modules.
|
||||
|
||||
`forRoot()` and `forChild()` are conventional names for methods that
|
||||
configure services in root and feature modules respectively.
|
||||
|
||||
Angular doesn't recognize these names but Angular developers do.
|
||||
Follow this convention when you write similar modules with configurable service providers.
|
||||
|
||||
|
||||
|
@ -384,7 +382,7 @@ This means that lazy-loaded modules can't reach them.
|
|||
|
||||
Providers should be configured using `@Injectable` syntax. If possible, they should be provided in the application root (`providedIn: 'root'`). Services that are configured this way are lazily loaded if they are only used from a lazily loaded context.
|
||||
|
||||
If it's the consumer's decision whether a provider is available application-wide or not,
|
||||
If it's the consumer's decision whether a provider is available application-wide or not,
|
||||
then register providers in modules (`@NgModule.providers`) instead of registering in components (`@Component.providers`).
|
||||
|
||||
Register a provider with a component when you _must_ limit the scope of a service instance
|
||||
|
@ -478,9 +476,9 @@ from the root app injector. If the injection succeeds, the class has been loaded
|
|||
You can throw an error or take other remedial action.
|
||||
|
||||
Certain NgModules, such as `BrowserModule`, implement such a guard.
|
||||
Here is a custom constructor for an NgModule called `CoreModule`.
|
||||
Here is a custom constructor for an NgModule called `GreetingModule`.
|
||||
|
||||
<code-example path="ngmodule-faq/src/app/core/core.module.ts" region="ctor" header="src/app/core/core.module.ts (Constructor)" linenums="false">
|
||||
<code-example path="ngmodules/src/app/greeting/greeting.module.ts" region="ctor" header="src/app/greeting/greeting.module.ts (Constructor)" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<hr/>
|
||||
|
@ -591,19 +589,6 @@ Nor should any of its imported or re-exported modules have `providers`.
|
|||
Import the `SharedModule` in your _feature_ modules,
|
||||
both those loaded when the app starts and those you lazy load later.
|
||||
|
||||
### `CoreModule`
|
||||
`CoreModule` is a conventional name for an `NgModule` with `providers` for
|
||||
the singleton services you load when the application starts.
|
||||
|
||||
Import `CoreModule` in the root `AppModule` only.
|
||||
Never import `CoreModule` in any other module.
|
||||
|
||||
Consider making `CoreModule` a pure services module
|
||||
with no `declarations`.
|
||||
|
||||
For more information, see [Sharing NgModules](guide/sharing-ngmodules)
|
||||
and [Singleton Services](guide/singleton-services).
|
||||
|
||||
### Feature Modules
|
||||
|
||||
Feature modules are modules you create around specific application business domains, user workflows, and utility collections. They support your app by containing a particular feature,
|
||||
|
|
|
@ -23,18 +23,18 @@ This command creates the following `UserService` skeleton:
|
|||
|
||||
<code-example path="providers/src/app/user.service.0.ts" header="src/app/user.service.0.ts" linenums="false"> </code-example>
|
||||
|
||||
You can now inject `UserService` anywhere in your application.
|
||||
You can now inject `UserService` anywhere in your application.
|
||||
|
||||
The service itself is a class that the CLI generated and that's decorated with `@Injectable`. By default, this decorator is configured with a `providedIn` property, which creates a provider for the service. In this case, `providedIn: 'root'` specifies that the service should be provided in the root injector.
|
||||
The service itself is a class that the CLI generated and that's decorated with `@Injectable()`. By default, this decorator has a `providedIn` property, which creates a provider for the service. In this case, `providedIn: 'root'` specifies that Angular should provide the service in the root injector.
|
||||
|
||||
|
||||
## Provider scope
|
||||
|
||||
When you add a service provider to the root application injector, it’s available throughout the app. Additionally, these providers are also available to all the classes in the app as long they have the lookup token.
|
||||
When you add a service provider to the root application injector, it’s available throughout the app. Additionally, these providers are also available to all the classes in the app as long they have the lookup token.
|
||||
|
||||
You should always provide your service in the root injector unless there is a case where you want the service to be available only if the consumer imports a particular `@NgModule`.
|
||||
|
||||
## providedIn and NgModules
|
||||
## `providedIn` and NgModules
|
||||
|
||||
It's also possible to specify that a service should be provided in a particular `@NgModule`. For example, if you don't want `UserService` to be available to applications unless they import a `UserModule` you've created, you can specify that the service should be provided in the module:
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ When the browser's URL changes, that router looks for a corresponding `Route`
|
|||
from which it can determine the component to display.
|
||||
|
||||
A router has no routes until you configure it.
|
||||
The following example creates five route definitions, configures the router via the `RouterModule.forRoot` method,
|
||||
The following example creates five route definitions, configures the router via the `RouterModule.forRoot()` method,
|
||||
and adds the result to the `AppModule`'s `imports` array.
|
||||
|
||||
|
||||
|
@ -110,7 +110,7 @@ and adds the result to the `AppModule`'s `imports` array.
|
|||
|
||||
|
||||
The `appRoutes` array of *routes* describes how to navigate.
|
||||
Pass it to the `RouterModule.forRoot` method in the module `imports` to configure the router.
|
||||
Pass it to the `RouterModule.forRoot()` method in the module `imports` to configure the router.
|
||||
|
||||
Each `Route` maps a URL `path` to a component.
|
||||
There are _no leading slashes_ in the _path_.
|
||||
|
@ -148,8 +148,8 @@ If you need to see what events are happening during the navigation lifecycle, th
|
|||
|
||||
### Router outlet
|
||||
|
||||
The `RouterOutlet` is a directive from the router library that is used like a component.
|
||||
It acts as a placeholder that marks the spot in the template where the router should
|
||||
The `RouterOutlet` is a directive from the router library that is used like a component.
|
||||
It acts as a placeholder that marks the spot in the template where the router should
|
||||
display the components for that outlet.
|
||||
|
||||
|
||||
|
@ -199,9 +199,9 @@ The `RouterLinkActive` directive toggles css classes for active `RouterLink` bin
|
|||
On each anchor tag, you see a [property binding](guide/template-syntax#property-binding) to the `RouterLinkActive` directive that look like `routerLinkActive="..."`.
|
||||
|
||||
The template expression to the right of the equals (=) contains a space-delimited string of CSS classes
|
||||
that the Router will add when this link is active (and remove when the link is inactive). You set the `RouterLinkActive`
|
||||
directive to a string of classes such as `[routerLinkActive]="'active fluffy'"` or bind it to a component
|
||||
property that returns such a string.
|
||||
that the Router will add when this link is active (and remove when the link is inactive). You set the `RouterLinkActive`
|
||||
directive to a string of classes such as `[routerLinkActive]="'active fluffy'"` or bind it to a component
|
||||
property that returns such a string.
|
||||
|
||||
Active route links cascade down through each level of the route tree, so parent and child router links can be active at the same time. To override this behavior, you can bind to the `[routerLinkActiveOptions]` input binding with the `{ exact: true }` expression. By using `{ exact: true }`, a given `RouterLink` will only be active if its URL is an exact match to the current URL.
|
||||
|
||||
|
@ -912,10 +912,11 @@ In order to use the Router, you must first register the `RouterModule` from the
|
|||
|
||||
<div class="alert is-important">
|
||||
|
||||
**Note:** The `RouterModule.forRoot` method is a pattern used to register application-wide providers. Read more about application-wide providers in the [Singleton services](guide/singleton-services#forroot) guide.
|
||||
**Note:** The `RouterModule.forRoot` method is a pattern used to register application-wide providers. Read more about application-wide providers in the [Singleton services](guide/singleton-services#forRoot-router) guide.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<code-example path="router/src/app/app.module.1.ts" linenums="false" header="src/app/app.module.ts (first-config)" region="first-config">
|
||||
|
||||
</code-example>
|
||||
|
@ -1071,7 +1072,7 @@ You've learned how to do the following:
|
|||
* Load the router library.
|
||||
* Add a nav bar to the shell template with anchor tags, `routerLink` and `routerLinkActive` directives.
|
||||
* Add a `router-outlet` to the shell template where views will be displayed.
|
||||
* Configure the router module with `RouterModule.forRoot`.
|
||||
* Configure the router module with `RouterModule.forRoot()`.
|
||||
* Set the router to compose HTML5 browser URLs.
|
||||
* handle invalid routes with a `wildcard` route.
|
||||
* navigate to the default route when the app launches with an empty path.
|
||||
|
@ -1147,7 +1148,7 @@ The starter app's structure looks like this:
|
|||
hero-list.component.ts
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
|
@ -1173,7 +1174,7 @@ The starter app's structure looks like this:
|
|||
page-not-found.component.ts
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
|
@ -1280,9 +1281,8 @@ The **Routing Module** has several characteristics:
|
|||
### Integrate routing with your app
|
||||
|
||||
The sample routing application does not include routing by default.
|
||||
When you create a new workspace and initial application, the [Angular CLI](cli) [`ng new`](cli/new) command prompts you to add "Angular routing". Enter `Y`.
|
||||
When you generate a new application using the [`ng generate app`](cli/generate) command, specify the `--routing` option. These options tell the CLI to include the `@angular/router` npm package and create a file named `app-routing.module.ts`.
|
||||
|
||||
When you use the [Angular CLI](cli) to create a project that will use routing, set the `--routing` option for the project or app, and for each NgModule.
|
||||
When you create or initialize a new project (using the CLI [`ng new`](cli/new) command) or a new app (using the [`ng generate app`](cli/generate) command), specify the `--routing` option. This tells the CLI to include the `@angular/router` npm package and create a file named `app-routing.module.ts`.
|
||||
You can then use routing in any NgModule that you add to the project or app.
|
||||
|
||||
For example, the following command generates an NgModule that can use routing.
|
||||
|
@ -1307,7 +1307,7 @@ Create an `AppRouting` module in the `/app` folder to contain the routing config
|
|||
|
||||
Import the `CrisisListComponent`, `HeroListComponent`, and `PageNotFoundComponent` symbols
|
||||
just like you did in the `app.module.ts`. Then move the `Router` imports
|
||||
and routing configuration, including `RouterModule.forRoot`, into this routing module.
|
||||
and routing configuration, including `RouterModule.forRoot()`, into this routing module.
|
||||
|
||||
Re-export the Angular `RouterModule` by adding it to the module `exports` array.
|
||||
By re-exporting the `RouterModule` here the components declared in `AppModule` will have access to router directives such as `RouterLink` and `RouterOutlet`.
|
||||
|
@ -1318,7 +1318,7 @@ After these steps, the file should look like this.
|
|||
|
||||
</code-example>
|
||||
|
||||
Next, update the `app.module.ts` file, removing `RouterModule.forRoot` in
|
||||
Next, update the `app.module.ts` file, removing `RouterModule.forRoot()` in
|
||||
the `imports` array.
|
||||
|
||||
<code-example path="router/src/app/app.module.2.ts" header="src/app/app.module.ts">
|
||||
|
@ -1427,7 +1427,7 @@ Follow these steps:
|
|||
|
||||
* Change the component class name to `HeroListComponent`.
|
||||
* Change the `selector` to `app-hero-list`.
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Selectors are **not required** for _routed components_ due to the components are dynamically inserted when the page is rendered, but are useful for identifying and targeting them in your HTML element tree.
|
||||
|
@ -1501,7 +1501,7 @@ When you're done, you'll have these *hero management* files:
|
|||
|
||||
<div class='file'>
|
||||
hero.service.ts
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
hero.ts
|
||||
|
@ -1509,7 +1509,7 @@ When you're done, you'll have these *hero management* files:
|
|||
|
||||
<div class='file'>
|
||||
heroes-routing.module.ts
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
heroes.module.ts
|
||||
|
@ -1551,7 +1551,7 @@ Now that you have routes for the `Heroes` module, register them with the `Router
|
|||
`RouterModule` _almost_ as you did in the `AppRoutingModule`.
|
||||
|
||||
There is a small but critical difference.
|
||||
In the `AppRoutingModule`, you used the static **`RouterModule.forRoot`** method to register the routes and application level service providers.
|
||||
In the `AppRoutingModule`, you used the static **`RouterModule.forRoot()`** method to register the routes and application level service providers.
|
||||
In a feature module you use the static **`forChild`** method.
|
||||
|
||||
|
||||
|
@ -1559,7 +1559,7 @@ In a feature module you use the static **`forChild`** method.
|
|||
|
||||
|
||||
|
||||
Only call `RouterModule.forRoot` in the root `AppRoutingModule`
|
||||
Only call `RouterModule.forRoot()` in the root `AppRoutingModule`
|
||||
(or the `AppModule` if that's where you register top level application routes).
|
||||
In any other module, you must call the **`RouterModule.forChild`** method to register additional routes.
|
||||
|
||||
|
@ -2243,7 +2243,7 @@ This file does the following:
|
|||
|
||||
You could also create more transitions for other routes. This trigger is sufficient for the current milestone.
|
||||
|
||||
Back in the `AppComponent`, import the `RouterOutlet` token from the `@angular/router` package and the `slideInAnimation` from
|
||||
Back in the `AppComponent`, import the `RouterOutlet` token from the `@angular/router` package and the `slideInAnimation` from
|
||||
`'./animations.ts`.
|
||||
|
||||
Add an `animations` array to the `@Component` metadata's that contains the `slideInAnimation`.
|
||||
|
@ -2325,7 +2325,7 @@ After these changes, the folder structure looks like this:
|
|||
crisis-list.component.ts
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
heroes
|
||||
|
@ -2375,7 +2375,7 @@ After these changes, the folder structure looks like this:
|
|||
|
||||
<div class='file'>
|
||||
hero.service.ts
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
hero.ts
|
||||
|
@ -2383,7 +2383,7 @@ After these changes, the folder structure looks like this:
|
|||
|
||||
<div class='file'>
|
||||
heroes-routing.module.ts
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
heroes.module.ts
|
||||
|
@ -2418,7 +2418,7 @@ After these changes, the folder structure looks like this:
|
|||
page-not-found.component.ts
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -2453,7 +2453,7 @@ After these changes, the folder structure looks like this:
|
|||
|
||||
<div class='file'>
|
||||
message.service.ts
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='file'>
|
||||
index.html
|
||||
|
@ -2487,7 +2487,7 @@ Here are the relevant files for this version of the sample application.
|
|||
|
||||
<code-pane header="animations.ts" path="router/src/app/animations.ts">
|
||||
|
||||
</code-pane>
|
||||
</code-pane>
|
||||
|
||||
<code-pane header="app.component.html" path="router/src/app/app.component.2.html">
|
||||
|
||||
|
@ -2507,11 +2507,11 @@ Here are the relevant files for this version of the sample application.
|
|||
|
||||
<code-pane header="hero-list.component.css" path="router/src/app/heroes/hero-list/hero-list.component.css">
|
||||
|
||||
</code-pane>
|
||||
</code-pane>
|
||||
|
||||
<code-pane header="hero-list.component.html" path="router/src/app/heroes/hero-list/hero-list.component.html">
|
||||
|
||||
</code-pane>
|
||||
</code-pane>
|
||||
|
||||
<code-pane header="hero-list.component.ts" path="router/src/app/heroes/hero-list/hero-list.component.ts">
|
||||
|
||||
|
@ -2539,7 +2539,7 @@ Here are the relevant files for this version of the sample application.
|
|||
|
||||
<code-pane header="message.service.ts" path="router/src/app/message.service.ts">
|
||||
|
||||
</code-pane>
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
@ -2721,7 +2721,7 @@ _before_ the `AppRoutingModule`:
|
|||
|
||||
<code-pane path="router/src/app/crisis-center/crisis-center.module.ts"header="src/app/crisis-center/crisis-center.module.ts">
|
||||
|
||||
</code-pane>
|
||||
</code-pane>
|
||||
|
||||
<code-pane path="router/src/app/app.module.4.ts" linenums="false" header="src/app/app.module.ts (import CrisisCenterModule)" region="crisis-center-module">
|
||||
|
||||
|
@ -3382,7 +3382,7 @@ update the admin route with a `canActivate` guard property that references it:
|
|||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
The admin feature is now protected by the guard, albeit protected poorly.
|
||||
|
||||
|
||||
|
@ -3461,7 +3461,7 @@ Register a `/login` route in the `auth/auth-routing.module.ts`. In `app.module.t
|
|||
|
||||
<code-pane header="src/app/auth/login/login.component.html" path="router/src/app/auth/login/login.component.html">
|
||||
|
||||
</code-pane>
|
||||
</code-pane>
|
||||
|
||||
<code-pane header="src/app/auth/login/login.component.ts" path="router/src/app/auth/login/login.component.1.ts">
|
||||
|
||||
|
@ -4131,9 +4131,9 @@ You could try this now and confirm that the `CrisisCenterModule` loads after yo
|
|||
|
||||
To enable preloading of all lazy loaded modules, import the `PreloadAllModules` token from the Angular router package.
|
||||
|
||||
The second argument in the `RouterModule.forRoot` method takes an object for additional configuration options.
|
||||
The second argument in the `RouterModule.forRoot()` method takes an object for additional configuration options.
|
||||
The `preloadingStrategy` is one of those options.
|
||||
Add the `PreloadAllModules` token to the `forRoot` call:
|
||||
Add the `PreloadAllModules` token to the `forRoot()` call:
|
||||
|
||||
<code-example path="router/src/app/app-routing.module.6.ts" linenums="false" header="src/app/app-routing.module.ts (preload all)" region="forRoot">
|
||||
|
||||
|
@ -4220,7 +4220,7 @@ Shortly, you'll extend the `AdminDashboardComponent` to inject this service and
|
|||
But first, make a few changes to the `AppRoutingModule`.
|
||||
|
||||
1. Import `SelectivePreloadingStrategyService` into `AppRoutingModule`.
|
||||
1. Replace the `PreloadAllModules` strategy in the call to `forRoot` with this `SelectivePreloadingStrategyService`.
|
||||
1. Replace the `PreloadAllModules` strategy in the call to `forRoot()` with this `SelectivePreloadingStrategyService`.
|
||||
1. Add the `SelectivePreloadingStrategyService` strategy to the `AppRoutingModule` providers array so it can be injected
|
||||
elsewhere in the app.
|
||||
|
||||
|
@ -4477,7 +4477,7 @@ The router supports both styles with two `LocationStrategy` providers:
|
|||
1. `PathLocationStrategy`—the default "HTML5 pushState" style.
|
||||
1. `HashLocationStrategy`—the "hash URL" style.
|
||||
|
||||
The `RouterModule.forRoot` function sets the `LocationStrategy` to the `PathLocationStrategy`,
|
||||
The `RouterModule.forRoot()` function sets the `LocationStrategy` to the `PathLocationStrategy`,
|
||||
making it the default strategy.
|
||||
You can switch to the `HashLocationStrategy` with an override during the bootstrapping process if you prefer it.
|
||||
|
||||
|
@ -4592,7 +4592,7 @@ Those developers may still use HTML5 URLs by taking two remedial steps:
|
|||
#### *HashLocationStrategy*
|
||||
|
||||
You can go old-school with the `HashLocationStrategy` by
|
||||
providing the `useHash: true` in an object as the second argument of the `RouterModule.forRoot`
|
||||
providing the `useHash: true` in an object as the second argument of the `RouterModule.forRoot()`
|
||||
in the `AppModule`.
|
||||
|
||||
|
||||
|
|
|
@ -14,46 +14,99 @@ For a sample app using the app-wide singleton service that this page describes,
|
|||
|
||||
There are two ways to make a service a singleton in Angular:
|
||||
|
||||
* Declare that the service should be provided in the application root.
|
||||
* Include the service in the `AppModule` or in a module that is only imported by the `AppModule`.
|
||||
* Declare `root` for the value of the `@Injectable()` `providedIn` property
|
||||
* Include the service in the `AppModule` or in a module that is only imported by the `AppModule`
|
||||
|
||||
Beginning with Angular 6.0, the preferred way to create a singleton service is to specify on the service that it should be provided in the application root. This is done by setting `providedIn` to `root` on the service's `@Injectable` decorator:
|
||||
|
||||
{@a providedIn}
|
||||
|
||||
### Using `providedIn`
|
||||
|
||||
Beginning with Angular 6.0, the preferred way to create a singleton service is to set `providedIn` to `root` on the service's `@Injectable()` decorator. This tells Angular
|
||||
to provide the service in the application root.
|
||||
|
||||
<code-example path="providers/src/app/user.service.0.ts" header="src/app/user.service.0.ts" linenums="false"> </code-example>
|
||||
|
||||
|
||||
For more detailed information on services, see the [Services](tutorial/toh-pt4) chapter of the
|
||||
[Tour of Heroes tutorial](tutorial).
|
||||
|
||||
### NgModule `providers` array
|
||||
|
||||
## `forRoot()`
|
||||
In apps built with Angular versions prior to 6.0, services are registered NgModule `providers` arrays as follows:
|
||||
|
||||
If a module provides both providers and declarations (components, directives, pipes) then loading it in a child injector such as a route, would duplicate the provider instances. The duplication of providers would cause issues as they would shadow the root instances, which are probably meant to be singletons. For this reason Angular provides a way to separate providers out of the module so that same module can be imported into the root module with `providers` and child modules without `providers`.
|
||||
```ts
|
||||
@NgModule({
|
||||
...
|
||||
providers: [UserService],
|
||||
...
|
||||
})
|
||||
|
||||
1. Create a static method `forRoot()` (by convention) on the module.
|
||||
2. Place the providers into the `forRoot` method as follows.
|
||||
```
|
||||
|
||||
<!-- MH: show a simple example how to do that without going to deep into it. -->
|
||||
If this NgModule were the root `AppModule`, the `UserService` would be a singleton and available
|
||||
throughout the app. Though you may see it coded this way, using the `providedIn` property of the `@Injectable()` decorator on the service itself is preferable as of Angular 6.0 as it makes your services tree-shakable.
|
||||
|
||||
To make this more concrete, consider the `RouterModule` as an example. `RouterModule` needs to provide the `Router` service, as well as the `RouterOutlet` directive. `RouterModule` has to be imported by the root application module so that the application has a `Router` and the application has at least one `RouterOutlet`. It also must be imported by the individual route components so that they can place `RouterOutlet` directives into their template for sub-routes.
|
||||
{@a forRoot}
|
||||
|
||||
If the `RouterModule` didn’t have `forRoot()` then each route component would instantiate a new `Router` instance, which would break the application as there can only be one `Router`. For this reason, the `RouterModule` has the `RouterOutlet` declaration so that it is available everywhere, but the `Router` provider is only in the `forRoot()`. The result is that the root application module imports `RouterModule.forRoot(...)` and gets a `Router`, whereas all route components import `RouterModule` which does not include the `Router`.
|
||||
## The `forRoot()` pattern
|
||||
|
||||
If you have a module which provides both providers and declarations, use this pattern to separate them out.
|
||||
Generally, you'll only need `providedIn` for providing services and `forRoot()`/`forChild()` for routing. However, understanding how `forRoot()` works to make sure a service is a singleton will inform your development at a deeper level.
|
||||
|
||||
A module that adds providers to the application can offer a
|
||||
facility for configuring those providers as well through the
|
||||
`forRoot()` method.
|
||||
If a module defines both providers and declarations (components, directives, pipes),
|
||||
then loading the module in multiple feature modules would duplicate the registration of the service. This could result in multiple service instances and the service would no longer behave as a singleton.
|
||||
|
||||
There are multiple ways to prevent this:
|
||||
|
||||
* Use the [`providedIn` syntax](guide/singleton-services#providedIn) instead of registering the service in the module.
|
||||
* Separate your services into their own module.
|
||||
* Define `forRoot()` and `forChild()` methods in the module.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** There are two example apps where you can see this scenario; the more advanced <live-example noDownload>NgModules live example</live-example>, which contains `forRoot()` and `forChild()` in the routing modules and the `GreetingModule`, and the simpler <live-example name="lazy-loading-ngmodules" noDownload>Lazy Loading live example</live-example>. For an introductory explanation see the [Lazy Loading Feature Modules](guide/lazy-loading-ngmodules) guide.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
Use `forRoot()` to
|
||||
separate providers from a module so you can import that module into the root module
|
||||
with `providers` and child modules without `providers`.
|
||||
|
||||
1. Create a static method `forRoot()` on the module.
|
||||
2. Place the providers into the `forRoot()` method.
|
||||
|
||||
<code-example path="ngmodules/src/app/greeting/greeting.module.ts" region="for-root" header="src/app/greeting/greeting.module.ts" linenums="false"> </code-example>
|
||||
|
||||
|
||||
{@a forRoot-router}
|
||||
|
||||
### `forRoot()` and the `Router`
|
||||
|
||||
`RouterModule` provides the `Router` service, as well as router directives, such as `RouterOutlet` and `routerLink`. The root application module imports `RouterModule` so that the application has a `Router` and the root application components can access the router directives. Any feature modules must also import `RouterModule` so that their components can place router directives into their templates.
|
||||
|
||||
If the `RouterModule` didn’t have `forRoot()` then each feature module would instantiate a new `Router` instance, which would break the application as there can only be one `Router`. By using the `forRoot()` method, the root application module imports `RouterModule.forRoot(...)` and gets a `Router`, and all feature modules import `RouterModule.forChild(...)` which does not instantiate another `Router`.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
**Note:** If you have a module which has both providers and declarations,
|
||||
you _can_ use this
|
||||
technique to separate them out and you may see this pattern in legacy apps.
|
||||
However, since Angular 6.0, the best practice for providing services is with the
|
||||
`@Injectable()` `providedIn` property.
|
||||
|
||||
</div>
|
||||
|
||||
### How `forRoot()` works
|
||||
|
||||
`forRoot()` takes a service configuration object and returns a
|
||||
[ModuleWithProviders](api/core/ModuleWithProviders), which is
|
||||
a simple object with the following properties:
|
||||
|
||||
* `ngModule`: in this example, the `CoreModule` class.
|
||||
* `providers`: the configured providers.
|
||||
* `ngModule`: in this example, the `GreetingModule` class
|
||||
* `providers`: the configured providers
|
||||
|
||||
In the <live-example name="ngmodules">live example</live-example>
|
||||
the root `AppModule` imports the `CoreModule` and adds the
|
||||
the root `AppModule` imports the `GreetingModule` and adds the
|
||||
`providers` to the `AppModule` providers. Specifically,
|
||||
Angular accumulates all imported providers
|
||||
before appending the items listed in `@NgModule.providers`.
|
||||
|
@ -61,25 +114,26 @@ This sequence ensures that whatever you add explicitly to
|
|||
the `AppModule` providers takes precedence over the providers
|
||||
of imported modules.
|
||||
|
||||
Import `CoreModule` and use its `forRoot()` method one time, in `AppModule`, because it registers services and you only want to register those services one time in your app. If you were to register them more than once, you could end up with multiple instances of the service and a runtime error.
|
||||
The sample app imports `GreetingModule` and uses its `forRoot()` method one time, in `AppModule`. Registering it once like this prevents multiple instances.
|
||||
|
||||
You can also add a `forRoot()` method in the `CoreModule` that configures
|
||||
the core `UserService`.
|
||||
You can also add a `forRoot()` method in the `GreetingModule` that configures
|
||||
the greeting `UserService`.
|
||||
|
||||
In the following example, the optional, injected `UserServiceConfig`
|
||||
extends the core `UserService`. If a `UserServiceConfig` exists, the `UserService` sets the user name from that config.
|
||||
extends the greeting `UserService`. If a `UserServiceConfig` exists, the `UserService` sets the user name from that config.
|
||||
|
||||
<code-example path="ngmodules/src/app/core/user.service.ts" region="ctor" header="src/app/core/user.service.ts (constructor)" linenums="false">
|
||||
<code-example path="ngmodules/src/app/greeting/user.service.ts" region="ctor" header="src/app/greeting/user.service.ts (constructor)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Here's `forRoot()` that takes a `UserServiceConfig` object:
|
||||
|
||||
<code-example path="ngmodules/src/app/core/core.module.ts" region="for-root" header="src/app/core/core.module.ts (forRoot)" linenums="false">
|
||||
<code-example path="ngmodules/src/app/greeting/greeting.module.ts" region="for-root" header="src/app/greeting/greeting.module.ts (forRoot)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
Lastly, call it within the `imports` list of the `AppModule`.
|
||||
Lastly, call it within the `imports` list of the `AppModule`. In the following
|
||||
snippet, other parts of the file are left out. For the complete file, see the <live-example name="ngmodules"></live-example>, or continue to the next section of this document.
|
||||
|
||||
<code-example path="ngmodules/src/app/app.module.ts" region="import-for-root" header="src/app/app.module.ts (imports)" linenums="false">
|
||||
|
||||
|
@ -87,61 +141,50 @@ Lastly, call it within the `imports` list of the `AppModule`.
|
|||
|
||||
The app displays "Miss Marple" as the user instead of the default "Sherlock Holmes".
|
||||
|
||||
Remember to _import_ `CoreModule` as a Javascript import at the top of the file; don't add it to more than one `@NgModule` `imports` list.
|
||||
Remember to import `GreetingModule` as a Javascript import at the top of the file and don't add it to more than one `@NgModule` `imports` list.
|
||||
|
||||
<!-- KW--Does this mean that if we need it elsewhere we only import it at the top? I thought the services would all be available since we were importing it into `AppModule` in `providers`. -->
|
||||
## Prevent reimport of the `GreetingModule`
|
||||
|
||||
## Prevent reimport of the `CoreModule`
|
||||
|
||||
Only the root `AppModule` should import the `CoreModule`. If a
|
||||
Only the root `AppModule` should import the `GreetingModule`. If a
|
||||
lazy-loaded module imports it too, the app can generate
|
||||
[multiple instances](guide/ngmodule-faq#q-why-bad) of a service.
|
||||
|
||||
To guard against a lazy-loaded module re-importing `CoreModule`, add the following `CoreModule` constructor.
|
||||
To guard against a lazy loaded module re-importing `GreetingModule`, add the following `GreetingModule` constructor.
|
||||
|
||||
<code-example path="ngmodules/src/app/core/core.module.ts" region="ctor" header="src/app/core/core.module.ts" linenums="false">
|
||||
<code-example path="ngmodules/src/app/greeting/greeting.module.ts" region="ctor" header="src/app/greeting/greeting.module.ts" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
The constructor tells Angular to inject the `CoreModule` into itself.
|
||||
The constructor tells Angular to inject the `GreetingModule` into itself.
|
||||
The injection would be circular if Angular looked for
|
||||
`CoreModule` in the _current_ injector. The `@SkipSelf`
|
||||
decorator means "look for `CoreModule` in an ancestor
|
||||
`GreetingModule` in the _current_ injector, but the `@SkipSelf()`
|
||||
decorator means "look for `GreetingModule` in an ancestor
|
||||
injector, above me in the injector hierarchy."
|
||||
|
||||
If the constructor executes as intended in the `AppModule`,
|
||||
there would be no ancestor injector that could provide an instance of `CoreModule` and the injector should give up.
|
||||
|
||||
By default, the injector throws an error when it can't
|
||||
find a requested provider.
|
||||
The `@Optional` decorator means not finding the service is OK.
|
||||
The `@Optional()` decorator means not finding the service is OK.
|
||||
The injector returns `null`, the `parentModule` parameter is null,
|
||||
and the constructor concludes uneventfully.
|
||||
|
||||
It's a different story if you improperly import `CoreModule` into a lazy-loaded module such as `CustomersModule`.
|
||||
It's a different story if you improperly import `GreetingModule` into a lazy loaded module such as `CustomersModule`.
|
||||
|
||||
Angular creates a lazy-loaded module with its own injector,
|
||||
a _child_ of the root injector.
|
||||
`@SkipSelf` causes Angular to look for a `CoreModule` in the parent injector, which this time is the root injector.
|
||||
Angular creates a lazy loaded module with its own injector,
|
||||
a child of the root injector.
|
||||
`@SkipSelf()` causes Angular to look for a `GreetingModule` in the parent injector, which this time is the root injector.
|
||||
Of course it finds the instance imported by the root `AppModule`.
|
||||
Now `parentModule` exists and the constructor throws the error.
|
||||
|
||||
Here are the two files in their entirety for reference:
|
||||
|
||||
<code-tabs linenums="false">
|
||||
<code-pane
|
||||
header="app.module.ts"
|
||||
path="ngmodules/src/app/app.module.ts">
|
||||
<code-pane header="app.module.ts" path="ngmodules/src/app/app.module.ts">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
header="core.module.ts"
|
||||
region="whole-core-module"
|
||||
path="ngmodules/src/app/core/core.module.ts">
|
||||
<code-pane header="greeting.module.ts" region="whole-greeting-module" path="ngmodules/src/app/greeting/greeting.module.ts">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
|
||||
<hr>
|
||||
<hr />
|
||||
|
||||
## More on NgModules
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue