diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e27f3b5cc2..1453c0d706 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -754,6 +754,10 @@ testing/** @angular/fw-test /aio/content/examples/toh-pt4/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes /aio/content/examples/toh-pt5/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes /aio/content/examples/toh-pt6/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes +/aio/content/examples/getting-started-v0/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes +/aio/content/examples/getting-started/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes +/aio/content/getting-started/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes +/aio/content/images/guide/getting-started/** @angular/fw-docs-intro @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes diff --git a/aio/content/examples/getting-started-v0/e2e/src/app.e2e-spec.ts b/aio/content/examples/getting-started-v0/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000000..09aa3abfed --- /dev/null +++ b/aio/content/examples/getting-started-v0/e2e/src/app.e2e-spec.ts @@ -0,0 +1,21 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Getting Started V0', () => { + beforeEach(() => { + return browser.get('/'); + }); + + it('should display "My Store" in the top bar', async() => { + const title = await element(by.css('app-root app-top-bar h1')).getText(); + + expect(title).toEqual('My Store'); + }); + + it('should display "Products" on the homepage', async() => { + const title = await element(by.css('app-root app-product-list h2')).getText(); + + expect(title).toEqual('Products'); + }); +}); diff --git a/aio/content/examples/getting-started-v0/example-config.json b/aio/content/examples/getting-started-v0/example-config.json new file mode 100644 index 0000000000..32a5ae69cc --- /dev/null +++ b/aio/content/examples/getting-started-v0/example-config.json @@ -0,0 +1,4 @@ +{ + "useCommonBoilerplate": false, + "projectType": "getting-started" +} \ No newline at end of file diff --git a/aio/content/examples/getting-started-v0/src/app/app.component.css b/aio/content/examples/getting-started-v0/src/app/app.component.css new file mode 100644 index 0000000000..b7ef084c56 --- /dev/null +++ b/aio/content/examples/getting-started-v0/src/app/app.component.css @@ -0,0 +1,3 @@ +p { + font-family: Lato; +} \ No newline at end of file diff --git a/aio/content/examples/getting-started-v0/src/app/app.component.html b/aio/content/examples/getting-started-v0/src/app/app.component.html new file mode 100644 index 0000000000..14109efcf1 --- /dev/null +++ b/aio/content/examples/getting-started-v0/src/app/app.component.html @@ -0,0 +1,5 @@ + + +
+ +
\ No newline at end of file diff --git a/aio/content/examples/getting-started-v0/src/app/app.component.ts b/aio/content/examples/getting-started-v0/src/app/app.component.ts new file mode 100644 index 0000000000..e98ec2a712 --- /dev/null +++ b/aio/content/examples/getting-started-v0/src/app/app.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: [ './app.component.css' ] +}) +export class AppComponent {} diff --git a/aio/content/examples/getting-started-v0/src/app/app.module.ts b/aio/content/examples/getting-started-v0/src/app/app.module.ts new file mode 100644 index 0000000000..1dbba86431 --- /dev/null +++ b/aio/content/examples/getting-started-v0/src/app/app.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { TopBarComponent } from './top-bar/top-bar.component'; +import { ProductListComponent } from './product-list/product-list.component'; + +@NgModule({ + imports: [ + BrowserModule, + ReactiveFormsModule, + RouterModule.forRoot([ + { path: '', component: ProductListComponent }, + ]) + ], + declarations: [ + AppComponent, + TopBarComponent, + ProductListComponent + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/getting-started-v0/src/app/product-list/product-list.component.css b/aio/content/examples/getting-started-v0/src/app/product-list/product-list.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/getting-started-v0/src/app/product-list/product-list.component.html b/aio/content/examples/getting-started-v0/src/app/product-list/product-list.component.html new file mode 100644 index 0000000000..1007667e7b --- /dev/null +++ b/aio/content/examples/getting-started-v0/src/app/product-list/product-list.component.html @@ -0,0 +1 @@ +

Products

diff --git a/aio/content/examples/getting-started-v0/src/app/product-list/product-list.component.ts b/aio/content/examples/getting-started-v0/src/app/product-list/product-list.component.ts new file mode 100644 index 0000000000..af123522ea --- /dev/null +++ b/aio/content/examples/getting-started-v0/src/app/product-list/product-list.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; + +import { products } from '../products'; + +@Component({ + selector: 'app-product-list', + templateUrl: './product-list.component.html', + styleUrls: ['./product-list.component.css'] +}) +export class ProductListComponent { + products = products; + + share() { + window.alert('The product has been shared!'); + } +} diff --git a/aio/content/examples/getting-started-v0/src/app/products.ts b/aio/content/examples/getting-started-v0/src/app/products.ts new file mode 100644 index 0000000000..da94ebbdea --- /dev/null +++ b/aio/content/examples/getting-started-v0/src/app/products.ts @@ -0,0 +1,17 @@ +export const products = [ + { + name: 'Phone XL', + price: 799, + description: 'A large phone with one of the best screens' + }, + { + name: 'Phone Mini', + price: 699, + description: 'A great phone with one of the best cameras' + }, + { + name: 'Phone Standard', + price: 299, + description: '' + } +]; diff --git a/aio/content/examples/getting-started-v0/src/app/top-bar/top-bar.component.css b/aio/content/examples/getting-started-v0/src/app/top-bar/top-bar.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/getting-started-v0/src/app/top-bar/top-bar.component.html b/aio/content/examples/getting-started-v0/src/app/top-bar/top-bar.component.html new file mode 100644 index 0000000000..dafaed6350 --- /dev/null +++ b/aio/content/examples/getting-started-v0/src/app/top-bar/top-bar.component.html @@ -0,0 +1,5 @@ + +

My Store

+
+ +shopping_cartCheckout \ No newline at end of file diff --git a/aio/content/examples/getting-started-v0/src/app/top-bar/top-bar.component.ts b/aio/content/examples/getting-started-v0/src/app/top-bar/top-bar.component.ts new file mode 100644 index 0000000000..e531b08799 --- /dev/null +++ b/aio/content/examples/getting-started-v0/src/app/top-bar/top-bar.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-top-bar', + templateUrl: './top-bar.component.html', + styleUrls: ['./top-bar.component.css'] +}) +export class TopBarComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/aio/content/examples/getting-started-v0/src/assets/shipping.json b/aio/content/examples/getting-started-v0/src/assets/shipping.json new file mode 100644 index 0000000000..94db54c96d --- /dev/null +++ b/aio/content/examples/getting-started-v0/src/assets/shipping.json @@ -0,0 +1,14 @@ +[ + { + "type": "Overnight", + "price": 25.99 + }, + { + "type": "2-Day", + "price": 9.99 + }, + { + "type": "Postal", + "price": 2.99 + } +] \ No newline at end of file diff --git a/aio/content/examples/getting-started-v0/src/index.html b/aio/content/examples/getting-started-v0/src/index.html new file mode 100644 index 0000000000..d7c3a0a6d6 --- /dev/null +++ b/aio/content/examples/getting-started-v0/src/index.html @@ -0,0 +1,18 @@ + + + + + Angular Getting Started + + + + + + + + + + diff --git a/aio/content/examples/getting-started-v0/src/main.ts b/aio/content/examples/getting-started-v0/src/main.ts new file mode 100644 index 0000000000..61c5f24eca --- /dev/null +++ b/aio/content/examples/getting-started-v0/src/main.ts @@ -0,0 +1,11 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { environment } from './environments/environment'; + +import { AppModule } from './app/app.module'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/getting-started-v0/stackblitz.json b/aio/content/examples/getting-started-v0/stackblitz.json new file mode 100644 index 0000000000..a1f0944d46 --- /dev/null +++ b/aio/content/examples/getting-started-v0/stackblitz.json @@ -0,0 +1,9 @@ +{ + "description": "Getting Started", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[0-9].*" + ], + "tags": ["Angular", "getting started", "tutorial"] +} diff --git a/aio/content/examples/getting-started/e2e/src/app.e2e-spec.ts b/aio/content/examples/getting-started/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000000..e2a2bd2db0 --- /dev/null +++ b/aio/content/examples/getting-started/e2e/src/app.e2e-spec.ts @@ -0,0 +1,118 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by, ExpectedConditions as EC, logging, ElementFinder, ElementArrayFinder } from 'protractor'; + +describe('Getting Started', () => { + const pageElements = { + topBarHeader: element(by.css('app-root app-top-bar h1')), + topBarLinks: element(by.css('app-root app-top-bar a')), + topBarCheckoutLink: element(by.cssContainingText('app-root app-top-bar a', 'Checkout')), + productListHeader: element(by.css('app-root app-product-list h2')), + productListItems: element.all(by.css('app-root app-product-list h3')), + productListLinks: element.all(by.css('app-root app-product-list a')), + productDetailsPage: element(by.css('app-root app-product-details div')), + cartPage: element(by.css('app-root app-cart')) + }; + + describe('General', () => { + beforeAll(async() => { + await browser.get('/'); + }); + + it('should display "My Store"', async() => { + const title = await pageElements.topBarHeader.getText(); + + expect(title).toEqual('My Store'); + }); + + it('should display "Products" on the homepage', async() => { + const title = await pageElements.productListHeader.getText(); + + expect(title).toEqual('Products'); + }); + }); + + describe('Product List', () => { + beforeAll(async() => { + await browser.get('/'); + }); + + it('should display 3 items', async() => { + const products = await pageElements.productListItems; + + expect(products.length).toEqual(3); + }); + }); + + describe('Product Details', () => { + beforeEach(async() => { + await browser.get('/'); + }); + + it('should display information for a product', async() => { + await pageElements.productListLinks.get(0).click(); + + const product = pageElements.productDetailsPage; + const productHeader = await product.element(by.css('h3')).getText(); + const productPrice = await product.element(by.css('h4')).getText(); + const productDescription = await product.element(by.css('p')).getText(); + + expect(await product.isDisplayed()).toBeTruthy(); + expect(productHeader).toBe('Phone XL'); + expect(productPrice).toBe('$799.00'); + expect(productDescription).toBe('A large phone with one of the best screens'); + }); + + it('should add the product to the cart', async() => { + await pageElements.productListLinks.get(0).click(); + + const product = pageElements.productDetailsPage; + const buyButton = await product.element(by.css('button')); + const checkoutLink = pageElements.topBarCheckoutLink; + + await buyButton.click(); + await browser.wait(EC.alertIsPresent(), 1000); + await browser.switchTo().alert().accept(); + await checkoutLink.click(); + + const cartItems = await element.all(by.css('app-root app-cart div.cart-item')); + expect(cartItems.length).toBe(1); + }); + }); + + describe('Cart', () => { + + beforeEach(async() => { + await browser.get('/'); + }); + + it('should go through the checkout process', async() => { + await pageElements.productListLinks.get(0).click(); + + const checkoutLink = pageElements.topBarCheckoutLink; + const productDetailsPage = pageElements.productDetailsPage; + const buyButton = await productDetailsPage.element(by.css('button')); + + const cartPage = pageElements.cartPage; + const inputFields = cartPage.all(by.css('form input')); + + const purchaseButton = await cartPage.element(by.css('button')); + const nameField = inputFields.get(0); + const addressField = inputFields.get(1); + + await buyButton.click(); + await browser.wait(EC.alertIsPresent(), 1000); + await browser.switchTo().alert().accept(); + await checkoutLink.click(); + + await nameField.sendKeys('Customer'); + await addressField.sendKeys('Address'); + await purchaseButton.click(); + + const logs = await browser.manage().logs().get(logging.Type.BROWSER); + const cartMessages = logs.filter(({ message }) => message.includes('Your order has been submitted')); + + expect(cartMessages.length).toBe(1); + }); + }); +}); diff --git a/aio/content/examples/getting-started/example-config.json b/aio/content/examples/getting-started/example-config.json new file mode 100644 index 0000000000..32a5ae69cc --- /dev/null +++ b/aio/content/examples/getting-started/example-config.json @@ -0,0 +1,4 @@ +{ + "useCommonBoilerplate": false, + "projectType": "getting-started" +} \ No newline at end of file diff --git a/aio/content/examples/getting-started/src/app/app.component.css b/aio/content/examples/getting-started/src/app/app.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/getting-started/src/app/app.component.html b/aio/content/examples/getting-started/src/app/app.component.html new file mode 100644 index 0000000000..14109efcf1 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/app.component.html @@ -0,0 +1,5 @@ + + +
+ +
\ No newline at end of file diff --git a/aio/content/examples/getting-started/src/app/app.component.ts b/aio/content/examples/getting-started/src/app/app.component.ts new file mode 100644 index 0000000000..68478a8623 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/app.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] +}) +export class AppComponent { +} diff --git a/aio/content/examples/getting-started/src/app/app.module.ts b/aio/content/examples/getting-started/src/app/app.module.ts new file mode 100644 index 0000000000..cbbecc5b5e --- /dev/null +++ b/aio/content/examples/getting-started/src/app/app.module.ts @@ -0,0 +1,55 @@ +// #docplaster +// #docregion http-client-module-import, http-client-module +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; +import { HttpClientModule } from '@angular/common/http'; +// #enddocregion http-client-module-import +import { ReactiveFormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { TopBarComponent } from './top-bar/top-bar.component'; +import { ProductListComponent } from './product-list/product-list.component'; +import { ProductAlertsComponent } from './product-alerts/product-alerts.component'; +import { ProductDetailsComponent } from './product-details/product-details.component'; +// #enddocregion http-client-module +import { CartComponent } from './cart/cart.component'; +import { ShippingComponent } from './shipping/shipping.component'; + +// #docregion product-details-route, http-client-module, shipping-route, cart-route + +@NgModule({ + imports: [ + BrowserModule, + // #enddocregion product-details-route, cart-route + HttpClientModule, + // #docregion product-details-route, cart-route + ReactiveFormsModule, + RouterModule.forRoot([ + { path: '', component: ProductListComponent }, + { path: 'products/:productId', component: ProductDetailsComponent }, +// #enddocregion product-details-route + { path: 'cart', component: CartComponent }, +// #enddocregion cart-route, http-client-module + { path: 'shipping', component: ShippingComponent }, +// #enddocregion shipping-route +// #docregion product-details-route, http-client-module, shipping-route, cart-route + ]) + ], + // #enddocregion product-details-route, cart-route + declarations: [ + AppComponent, + TopBarComponent, + ProductListComponent, + ProductAlertsComponent, + ProductDetailsComponent, +// #enddocregion http-client-module + CartComponent, + ShippingComponent +// #docregion http-client-module + ], + bootstrap: [ + AppComponent + ] +}) +export class AppModule { } diff --git a/aio/content/examples/getting-started/src/app/cart.service.1.ts b/aio/content/examples/getting-started/src/app/cart.service.1.ts new file mode 100644 index 0000000000..40d52da108 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/cart.service.1.ts @@ -0,0 +1,13 @@ +// #docplaster +// #docregion +import { Injectable } from '@angular/core'; + +// #docregion v1 +@Injectable({ + providedIn: 'root' +}) +export class CartService { + + constructor() {} + +} diff --git a/aio/content/examples/getting-started/src/app/cart.service.ts b/aio/content/examples/getting-started/src/app/cart.service.ts new file mode 100644 index 0000000000..af7a3dc930 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/cart.service.ts @@ -0,0 +1,39 @@ +// #docplaster +// #docregion import-http +import { Injectable } from '@angular/core'; + +import { HttpClient } from '@angular/common/http'; +// #enddocregion import-http +@Injectable({ + providedIn: 'root' +}) +// #docregion props, methods, inject-http, get-shipping +export class CartService { + items = []; +// #enddocregion props, methods + + constructor( + private http: HttpClient + ) {} +// #enddocregion inject-http +// #docregion methods + + addToCart(product) { + this.items.push(product); + } + + getItems() { + return this.items; + } + + clearCart() { + this.items = []; + return this.items; + } +// #enddocregion methods + + getShippingPrices() { + return this.http.get('/assets/shipping.json'); + } +// #docregion props, methods, import-inject +} diff --git a/aio/content/examples/getting-started/src/app/cart/cart.component.1.ts b/aio/content/examples/getting-started/src/app/cart/cart.component.1.ts new file mode 100644 index 0000000000..c9276d4d36 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/cart/cart.component.1.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-cart', + templateUrl: './cart.component.html', + styleUrls: ['./cart.component.css'] +}) +export class CartComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/aio/content/examples/getting-started/src/app/cart/cart.component.2.html b/aio/content/examples/getting-started/src/app/cart/cart.component.2.html new file mode 100644 index 0000000000..050618d664 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/cart/cart.component.2.html @@ -0,0 +1,15 @@ + + +

Cart

+ + +

+ Shipping Prices +

+ + +
+ {{ item.name }} + {{ item.price | currency }} +
+ \ No newline at end of file diff --git a/aio/content/examples/getting-started/src/app/cart/cart.component.2.ts b/aio/content/examples/getting-started/src/app/cart/cart.component.2.ts new file mode 100644 index 0000000000..8615c5a391 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/cart/cart.component.2.ts @@ -0,0 +1,21 @@ +// #docplaster +// #docregion imports +import { Component } from '@angular/core'; +import { CartService } from '../cart.service'; +// #enddocregion imports + +@Component({ + selector: 'app-cart', + templateUrl: './cart.component.html', + styleUrls: ['./cart.component.css'] +}) +// #docregion inject-cart, items, submit +export class CartComponent { +// #enddocregion inject-cart + items; +// #docregion inject-cart + + constructor( + private cartService: CartService + ) { } +} diff --git a/aio/content/examples/getting-started/src/app/cart/cart.component.3.ts b/aio/content/examples/getting-started/src/app/cart/cart.component.3.ts new file mode 100644 index 0000000000..e7cb5addac --- /dev/null +++ b/aio/content/examples/getting-started/src/app/cart/cart.component.3.ts @@ -0,0 +1,21 @@ +// #docplaster +// #docregion imports +import { Component } from '@angular/core'; +import { CartService } from '../cart.service'; +// #enddocregion imports + +@Component({ + selector: 'app-cart', + templateUrl: './cart.component.html', + styleUrls: ['./cart.component.css'] +}) +// #docregion props-services, submit +export class CartComponent { + items; + +constructor( + private cartService: CartService + ) { + this.items = this.cartService.getItems(); + } +} diff --git a/aio/content/examples/getting-started/src/app/cart/cart.component.css b/aio/content/examples/getting-started/src/app/cart/cart.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/getting-started/src/app/cart/cart.component.html b/aio/content/examples/getting-started/src/app/cart/cart.component.html new file mode 100644 index 0000000000..b3315d7391 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/cart/cart.component.html @@ -0,0 +1,28 @@ + + +

Cart

+ +

+ Shipping Prices +

+ +
+ {{ item.name }} + {{ item.price | currency }} +
+ +
+ +
+ + +
+ +
+ + +
+ + + +
diff --git a/aio/content/examples/getting-started/src/app/cart/cart.component.ts b/aio/content/examples/getting-started/src/app/cart/cart.component.ts new file mode 100644 index 0000000000..30b9a9d142 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/cart/cart.component.ts @@ -0,0 +1,40 @@ +// #docplaster +// #docregion imports +import { Component } from '@angular/core'; +import { FormBuilder } from '@angular/forms'; + +import { CartService } from '../cart.service'; +// #enddocregion imports + +@Component({ + selector: 'app-cart', + templateUrl: './cart.component.html', + styleUrls: ['./cart.component.css'] +}) +// #docregion props-services, submit +export class CartComponent { + items; + checkoutForm; + + constructor( + private cartService: CartService, + private formBuilder: FormBuilder, + ) { + this.items = this.cartService.getItems(); + + this.checkoutForm = this.formBuilder.group({ + name: '', + address: '' + }); + } + + // #enddocregion props-services + onSubmit(customerData) { + // Process checkout data here + console.warn('Your order has been submitted', customerData); + + this.items = this.cartService.clearCart(); + this.checkoutForm.reset(); + } + // #docregion props-services +} diff --git a/aio/content/examples/getting-started/src/app/product-alerts/product-alerts.component.1.html b/aio/content/examples/getting-started/src/app/product-alerts/product-alerts.component.1.html new file mode 100644 index 0000000000..7ed54e8511 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-alerts/product-alerts.component.1.html @@ -0,0 +1,3 @@ +

+ +

diff --git a/aio/content/examples/getting-started/src/app/product-alerts/product-alerts.component.1.ts b/aio/content/examples/getting-started/src/app/product-alerts/product-alerts.component.1.ts new file mode 100644 index 0000000000..166a32daad --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-alerts/product-alerts.component.1.ts @@ -0,0 +1,19 @@ +// #docplaster +// #docregion as-generated, imports +import { Component } from '@angular/core'; +// #enddocregion as-generated +import { Input } from '@angular/core'; +// #enddocregion imports +// #docregion as-generated + +@Component({ + selector: 'app-product-alerts', + templateUrl: './product-alerts.component.html', + styleUrls: ['./product-alerts.component.css'] +}) +// #docregion input-decorator +export class ProductAlertsComponent { +// #enddocregion as-generated + @Input() product; +// #docregion as-generated +} diff --git a/aio/content/examples/getting-started/src/app/product-alerts/product-alerts.component.css b/aio/content/examples/getting-started/src/app/product-alerts/product-alerts.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/getting-started/src/app/product-alerts/product-alerts.component.html b/aio/content/examples/getting-started/src/app/product-alerts/product-alerts.component.html new file mode 100644 index 0000000000..bcf6553d99 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-alerts/product-alerts.component.html @@ -0,0 +1,3 @@ +

+ +

diff --git a/aio/content/examples/getting-started/src/app/product-alerts/product-alerts.component.ts b/aio/content/examples/getting-started/src/app/product-alerts/product-alerts.component.ts new file mode 100644 index 0000000000..42b4d59e0a --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-alerts/product-alerts.component.ts @@ -0,0 +1,17 @@ +// #docplaster +// #docregion imports +import { Component } from '@angular/core'; +import { Input } from '@angular/core'; +import { Output, EventEmitter } from '@angular/core'; +// #enddocregion imports + +@Component({ + selector: 'app-product-alerts', + templateUrl: './product-alerts.component.html', + styleUrls: ['./product-alerts.component.css'] +}) +// #docregion input-output +export class ProductAlertsComponent { + @Input() product; + @Output() notify = new EventEmitter(); +} diff --git a/aio/content/examples/getting-started/src/app/product-details/product-details.component.1.ts b/aio/content/examples/getting-started/src/app/product-details/product-details.component.1.ts new file mode 100644 index 0000000000..ec22944f7d --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-details/product-details.component.1.ts @@ -0,0 +1,31 @@ +// #docplaster +// #docregion imports +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { products } from '../products'; +// #enddocregion imports + +@Component({ + selector: 'app-product-details', + templateUrl: './product-details.component.html', + styleUrls: ['./product-details.component.css'] +}) +// #docregion props-methods, add-to-cart +export class ProductDetailsComponent implements OnInit { + product; + + constructor( + private route: ActivatedRoute, + ) { } + + // #enddocregion props-methods + // #docregion get-product + ngOnInit() { + this.route.paramMap.subscribe(params => { + this.product = products[+params.get('productId')]; + }); + } + // #enddocregion get-product + // #docregion props-methods +} diff --git a/aio/content/examples/getting-started/src/app/product-details/product-details.component.css b/aio/content/examples/getting-started/src/app/product-details/product-details.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/getting-started/src/app/product-details/product-details.component.html b/aio/content/examples/getting-started/src/app/product-details/product-details.component.html new file mode 100644 index 0000000000..5b4a5452d2 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-details/product-details.component.html @@ -0,0 +1,13 @@ + + +

Product Details

+ +
+

{{ product.name }}

+

{{ product.price | currency }}

+

{{ product.description }}

+ + + + +
diff --git a/aio/content/examples/getting-started/src/app/product-details/product-details.component.ts b/aio/content/examples/getting-started/src/app/product-details/product-details.component.ts new file mode 100644 index 0000000000..0f53e18b04 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-details/product-details.component.ts @@ -0,0 +1,46 @@ +// #docplaster +// #docregion imports, cart-service +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { products } from '../products'; +// #enddocregion imports +import { CartService } from '../cart.service'; +// #enddocregion cart-service + +@Component({ + selector: 'app-product-details', + templateUrl: './product-details.component.html', + styleUrls: ['./product-details.component.css'] +}) +// #docregion props-methods, get-product, inject-cart-service, add-to-cart +export class ProductDetailsComponent implements OnInit { +// #enddocregion add-to-cart, get-product, inject-cart-service + product; + +// #docregion inject-cart-service + constructor( + private route: ActivatedRoute, +// #enddocregion props-methods + private cartService: CartService +// #docregion props-methods + ) { } +// #enddocregion inject-cart-service + +// #docregion get-product + ngOnInit() { +// #enddocregion props-methods + this.route.paramMap.subscribe(params => { + this.product = products[+params.get('productId')]; + }); +// #docregion props-methods + } + +// #enddocregion props-methods, get-product +// #docregion add-to-cart + addToCart(product) { + window.alert('Your product has been added to the cart!'); + this.cartService.addToCart(product); + } +// #docregion props-methods, get-product, inject-cart-service +} diff --git a/aio/content/examples/getting-started/src/app/product-list/product-list.component.1.html b/aio/content/examples/getting-started/src/app/product-list/product-list.component.1.html new file mode 100644 index 0000000000..1007667e7b --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-list/product-list.component.1.html @@ -0,0 +1 @@ +

Products

diff --git a/aio/content/examples/getting-started/src/app/product-list/product-list.component.1.ts b/aio/content/examples/getting-started/src/app/product-list/product-list.component.1.ts new file mode 100644 index 0000000000..f70d700ab1 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-list/product-list.component.1.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; +import { products } from '../products'; + +@Component({ + selector: 'app-product-list', + templateUrl: './product-list.component.html', + styleUrls: ['./product-list.component.css'] +}) +export class ProductListComponent { + products = products; + + share() { + window.alert('The product has been shared!'); + } +} diff --git a/aio/content/examples/getting-started/src/app/product-list/product-list.component.2.html b/aio/content/examples/getting-started/src/app/product-list/product-list.component.2.html new file mode 100644 index 0000000000..4849729b49 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-list/product-list.component.2.html @@ -0,0 +1,20 @@ + + +

Products

+ +
+ + +

+ + + + {{ product.name }} + + + +

+ + +
+ diff --git a/aio/content/examples/getting-started/src/app/product-list/product-list.component.3.html b/aio/content/examples/getting-started/src/app/product-list/product-list.component.3.html new file mode 100644 index 0000000000..3e957f5740 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-list/product-list.component.3.html @@ -0,0 +1,15 @@ +

Products

+ +
+ +

+ + {{ product.name }} + +

+ +

+ Description: {{ product.description }} +

+ +
diff --git a/aio/content/examples/getting-started/src/app/product-list/product-list.component.4.html b/aio/content/examples/getting-started/src/app/product-list/product-list.component.4.html new file mode 100644 index 0000000000..1e2781d8d2 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-list/product-list.component.4.html @@ -0,0 +1,19 @@ +

Products

+ +
+ +

+ + {{ product.name }} + +

+ +

+ Description: {{ product.description }} +

+ + + +
diff --git a/aio/content/examples/getting-started/src/app/product-list/product-list.component.5.html b/aio/content/examples/getting-started/src/app/product-list/product-list.component.5.html new file mode 100644 index 0000000000..131c58543e --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-list/product-list.component.5.html @@ -0,0 +1,28 @@ +

Products

+ +
+ + +

+ + {{ product.name }} + +

+ + +

+ Description: {{ product.description }} +

+ + + + + + + + +
+ diff --git a/aio/content/examples/getting-started/src/app/product-list/product-list.component.6.html b/aio/content/examples/getting-started/src/app/product-list/product-list.component.6.html new file mode 100644 index 0000000000..47ed78e7e1 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-list/product-list.component.6.html @@ -0,0 +1,27 @@ +

Products

+ +
+ +

+ + {{ product.name }} + +

+ +

+ Description: {{ product.description }} +

+ + + + + + + + +
+ diff --git a/aio/content/examples/getting-started/src/app/product-list/product-list.component.css b/aio/content/examples/getting-started/src/app/product-list/product-list.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/getting-started/src/app/product-list/product-list.component.html b/aio/content/examples/getting-started/src/app/product-list/product-list.component.html new file mode 100644 index 0000000000..f1b86df848 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-list/product-list.component.html @@ -0,0 +1,26 @@ +

Products

+ + +
+ +

+ + {{ product.name }} + +

+ +

+ Description: {{ product.description }} +

+ + + + + + +
+ diff --git a/aio/content/examples/getting-started/src/app/product-list/product-list.component.ts b/aio/content/examples/getting-started/src/app/product-list/product-list.component.ts new file mode 100644 index 0000000000..4a6c616738 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/product-list/product-list.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; + +import { products } from '../products'; + +@Component({ + selector: 'app-product-list', + templateUrl: './product-list.component.html', + styleUrls: ['./product-list.component.css'] +}) +// #docregion on-notify +export class ProductListComponent { + products = products; + + share() { + window.alert('The product has been shared!'); + } + + onNotify() { + window.alert('You will be notified when the product goes on sale'); + } +} diff --git a/aio/content/examples/getting-started/src/app/products.ts b/aio/content/examples/getting-started/src/app/products.ts new file mode 100644 index 0000000000..da94ebbdea --- /dev/null +++ b/aio/content/examples/getting-started/src/app/products.ts @@ -0,0 +1,17 @@ +export const products = [ + { + name: 'Phone XL', + price: 799, + description: 'A large phone with one of the best screens' + }, + { + name: 'Phone Mini', + price: 699, + description: 'A great phone with one of the best cameras' + }, + { + name: 'Phone Standard', + price: 299, + description: '' + } +]; diff --git a/aio/content/examples/getting-started/src/app/shipping/shipping.component.1.ts b/aio/content/examples/getting-started/src/app/shipping/shipping.component.1.ts new file mode 100644 index 0000000000..04f1772ab9 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/shipping/shipping.component.1.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-shipping', + templateUrl: './shipping.component.html', + styleUrls: ['./shipping.component.css'] +}) +export class Shipping1Component implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/aio/content/examples/getting-started/src/app/shipping/shipping.component.css b/aio/content/examples/getting-started/src/app/shipping/shipping.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/getting-started/src/app/shipping/shipping.component.html b/aio/content/examples/getting-started/src/app/shipping/shipping.component.html new file mode 100644 index 0000000000..d4566bc318 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/shipping/shipping.component.html @@ -0,0 +1,6 @@ +

Shipping Prices

+ +
+ {{ shipping.type }} + {{ shipping.price | currency }} +
\ No newline at end of file diff --git a/aio/content/examples/getting-started/src/app/shipping/shipping.component.ts b/aio/content/examples/getting-started/src/app/shipping/shipping.component.ts new file mode 100644 index 0000000000..a5119c8319 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/shipping/shipping.component.ts @@ -0,0 +1,22 @@ +// #docplaster +// #docregion imports +import { Component } from '@angular/core'; + +import { CartService } from '../cart.service'; +// #enddocregion + +@Component({ + selector: 'app-shipping', + templateUrl: './shipping.component.html', + styleUrls: ['./shipping.component.css'] +}) +// #docregion props, ctor +export class ShippingComponent { + shippingCosts; +// #enddocregion props + + constructor(private cartService: CartService) { + this.shippingCosts = this.cartService.getShippingPrices(); + } +// #docregion props +} diff --git a/aio/content/examples/getting-started/src/app/top-bar/top-bar.component.1.html b/aio/content/examples/getting-started/src/app/top-bar/top-bar.component.1.html new file mode 100644 index 0000000000..82f53e5f1e --- /dev/null +++ b/aio/content/examples/getting-started/src/app/top-bar/top-bar.component.1.html @@ -0,0 +1,7 @@ + +

My Store

+
+ + + shopping_cartCheckout + diff --git a/aio/content/examples/getting-started/src/app/top-bar/top-bar.component.css b/aio/content/examples/getting-started/src/app/top-bar/top-bar.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/getting-started/src/app/top-bar/top-bar.component.html b/aio/content/examples/getting-started/src/app/top-bar/top-bar.component.html new file mode 100644 index 0000000000..f96b490311 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/top-bar/top-bar.component.html @@ -0,0 +1,7 @@ + +

My Store

+
+ + + shopping_cartCheckout + diff --git a/aio/content/examples/getting-started/src/app/top-bar/top-bar.component.ts b/aio/content/examples/getting-started/src/app/top-bar/top-bar.component.ts new file mode 100644 index 0000000000..12fe8c9488 --- /dev/null +++ b/aio/content/examples/getting-started/src/app/top-bar/top-bar.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-top-bar', + templateUrl: './top-bar.component.html', + styleUrls: ['./top-bar.component.css'] +}) +export class TopBarComponent { + +} diff --git a/aio/content/examples/getting-started/src/assets/shipping.json b/aio/content/examples/getting-started/src/assets/shipping.json new file mode 100644 index 0000000000..94db54c96d --- /dev/null +++ b/aio/content/examples/getting-started/src/assets/shipping.json @@ -0,0 +1,14 @@ +[ + { + "type": "Overnight", + "price": 25.99 + }, + { + "type": "2-Day", + "price": 9.99 + }, + { + "type": "Postal", + "price": 2.99 + } +] \ No newline at end of file diff --git a/aio/content/examples/getting-started/src/index.html b/aio/content/examples/getting-started/src/index.html new file mode 100644 index 0000000000..d7c3a0a6d6 --- /dev/null +++ b/aio/content/examples/getting-started/src/index.html @@ -0,0 +1,18 @@ + + + + + Angular Getting Started + + + + + + + + + + diff --git a/aio/content/examples/getting-started/src/main.ts b/aio/content/examples/getting-started/src/main.ts new file mode 100644 index 0000000000..c7b673cf44 --- /dev/null +++ b/aio/content/examples/getting-started/src/main.ts @@ -0,0 +1,12 @@ +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) + .catch(err => console.error(err)); diff --git a/aio/content/examples/getting-started/stackblitz.json b/aio/content/examples/getting-started/stackblitz.json new file mode 100644 index 0000000000..a1f0944d46 --- /dev/null +++ b/aio/content/examples/getting-started/stackblitz.json @@ -0,0 +1,9 @@ +{ + "description": "Getting Started", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[0-9].*" + ], + "tags": ["Angular", "getting started", "tutorial"] +} diff --git a/aio/content/getting-started/data.md b/aio/content/getting-started/data.md new file mode 100644 index 0000000000..56a2260967 --- /dev/null +++ b/aio/content/getting-started/data.md @@ -0,0 +1,393 @@ +# Managing Data + +At the end of [Routing](getting-started/routing "Getting Started: Routing"), the online store application has a product catalog with two views: a product list and product details. +Users can click on a product name from the list to see details in a new view, with a distinct URL (route). + +In this section, you'll create the shopping cart. You'll: +* Update the product details page to include a "Buy" button, which adds the current product to a list of products managed by a cart service. +* Add a cart component, which displays the items you added to your cart. +* Add a shipping component, which retrieves shipping prices for the items in the cart by using Angular's HttpClient to retrieve shipping data from a `.json` file. + +{@a services} +## Services + +Services are an integral part of Angular applications. In Angular, a service is an instance of a class that can be made available to any part of your application using Angular's [dependency injection system](guide/glossary#dependency-injection-di "dependency injection definition"). + +Services are the place where you share data between parts of your application. For the online store, the cart service is where you store your cart data and methods. + +{@a create-cart-service} +## Create the shopping cart service + +Up to this point, users can view product information, and simulate sharing and being notified about product changes. They cannot, however, buy products. + +In this section, you'll add a "Buy" button the product details page. +You'll also set up a cart service to store information about products in the cart. + +
+ +Later, in the [Forms](getting-started/forms "Getting Started: Forms") part of this tutorial, this cart service also will be accessed from the page where the user checks out. + +
+ +{@a generate-cart-service} +### Define a cart service + +1. Generate a cart service. + + 1. Right click on the `app` folder, choose `Angular Generator`, and choose `**Service**`. Name the new service `cart`. + + + + 1. If the generated `@Injectable()` decorator does not include the `{ providedIn: 'root' }` statement, then insert it as shown above. + +1. In the `CartService` class, define an `items` property to store the list (array) of the current products in the cart. + + + +1. Define methods to add items to the cart, return cart items, and clear the cart items: + + + + + + + +{@a product-details-use-cart-service} +### Use the cart service + +In this section, you'll update the product details component to use the cart service. +You'll add a "Buy" button to the product details view. +When the "Buy" button is clicked, you'll use the cart service to add the current product to the cart. + +1. Open `product-details.component.ts`. + +1. Set up the component to be able to use the cart service. + + 1. Import the cart service. + + + + + 1. Inject the cart service. + + + + + + +1. Define the `addToCart()` method, which adds the current product to the cart. + + The `addToCart()` method: + * Receives the current `product` + * Uses the cart service's `#addToCart()` method to add the product the cart + * Displays a message that the product has been added to the cart + + + +1. Update the product details template to have a "Buy" button that adds the current product to the cart. + + 1. Open `product-details.component.html`. + + 1. Add a button with the label "Buy", and bind the `click()` event to the `addToCart()` method: + + + + +1. To see the new "Buy" button, refresh the application and click on a product's name to display its details. + +
+ Display details for selected product with a Buy button +
+ + 1. Click the "Buy" button. The product is added to the stored list of items in the cart, and a message is displayed. + +
+ Display details for selected product with a Buy button +
+ + +## Create the cart page + +At this point, user can put items in the cart by clicking "Buy", but they can't yet see their cart. + +We'll create the cart page in two steps: + +1. Create a cart component and set up routing to the new component. At this point, the cart page will only have default text. +1. Display the cart items. + +### Set up the component + + To create the cart page, you being by following the same steps you did to create the product details component and to set up routing for the new component. + +1. Generate a cart component, named `cart`. + + Reminder: In the file list, right-click the `app` folder, choose `Angular Generator` and `Component`. + + + +1. Add routing (a URL pattern) for the cart component. + + Reminder: Open `app.module.ts` and add a route for the component `CartComponent`, with a `path` of `cart`: + + + + + + +1. To see the new cart component, click the "Checkout" button. You can see the "cart works!" default text, and the URL has the pattern `https://getting-started.stackblitz.io/cart`, where `getting-started.stackblitz.io` may be different for your StackBlitz project. + + (Note: The "Checkout" button that we provided in the top-bar component was already configured with a `routerLink` for `/cart`.) + +
+ Display cart page before customizing +
+ + +### Display the cart items + +Services can be used to share data across components: + +* The product details component already uses the cart service (`CartService`) to add products to the cart. +* In this section, you'll update the cart component to use the cart service to display the products in the cart. + + +1. Open `cart.component.ts`. + +1. Set up the component to be able to use the cart service. (This is the same way you set up the product details component to use the cart service, above.) + + 1. Import the `CartService` from the `cart.service.ts` file. + + + + + 1. Inject the `CartService` to manage cart information. + + + + +1. Define the `items` property to store the products in the cart. + + + + +1. Set the items using the cart service's `getItems()` method. (You defined this method [when you generated `cart.service.ts`](#generate-cart-service).) + + The resulting `CartComponent` class should look like this: + + + + +1. Update the template with a header ("Cart"), and use a `
` with an `*ngFor` to display each of the cart items with its name and price. + + The resulting `CartComponent` template should look like this: + + + + +1. Test your cart component. + + 1. Click on "My Store" to go to the product list page. + 1. Click on a product name to display its details. + 1. Click "Buy" to add the product to the cart. + 1. Click "Checkout" to see the cart. + 1. To add another product, click "My Store" to return to the product list. Repeat the steps above. + +
+ Cart page with products added +
+ + +
+ +StackBlitz tip: Any time the preview refreshes, the cart is cleared. If you make changes to the app, the page refreshes, and you'll need to buy products again to populate the cart. + +
+ + + +
+ +Learn more: See [Introduction to Services and Dependency Injection](guide/architecture-services "Architecture > Intro to Services and DI") for more information about services. + +
+ + + +## Retrieve shipping prices + + +Data returned from servers often takes the form of a stream. +Streams are useful because they make it easy to transform the data that is returned, and to make modifications to the way data is requested. +The Angular HTTP client (`HttpClient`) is a built-in way to fetch data from external APIs and provide them to your application as a stream. + +In this section, you'll use the HTTP client to retrieve shipping prices from an external file. + +### Predefined shipping data + +For the purpose of this Getting Started, we have provided shipping data in `assets/shipping.json`. +You'll use this data to add shipping prices for items in the cart. + + + + + +### Enable HttpClient for app + +Before you can use Angular's HTTP client, you must set up your app to use `HttpClientModule`. + +Angular's `HttpClientModule` registers the providers needed to use a single instance of the `HttpClient` service throughout your app. +The `HttpClient` service is what you inject into your services to fetch data and interact with external APIs and resources. + +1. Open `app.module.ts`. + + This file contains imports and functionality that is available to the entire app. + +1. Import `HttpClientModule` from the `@angular/common/http` package. + + + + +1. Add `HttpClientModule` to the `imports` array of the app module (`@NgModule`). + + This registers Angular's `HttpClient` providers globally. + + + + + + + + +### Enable HttpClient for cart service + +1. Open `cart.service.ts`. + +1. Import `HttpClient` from the `@angular/common/http` package. + + + + +1. Inject `HttpClient` into the constructor of the `CartService` component class: + + + + + +### Define the get() method + +As you've seen, multiple components can leverage the same service. +Later in this tutorial, the shipping component will use the cart service to retrieve shipping data via HTTP from the `shipping.json` file. +Here you'll define the `get()` method that will be used. + +1. Continue working in `cart.service.ts`. + +1. Below the `clearCart()` method, define a new `getShippingPrices()` method that uses the `HttpClient#get()` method to retrieve the shipping data (types and prices). + + + + +
+ +Learn more: See the [HttpClient guide](guide/http "HttpClient guide") for more information about Angular's HttpClient. + +
+ + + + +## Define the shipping page + +Now that your app can retrieve shipping data, you'll create a shipping component and associated template. + +1. Generate a new component named `shipping`. + + Reminder: In the file list, right-click the `app` folder, choose `Angular Generator` and `Component`. + + + +1. In `app.module.ts`, add a route for shipping. Specify a `path` of `shipping` and a component of `ShippingComponent`. + + + + The new shipping component isn't hooked into any other component yet, but you can see it in the preview pane by entering the URL specified by its route. The URL has the pattern: `https://getting-started.stackblitz.io/shipping` where the `getting-started.stackblitz.io` part may be different for your StackBlitz project. + +1. Modify the shipping component so it uses the cart service to retrieve shipping data via HTTP from the `shipping.json` file. + + 1. Import the cart service. + + + + 1. Define a `shippingCosts` property. + + + + 1. Inject the cart service into the `ShippingComponent` class: + + ``` + constructor( + private cartService: CartService + ) { } + ``` + + + + 1. Set the `shippingCosts` property using the `getShippingPrices()` method from cart service. + + + +1. Update the shipping component's template to display the shipping types and prices using async pipe: + + + + + +1. Add a link from the cart page to the shipping page: + + + +1. Test your shipping prices feature: + + Click on the "Checkout" button to see the updated cart. (Remember that changing the app causes the preview to refresh, which empties the cart.) + +
+ Cart with link to shipping prices +
+ + Click on the link to navigate to the shipping prices. + +
+ Display shipping prices +
+ + +## Next steps + +Congratulations! You have an online store application with a product catalog and shopping cart. You also have the ability to look up and display shipping prices. + +To continue exploring Angular, choose either of the following options: +* [Continue to the "Forms" section](getting-started/forms "Getting Started: Forms") to finish the app by adding the shopping cart page and a form-based checkout feature. You'll create a form to collect user information as part of checkout. +* [Skip ahead to the "Deployment" section](getting-started/deployment "Getting Started: Deployment") to deploy your app to Firebase or move to local development. + + diff --git a/aio/content/getting-started/deployment.md b/aio/content/getting-started/deployment.md new file mode 100644 index 0000000000..0f60f03fe7 --- /dev/null +++ b/aio/content/getting-started/deployment.md @@ -0,0 +1,92 @@ +# Deployment + + +To deploy your application, you have to compile it, and then host the JavaScript, CSS, and HTML on a web server. Built Angular applications are very portable and can live in any environment or served by any technology, such as Node, Java, .NET, PHP, and many others. + +
+ + +Whether you came here directly from [Your First App](getting-started "Getting Started: Your First App"), or completed the entire online store application through the [Routing](getting-started/routing "Getting Started: Routing"), [Managing Data](getting-started/data "Getting Started: Managing Data"), and [Forms](getting-started/forms "Getting Started: Forms") sections, you have an application that you can deploy by following the instructions in this section. + + +
+ + + +## Deploying from StackBlitz + +StackBlitz allows you to publish your Angular app directly to Firebase from your project. The steps below outline how to deploy it quickly without setting up your own hosting environment. + +1. In your StackBlitz project, in the left menu bar, click the `Firebase` icon. +1. If you don’t have a `Firebase` account, visit the [Firebase](https://firebase.google.com/ "Firebase web site") to sign up for a free hosting account. +1. Click the `Sign into Google` button and follow the prompts to give `StackBlitz` access your `Firebase` projects +1. Select the `project` where you wish to deploy your application. +1. Click the `Deploy` button to deploy your application. +1. After the deployment completes, click the `Open live site` link to view your app live. + +## Building locally + +To build your application locally, you will need to download the source code from your StackBlitz project. Click the `Download Project` icon in the left menu across from `Project` to download your files. + +Once you have the source code downloaded and unzipped, use the [Angular Console](https://angularconsole.com "Angular Console web site") to serve the application, or you install Node and have the Angular CLI installed. + +From the terminal, install the Angular CLI globally with: + +```sh +npm install -g @angular/cli +``` + +This will install the command `ng` into your system, which is the command you use to create new workspaces, new projects, serve your application during development, or produce builds that can be shared or distributed. + +Create a new Angular CLI workspace using the [`ng new`](cli/new "CLI ng new command reference") command: + +```sh +ng new my-project-name +``` + +From there you replace the `/src` folder with the one from your `StackBlitz` download, and then perform a build. + +```sh +ng build --prod +``` + +This will produce the files that you need to deploy. + +#### Hosting the built project + +The files in the `dist/my-project-name` folder are static and can be hosted on any web server capable of serving files (node, Java, .NET) or any backend (Firebase, Google Cloud, App Engine, others). + +### Hosting an Angular app on Firebase + +One of the easiest ways to get your site live is to host it using Firebase. + +1. Sign up for a firebase account on [Firebase](https://firebase.google.com/ "Firebase web site"). +1. Create a new project, giving it any name you like. +1. Install the `firebase-tools` CLI that will handle your deployment using `npm install -g firebase-tools`. +1. Connect your CLI to your Firebase account and initialize the connection to your project using `firebase login` and `firebase init`. +1. Follow the prompts to select the `Firebase` project you creating for hosting. +1. Deploy your application with `firebase deploy` because StackBlitz has created a `firebase.json` that tells Firebase how to serve your app. +1. Once deployed, visit https://your-firebase-project-name.firebaseapp.com to see it live! + +### Hosting an Angular app anywhere else + +To host an Angular app on another web host, you'll need to upload or send the files to the host. +Because you are building a Single Page Application, you'll also need to make sure you redirect any invalid URLs to your `index.html` file. +Learn more about development and distribution of your application in the [Building & Serving](guide/build "Building and Serving Angular Apps") and [Deployment](guide/deployment "Deployment guide") guides. + +## Join our community + +You are now an Angular developer! [Share this moment](https://twitter.com/intent/tweet?url=https://next.angular.io/getting-started&text=I%20just%20finished%20the%20Angular%20Getting%20Started%20Tutorial "Angular on Twitter"), tell us what you thought of this Getting Started, or submit [suggestions for future editions](https://github.com/angular/angular/issues/new/choose "Angular GitHub repository new issue form"). + +Angular offers many more capabilities, and you now have a foundation that empowers you to build an application and explore those other capabilities: + +* Angular provides advanced capabilities for mobile apps, animation, internationalization, server-side rendering, and more. +* [Angular Material](https://material.angular.io/ "Angular Material web site") offers an extensive library of Material Design components. +* [Angular Protractor](https://protractor.angular.io/ "Angular Protractor web site") offers an end-to-end testing framework for Angular apps. +* Angular also has an extensive [network of 3rd-party tools and libraries](https://angular.io/resources "Angular resources list"). + +Keep current by following the [Angular blog](https://blog.angular.io/ "Angular blog"). + + + + diff --git a/aio/content/getting-started/forms.md b/aio/content/getting-started/forms.md new file mode 100644 index 0000000000..76f6e2652a --- /dev/null +++ b/aio/content/getting-started/forms.md @@ -0,0 +1,162 @@ +# Forms + +At the end of [Managing Data](getting-started/data "Getting Started: Managing Data"), the online store application has a product catalog and a shopping cart. + +In this section, you'll finish the app by adding a form-based checkout feature. You'll create a form to collect user information as part of checkout. + +## Forms in Angular + +Forms in Angular take the standard capabilities of the HTML based forms and add an orchestration layer to help with creating custom form controls, and to supply great validation experiences. There are two parts to an Angular Reactive form, the objects that live in the component to store and manage the form, and the visualization of the form that lives in the template. + +## Define the checkout form model + +First, you'll set up the checkout form model. The form model is the source of truth for the status of the form and is defined in the component class. + +1. Open `cart.component.ts`. + +1. Angular's `FormBuilder` service provides convenient methods for generating controls. As with the other services you've used, you need to import and inject the service before you can use it: + + 1. Import the `FormBuilder` service from the `@angular/forms` package. + + + + + The `FormBuilder` service is provided by the `ReactiveFormsModule`, which is already defined in the `AppModule` you modified previously (in `app.module.ts`). + + 1. Inject the `FormBuilder` service. + + ``` + export class CartComponent { + items; + + constructor( + private cartService: CartService, + private formBuilder: FormBuilder, + ) { } + } + ``` + + + +1. In the `CartComponent` class, define the `checkoutForm` property to store the form model. + + ``` + export class CartComponent { + items; + checkoutForm; + } + ``` + + +1. During checkout, the app will prompt the user for a name and address. So that you can gather that information later, set the `checkoutForm` property with a form model containing `name` and `address` fields, using the `FormBuilder#group()` method. + + ``` + export class CartComponent { + items; + checkoutForm; + + constructor( + private formBuilder: FormBuilder, + private cartService: CartService + ) { + this.items = this.cartService.getItems(); + + this.checkoutForm = this.formBuilder.group({ + name: '', + address: '' + }); + } + ``` + + + + +1. For the checkout process, users need to be able to submit the form data (their name and address). When the order is submitted, the form should reset and the cart should clear. + + In `cart.component.ts`, define an `onSubmit()` method to process the form. Use the `CartService#clearCart()` method to empty the cart items and reset the form after it is submitted. (In a real-world app, this method also would submit the data to an external server.) + + The entire cart component is shown below: + + + + +The form model is defined in the component class. To reflect the model in the view, you'll need a checkout form. + +## Create the checkout form + +Next, you'll add a checkout form at the bottom of the "Cart" page. + +1. Open `cart.component.html`. + +1. At the bottom of the template, add an empty HTML form to capture user information. + +1. Use a `formGroup` property binding to bind the `checkoutForm` to the `form` tag in the template. Also include a "Purchase" button to submit the form. + + ``` +
+ + + +
+ ``` + + + + + +1. On the `form` tag, use an `ngSubmit` event binding to listen for the form submission and call the `onSubmit()` method with the `checkoutForm` value. + + ``` +
+ ... +
+ ``` + + + +1. Add input fields for `name` and `address`. Use the `formControlName` attribute binding to bind the `checkoutForm` form controls for `name` and `address` to their input fields. The final complete component is shown below: + + + + +After putting a few items in the cart, users can now review their items, enter name and address, and submit their purchase: + +
+ Cart page with checkout form +
+ + +## Next steps + +Congratulations! You have a complete online store application with a product catalog, a shopping cart, and a checkout function. + +[Continue to the "Deployment" section](getting-started/deployment "Getting Started: Deployment") to deploy your app to Firebase or move to local development. + diff --git a/aio/content/getting-started/index.md b/aio/content/getting-started/index.md new file mode 100644 index 0000000000..8a9fd08ddd --- /dev/null +++ b/aio/content/getting-started/index.md @@ -0,0 +1,328 @@ +# Getting Started with Angular: Your First App + +Welcome to Angular! + +This tutorial introduces you to the essentials of Angular. +It leverages what you already know about HTML and JavaScript—plus some useful Angular features—to build a simple online store application, with a catalog, shopping cart, and check-out form. +You don't need to install anything: you'll build the app using the [StackBlitz](https://stackblitz.com/ "StackBlitz web site") online development environment. + + +
+
New to web development?
+ + +You'll find many resources to compliment the Angular docs. Mozilla's MDN docs include both [HTML](https://developer.mozilla.org/en-US/docs/Learn/HTML "Learning HTML: Guides and tutorials") and [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript "JavaScript") introductions. [TypeScript's docs](https://www.typescriptlang.org/docs/home.html "TypeScript documentation") include a 5-minute tutorial. Various online course platforms, such as [Udemy](http://www.udemy.com "Udemy online courses") and [Codeacademy](https://www.codecademy.com/ "Codeacademy online courses"), also cover web development basics. + + +
+ + + +{@a new-project} +## Create a new project + +

+Click here to create new project in StackBlitz. +

+ +StackBlitz creates a starter Angular app. +We've seeded this particular app with a top bar—containing the store name and checkout icon—and the title for a product list. + + + +
+ Starter online store app +
+ + +
+
StackBlitz tips
+ +* Log into StackBlitz, so you can save and resume your work. If you have a GitHub account, you can log into StackBlitz with that account. +* To copy a code example from this tutorial, click the icon at the top right of the code example box, and then paste the code snippet from the clipboard into StackBlitz. +* If the StackBlitz preview pane isn't showing what you expect, save and then click the refresh button. +* StackBlitz is continually improving, so there may be slight differences in generated code, but the app's behavior will be the same. + +
+ +{@a template-syntax} +## Template syntax + + + +Angular's template syntax extends HTML and JavaScript. +In this section, you'll learn about template syntax by enhancing the "Products" area. + +(So that you can focus on the template syntax, the following steps use predefined product data and methods from the `product-list.component.ts` file.) + +1. In the `product-list` folder, open the template file `product-list.component.html`. + +1. Modify the product list template to display a list of product names. + + 1. We want each product in the list to be displayed the same way, one after the other on the page. To iterate over the predefined list of products, use the `*ngFor` directive. Put the `*ngFor` directive on a `
`, as shown below: + + + + + `*ngFor` causes the `
` to be repeated for each product in the list. + +
+ `*ngFor` is a "structural directive". Structural directives shape or reshape the DOM's structure, typically by adding, removing, and manipulating the elements to which they are attached. Any directive with an * is a structural directive. +
+ + 1. To display the names of the products, use the interpolation syntax {{ }}. Interpolation renders a property's value as text. Inside the `
`, add an `

` heading to display the interpolation of the product's name property: + + + + + The preview pane immediately updates to display the name of each product in the list. + +
+ Product names added to list +
+ +1. In the final app, each product name will be a link to product details. Add the anchor now, and set the anchor's title to be the product's name by using the property binding [ ] syntax, as shown below: + + + + + + + In the preview pane, hover over the displayed product name to see the bound name property value. They are the same. Interpolation {{ }} lets you render the property value as text; property binding [ ] lets you use the property value in a template expression. + +
+ Product name anchor text is product name property +
+ + +1. Add the product descriptions. On the paragraph tag, use an `*ngIf` directive so that the paragraph element is only created if the current product has a description. + + + + + The app now displays the name and description of each product in the list, as shown below. Notice that the final product does not have a description paragraph at all. Because the product's description property is empty, the paragraph element—including the word "Description"—is not created. + +
+ Product descriptions added to list +
+ +1. Add a button so users can share a product with friends. Bind the button's `click` event to the `share()` event that we defined for you (in `product-list.component.ts`). Event binding is done by using ( ) around the event, as shown below: + + + + + Each product now has a "Share" button: + +
+ Share button added for each product +
+ + Test the "Share" button: + +
+ Alert box indicating product has been shared +
+ +The app now has a product list and sharing feature. +In the process, you've learned to use five common features of Angular's template syntax: +* `*ngFor` +* `*ngIf` +* Interpolation {{ }} +* Property binding [ ] +* Event binding ( ) + + +
+ +Learn more: See the [Template Syntax guide](guide/template-syntax "Template Syntax") for information about the full capabilities of Angular's template syntax. + +
+ + +{@a components} +## Components + +*Components* are the building blocks of Angular apps. +You've already been working with the product list component. + +A component is comprised of three things: +* **A component class,** which handles data and functionality. In the previous section, the product data and the `share()` method were defined for you in the component class. +* **An HTML template,** which determines what is presented to the user. In the previous section, you modified the product list's HTML template to display the name, description, and a "Share" button for each product. +* **Component-specific styles** that define the look and feel. The product list does not define any styles. + + + +An Angular application is composed of a tree of components, in which each Angular component has a specific purpose and responsibility. + +Currently, our app has three components: + +
+ Online store with three components +
+ +* `app-root` (orange box) is the application shell. This is first component to load, and the parent of all other components. You can think of it as the base page. +* `app-top-bar` (blue background) is the store name and checkout button. +* `app-product-list` (purple box) is the product list that you modified in the previous section. + +In the next section, you'll expand the app's capabilities by adding new component for a product alert. You'll add it as a child of the product list component. + + +
+ +Learn more: See [Introduction to Components](guide/architecture-components "Architecture > Introduction to Components") for more information about components and how they interact with templates. + +
+ + +{@a input} +## Input + +Currently, the product list displays the name and description for each product. +You might have noticed that product list component also defines a `products` property that contains imported data for each product. (See the `products` array in `products.ts`.) + +We're going to create a new alert feature. The alert feature will take a product as input. It will then check the product's price, and, if the price is greater than $700, it will display a "Notify Me" button that lets users sign up for notifications when the product goes on sale. + +1. Create a new product alerts component. + + 1. Right click on the `app` folder and use the `Angular Generator` to generate a new component named `product-alerts`. + +
+ StackBlitz command to generate component +
+ + The generator creates starter files for all three parts of the component: + * `product-alerts.component.ts` + * `product-alerts.component.html` + * `product-alerts.component.css` + +1. Open `product-alerts.component.ts`. + + + + 1. Notice the `@Component` decorator. This indicates that the following class is a component. It provides metadata about the component, including its templates, styles, and a selector. + + * The `selector` is used to identify the component. The selector is the name you give the Angular component when it is rendered as an HTML element on the page. By convention, Angular component selectors begin with the prefix `app-`, followed by the component name. + + * The template and style filenames. These reference the other two files generated for you. + + 1. The component definition also includes an exported class (`ProductAlertsComponent`), which handles functionality for the component. + +1. Set up the new product alerts component to receive a product as input: + + 1. Import `Input` from `@angular/core`. + + + + 1. In the `ProductAlertsComponent` class definition, define a property named `product` with an `@Input` decorator. The `@Input` decorator indicates that the property value will be passed in from the component's parent (in this case, the product list component). + + + +1. Define the view for the new product alert component. + + Open the `product-alerts.component.html` template and replace the placeholder paragraph with a "Notify Me" button that appears if the product price is over $700. + + + +1. Display the new product alert component as part of (a child of) the product list. + + 1. Open `product-list.component.html`. + + 1. To include the new component, use its selector (`app-product-alert`) as you would an HTML element. + + 1. Pass the current product as input to the component using property binding. + + + +The new product alert component takes a product as input from the product list. With that input, the it shows or hides the "Notify Me" button, based on the price of the product. The Phone XL price is over $700, so the "Notify Me" button appears on that product. + +
+ Product alert button added to products over $700 +
+ + +
+ +Learn more: See [Component Interaction](guide/component-interaction "Components & Templates > Component Interaction") for more information about passing data from a parent to child component, intercepting and acting upon a value from the parent, and detecting and acting on changes to input property values. + +
+ + +{@a output} +## Output + +The "Notify Me" button doesn't do anything yet. In this section, you'll set up the product alert component so that it emits an event up to the product list component when the user clicks "Notify Me". You'll define the notification behavior in the product list component. + +1. Open `product-alerts.component.ts`. + +1. Import `Output` and `EventEmitter` from `@angular/core`: + + + +1. In the component class, define a property named `notify` with an `@Output` decorator and an instance of event emitter. This makes it possible for the product alert component to emit an event when the value of the notify property changes. + + + +1. In the product alert template (`product-alerts.component.html`), update the "Notify Me" button with an event binding to call the `notify.emit()` method. + + + +1. Next, define the behavior that should happen when the button is clicked. Recall that it's the parent (product list component)—not the product alerts component—that's going to take the action. In the `product-list.component.ts` file, define an `onNotify()` method, similar to the `share()` method: + + + +1. Finally, update the product list component to receive output from the product alerts component. + + In `product-list.component.html`, bind the `app-product-alerts` component (which is what displays the "Notify Me" button) to the `onNotify()` method of the product list component. + + + +1. Try out the "Notify Me" button: + +
+ Product alert notification confirmation dialog +
+ + +
+ +Learn more: See [Component Interaction](guide/component-interaction "Components & Templates > Component Interaction") for more information about listening for events from child components, reading child properties or invoking child methods, and using a service for bi-directional communication within the family. + +
+ + +{@a next-steps} +## Next steps + +Congratulations! You've completed your first Angular app! + +You have a basic online store catalog, with a product list, "Share" button, and "Notify Me" button. +You've learned about the foundation of Angular: components and template syntax. +You've also learned how the component class and template interact, and how components communicate with each other. + +To continue exploring Angular, choose either of the following options: +* [Continue to the "Routing" section](getting-started/routing "Getting Started: Routing") to create a product details page that can be accessed by clicking a product name and that has its own URL pattern. +* [Skip ahead to the "Deployment" section](getting-started/deployment "Getting Started: Deployment") to deploy your app to Firebase or move to local development. + diff --git a/aio/content/getting-started/routing.md b/aio/content/getting-started/routing.md new file mode 100644 index 0000000000..6cf286de8e --- /dev/null +++ b/aio/content/getting-started/routing.md @@ -0,0 +1,126 @@ +# Routing + +At the end of [Your First App](getting-started "Getting Started: Your First App"), the online store application has a basic product catalog. +The app doesn't have any variable states or navigation. +There is one URL, and that URL always displays the "My Store" page with a list of products and their descriptions. + +In this section, you'll extend the app to display full product details in separate pages, with their own URLs. + +To do this, you'll use the Angular *router*. +The Angular [router](guide/glossary#router "router definition") enables you to show different components and data to the user based on where the user is in the application. +The router enables navigation from one view to the next as users perform application tasks: + +* Enter a URL in the address bar, and the browser navigates to a corresponding page. +* Click links on the page, and the browser navigates to a new page. +* Click the browser's back and forward buttons, and the browser navigates backward and forward through the history of pages you've seen. + + +## Registering a route + +The app is already set up to use the Angular router and to use routing to navigate to the product list component you modified earlier. Let's define a route to show individual product details. + +1. Generate a new component for product details. Give the component the name `product-details`. + + Reminder: In the file list, right-click the `app` folder, choose `Angular Generator` and `Component`. + +1. In `app.module.ts`, add a route for product details, with a `path` of `products/:productId` and `ProductDetailsComponent` for the `component`. + + + + + A route associates one or more URL paths with a component. + +1. Define a link using the `RouterLink` directive. The `routerLink` defines how the user navigates to the route (or URL) declaratively + in the component template. + + We want the user to click a product name to display the details for that product. + + 1. Open `product-list.component.html`. + + 1. Update the `*ngFor` directive to assign each index in the `products` array to the `productId` variable when iterating over the list. + + 1. Modify the product name anchor to include a `routerLink`. + + + + + + + The RouterLink directive give the router control over the anchor element. In this case, the route (URL) contains one fixed segment (`/products`) and the final segment is variable, inserting the id property of the current product. For example, the URL for a product with an `id` of 1 will be similar to `https://getting-started-myfork.stackblitz.io.products/1`. + +1. Test the router by clicking a product name. The app displays the product details component, which currently always says "product-details works!" (We'll fix this in the next section.) + + Notice that the URL in the preview window changes. The final segment is `products/1`. + +
+ Product details page with updated URL +
+ + + +## Using route information + +The product details component handles the display of each product. The Angular Router displays components based on the browser's URL and your defined routes. You'll use the Angular Router to combine the `products` data and route information to display the specific details for each product. + +1. Open `product-details.component.ts` + +1. Arrange to use product data from an external file. + + 1. Import `ActivatedRoute` from the `@angular/router` package, and the `products` array from `../products`. + + + + + 1. Define the `product` property and inject the `ActivatedRoute` into the constructor. + + + + + The `ActivatedRoute` is specific to each routed component loaded by the Angular Router. It contains information about the + route, its parameters, and additional data associated with the route. + + + +1. In the `ngOnInit()` method, _subscribe_ to route params and fetch the product based on the `productId`. + + + + + The route parameters correspond to the path variables defined in the route. The `productId` is provided from + the URL that was matched to the route. You use the `productId` to display the details for each unique product. + +1. Update the template to display product details information inside an `*ngIf`. + + + + +Now, when the user clicks on a name in the product list, the router navigates you to the distinct URL for the product, swaps out the product list component for the product details component, and displays the product details. + +
+ Product details page with updated URL and full details displayed +
+ + + +
+ +Learn more: See [Routing & Navigation](guide/router "Routing & Navigation") for more information about the Angular router. + +
+ + +## Next steps + +Congratulations! You have integrated routing into your online store. + +* Products are linked from the product list page to individual products +* Users can click on a product name from the list to see details in a new view, with a distinct URL (route) + +To continue exploring Angular, choose either of the following options: +* [Continue to the "Managing Data" section](getting-started/data "Getting Started: Managing Data") to add the shopping cart feature, using a service to manage the cart data and using HTTP to retrieve external data for shipping prices. +* [Skip ahead to the Deployment section](getting-started/deployment "Getting Started: Deployment") to deploy your app to Firebase or move to local development. + diff --git a/aio/content/guide/prerequisites-setup.md b/aio/content/guide/prerequisites-setup.md new file mode 100644 index 0000000000..2151f2991f --- /dev/null +++ b/aio/content/guide/prerequisites-setup.md @@ -0,0 +1,148 @@ +# Prerequisites and Setup / Creating a workspace / Local development / Local environment + +This guide describes how to get started with local development. + +It includes: +* Prerequisites +* How to install the Angular CLI +* How to create a workspace and initial app project +* How to serve an app project locally +* Additional resources + +{@a devenv} +{@a prerequisites} +## Prerequisites + + +{@a nodejs} +### Node.js + +Angular requires `Node.js` version 8.x or 10.x. + +* To check your version, run `node -v` in a terminal/console window. + +* To get `Node.js`, go to [nodejs.org](https://nodejs.org "Nodejs.org"). + +{@a npm} +### npm package manager: npm or yarn + +Angular, the Angular CLI, and Angular apps depend on features and functionality provided by libraries that are available as [npm packages](https://docs.npmjs.com/getting-started/what-is-npm). To download and install npm packages, you must have an npm package manager. + +The following package managers have been verified with Angular: + +* The [npm client](https://docs.npmjs.com/cli/npm) command line interface, which is installed with `Node.js` by default. To check if you have the npm client installed, run `npm -v` in a terminal/console window. Most of the documentation for Angular assumes the npm client. + +* The [yarn client](https://yarnpkg.com/) command line interface. + +{@a install-cli} + +## Step 1: Install the Angular CLI + +You use the Angular CLI +to create projects, generate application and library code, and perform a variety of ongoing development tasks such as testing, bundling, and deployment. + +Install the Angular CLI globally. + +To install the CLI using `npm`, open a terminal/console window and enter the following command: + + + + npm install -g @angular/cli + + + + + +{@a create-proj} + +## Step 2: Create a workspace and initial application + +You develop apps in the context of an Angular [**workspace**](guide/glossary#workspace). A workspace contains the files for one or more [**projects**](guide/glossary/#project). A project is the set of files that comprise an app, a library, or end-to-end (e2e) tests. + +To create a new workspace and initial app project: + +1. Run the CLI command `ng new` and provide the name `my-app`, as shown here: + + + ng new my-app + + + +2. The `ng new` command prompts you for information about features to include in the initial app project. Accept the defaults by pressing the Enter or Return key. + +The Angular CLI installs the necessary Angular npm packages and other dependencies. This can take a few minutes. + +It also creates the following workspace and starter project files: + +* A new workspace, with a root folder named `my-app` +* An initial skeleton app project, also called `my-app` (in the `src` subfolder) +* An end-to-end test project (in the `e2e` subfolder) +* Related configuration files + +The initial app project contains a simple Welcome app, ready to run. + +{@a serve} + +## Step 3: Serve the application + +Angular includes a server, so that you can easily build and serve your app locally. + +1. Go to the workspace folder (`my-app`). + +1. Launch the server by using the CLI command `ng serve`, with the `--open` option. + + + cd my-app + ng serve --open + + +The `ng serve` command launches the server, watches your files, +and rebuilds the app as you make changes to those files. + +The `--open` (or just `-o`) option automatically opens your browser +to `http://localhost:4200/`. + +Your app greets you with a message: + + +
+ Welcome to my-app! +
+ + + + +## Additional resources + +If you're new to Angular: + +* The [Getting Started](tutorial/) provides hands-on learning. It walks you through the steps to build your first app in an online environment and then deploy that app to your local system. While building a basic catalog and shopping cart app, you'll be introduced to components (the building blocks of Angular), Angular's HTML template syntax, basic display and navigation between views, using services and external data, and scaling and tuning your app. + +* The [Tour of Heroes tutorial](tutorial "Tour of Heroes tutorial") provides additional hands-on learning. It walks you through the steps to build an app that helps a staffing agency manage a group of superhero employees. All of the steps are done locally. + + +* The [Architecture guide](guide/architecture "Architecture guide") describes key concepts such as modules, components, services, and dependency injection (DI). It provides a foundation for more in-depth guides about specific Angular concepts and features. + +After the Tutorial and Architecture guide, you'll be ready to continue exploring Angular on your own through the other guides and references in this documentation set, focusing on the features most important for your apps. + + + + +## Related technologies and tools + +Angular assumes specific versions of many related technologies and tools, such as TypeScript, Karma, Protractor, tsickle, zone.js. + +The `package.json` is organized into two groups of packages: + +* [Dependencies](guide/npm-packages#dependencies) are essential to *running* applications. +* [DevDependencies](guide/npm-packages#dev-dependencies) are only necessary to *develop* applications. + +These packages are described in more detail in [Workspace dependencies](guide/npm-packages). + + + +{@a others} +## Managing different development environments + +If you already have projects running on your machine that use other versions of Node.js and npm, consider using [nvm](https://github.com/creationix/nvm) on Mac or Linux, or [nvm-windows](https://github.com/coreybutler/nvm-windows) on Windows, to manage the multiple versions of Node.js and npm. + diff --git a/aio/content/guide/quickstart.md b/aio/content/guide/quickstart.md index 34d9ee3a8a..be5dbd2bd6 100644 --- a/aio/content/guide/quickstart.md +++ b/aio/content/guide/quickstart.md @@ -1,14 +1,22 @@ -# Getting started +# QuickStart to Local Environment Setup and Development Welcome to Angular! Angular helps you build modern applications for the web, mobile, or desktop. -This guide shows you how to build and run a simple Angular -app. You'll use the [Angular CLI tool](cli "CLI command reference") to accelerate development, -while adhering to the [Style Guide](guide/styleguide "Angular style guide") recommendations that -benefit _every_ Angular project. +
+
Getting Started - Stackblitz
+ + +We recently introduced a [**new Getting Started**](getting-started) that leverages the [StackBlitz](https://stackblitz.com/) online development environment. +We recommend the new Getting Started for anyone who wants to quickly learn the essentials of Angular, in the context of building a basic online store app. + + +
+ + +This guide shows you how to build and run a simple Angular app in your local development environment using the [Angular CLI tool](cli "CLI command reference"). +At the end of this guide—as part of final code review—there is a link to download a copy of the final application code, so that you can compare your work, validate your local setup, or just explore a simple Angular app. This guide takes less than 30 minutes to complete. -At the end of this guide—as part of final code review—there is a link to download a copy of the final application code. (If you don't execute the commands in this guide, you can still download the final application code.) {@a devenv} diff --git a/aio/content/images/guide/getting-started/app-components.png b/aio/content/images/guide/getting-started/app-components.png new file mode 100644 index 0000000000..d30a8fb6fb Binary files /dev/null and b/aio/content/images/guide/getting-started/app-components.png differ diff --git a/aio/content/images/guide/getting-started/buy-alert.png b/aio/content/images/guide/getting-started/buy-alert.png new file mode 100644 index 0000000000..f97bbbc33a Binary files /dev/null and b/aio/content/images/guide/getting-started/buy-alert.png differ diff --git a/aio/content/images/guide/getting-started/cart-empty-with-shipping-prices.png b/aio/content/images/guide/getting-started/cart-empty-with-shipping-prices.png new file mode 100644 index 0000000000..e65332d7b9 Binary files /dev/null and b/aio/content/images/guide/getting-started/cart-empty-with-shipping-prices.png differ diff --git a/aio/content/images/guide/getting-started/cart-page-checkout-form-empty.png b/aio/content/images/guide/getting-started/cart-page-checkout-form-empty.png new file mode 100644 index 0000000000..6d094aa091 Binary files /dev/null and b/aio/content/images/guide/getting-started/cart-page-checkout-form-empty.png differ diff --git a/aio/content/images/guide/getting-started/cart-page-full.png b/aio/content/images/guide/getting-started/cart-page-full.png new file mode 100644 index 0000000000..578124e1b2 Binary files /dev/null and b/aio/content/images/guide/getting-started/cart-page-full.png differ diff --git a/aio/content/images/guide/getting-started/cart-page-no-items.png b/aio/content/images/guide/getting-started/cart-page-no-items.png new file mode 100644 index 0000000000..304eda983a Binary files /dev/null and b/aio/content/images/guide/getting-started/cart-page-no-items.png differ diff --git a/aio/content/images/guide/getting-started/cart-with-items-and-form.png b/aio/content/images/guide/getting-started/cart-with-items-and-form.png new file mode 100644 index 0000000000..03ff0b87d4 Binary files /dev/null and b/aio/content/images/guide/getting-started/cart-with-items-and-form.png differ diff --git a/aio/content/images/guide/getting-started/cart-with-shipping-link.png b/aio/content/images/guide/getting-started/cart-with-shipping-link.png new file mode 100644 index 0000000000..23c7846277 Binary files /dev/null and b/aio/content/images/guide/getting-started/cart-with-shipping-link.png differ diff --git a/aio/content/images/guide/getting-started/cart-works.png b/aio/content/images/guide/getting-started/cart-works.png new file mode 100644 index 0000000000..c5ec25a619 Binary files /dev/null and b/aio/content/images/guide/getting-started/cart-works.png differ diff --git a/aio/content/images/guide/getting-started/generate-component.png b/aio/content/images/guide/getting-started/generate-component.png new file mode 100644 index 0000000000..bbb8ec60f6 Binary files /dev/null and b/aio/content/images/guide/getting-started/generate-component.png differ diff --git a/aio/content/images/guide/getting-started/new-app.png b/aio/content/images/guide/getting-started/new-app.png new file mode 100644 index 0000000000..128ee6fdf9 Binary files /dev/null and b/aio/content/images/guide/getting-started/new-app.png differ diff --git a/aio/content/images/guide/getting-started/new-project.png b/aio/content/images/guide/getting-started/new-project.png new file mode 100644 index 0000000000..36878c25c7 Binary files /dev/null and b/aio/content/images/guide/getting-started/new-project.png differ diff --git a/aio/content/images/guide/getting-started/product-alert-button.png b/aio/content/images/guide/getting-started/product-alert-button.png new file mode 100644 index 0000000000..37a787286f Binary files /dev/null and b/aio/content/images/guide/getting-started/product-alert-button.png differ diff --git a/aio/content/images/guide/getting-started/product-alert-notification.png b/aio/content/images/guide/getting-started/product-alert-notification.png new file mode 100644 index 0000000000..7cb6a66630 Binary files /dev/null and b/aio/content/images/guide/getting-started/product-alert-notification.png differ diff --git a/aio/content/images/guide/getting-started/product-details-buy.png b/aio/content/images/guide/getting-started/product-details-buy.png new file mode 100644 index 0000000000..4d22c98a27 Binary files /dev/null and b/aio/content/images/guide/getting-started/product-details-buy.png differ diff --git a/aio/content/images/guide/getting-started/product-details-routed.png b/aio/content/images/guide/getting-started/product-details-routed.png new file mode 100644 index 0000000000..bfd9219c58 Binary files /dev/null and b/aio/content/images/guide/getting-started/product-details-routed.png differ diff --git a/aio/content/images/guide/getting-started/product-details-works.png b/aio/content/images/guide/getting-started/product-details-works.png new file mode 100644 index 0000000000..05cf0f9574 Binary files /dev/null and b/aio/content/images/guide/getting-started/product-details-works.png differ diff --git a/aio/content/images/guide/getting-started/product-details.png b/aio/content/images/guide/getting-started/product-details.png new file mode 100644 index 0000000000..65dd36e726 Binary files /dev/null and b/aio/content/images/guide/getting-started/product-details.png differ diff --git a/aio/content/images/guide/getting-started/shipping-prices-via-route.png b/aio/content/images/guide/getting-started/shipping-prices-via-route.png new file mode 100644 index 0000000000..a7f099d360 Binary files /dev/null and b/aio/content/images/guide/getting-started/shipping-prices-via-route.png differ diff --git a/aio/content/images/guide/getting-started/shipping-prices.png b/aio/content/images/guide/getting-started/shipping-prices.png new file mode 100644 index 0000000000..0d6f49f04d Binary files /dev/null and b/aio/content/images/guide/getting-started/shipping-prices.png differ diff --git a/aio/content/images/guide/getting-started/stackblitz-angular-icon.png b/aio/content/images/guide/getting-started/stackblitz-angular-icon.png new file mode 100644 index 0000000000..75a2daddc0 Binary files /dev/null and b/aio/content/images/guide/getting-started/stackblitz-angular-icon.png differ diff --git a/aio/content/images/guide/getting-started/stackblitz-icon-only-small.png b/aio/content/images/guide/getting-started/stackblitz-icon-only-small.png new file mode 100644 index 0000000000..c6ff5df84e Binary files /dev/null and b/aio/content/images/guide/getting-started/stackblitz-icon-only-small.png differ diff --git a/aio/content/images/guide/getting-started/stackblitz-icon-only.png b/aio/content/images/guide/getting-started/stackblitz-icon-only.png new file mode 100644 index 0000000000..93bce1aaa6 Binary files /dev/null and b/aio/content/images/guide/getting-started/stackblitz-icon-only.png differ diff --git a/aio/content/images/guide/getting-started/stackblitz-icon-small.png b/aio/content/images/guide/getting-started/stackblitz-icon-small.png new file mode 100644 index 0000000000..edffbd1573 Binary files /dev/null and b/aio/content/images/guide/getting-started/stackblitz-icon-small.png differ diff --git a/aio/content/images/guide/getting-started/stackblitz-icon-smallest.png b/aio/content/images/guide/getting-started/stackblitz-icon-smallest.png new file mode 100644 index 0000000000..3397527510 Binary files /dev/null and b/aio/content/images/guide/getting-started/stackblitz-icon-smallest.png differ diff --git a/aio/content/images/guide/getting-started/stackblitz-icon.png b/aio/content/images/guide/getting-started/stackblitz-icon.png new file mode 100644 index 0000000000..d115fd8545 Binary files /dev/null and b/aio/content/images/guide/getting-started/stackblitz-icon.png differ diff --git a/aio/content/images/guide/getting-started/starter-app-components.png b/aio/content/images/guide/getting-started/starter-app-components.png new file mode 100644 index 0000000000..2ad1088661 Binary files /dev/null and b/aio/content/images/guide/getting-started/starter-app-components.png differ diff --git a/aio/content/images/guide/getting-started/template-syntax-product-anchor.png b/aio/content/images/guide/getting-started/template-syntax-product-anchor.png new file mode 100644 index 0000000000..2321725319 Binary files /dev/null and b/aio/content/images/guide/getting-started/template-syntax-product-anchor.png differ diff --git a/aio/content/images/guide/getting-started/template-syntax-product-description.png b/aio/content/images/guide/getting-started/template-syntax-product-description.png new file mode 100644 index 0000000000..7fbc7d221a Binary files /dev/null and b/aio/content/images/guide/getting-started/template-syntax-product-description.png differ diff --git a/aio/content/images/guide/getting-started/template-syntax-product-names.png b/aio/content/images/guide/getting-started/template-syntax-product-names.png new file mode 100644 index 0000000000..cca1e49a25 Binary files /dev/null and b/aio/content/images/guide/getting-started/template-syntax-product-names.png differ diff --git a/aio/content/images/guide/getting-started/template-syntax-product-share-alert.png b/aio/content/images/guide/getting-started/template-syntax-product-share-alert.png new file mode 100644 index 0000000000..60b344e2a6 Binary files /dev/null and b/aio/content/images/guide/getting-started/template-syntax-product-share-alert.png differ diff --git a/aio/content/images/guide/getting-started/template-syntax-product-share-button.png b/aio/content/images/guide/getting-started/template-syntax-product-share-button.png new file mode 100644 index 0000000000..540f695ebe Binary files /dev/null and b/aio/content/images/guide/getting-started/template-syntax-product-share-button.png differ diff --git a/aio/content/images/guide/toh/component-hierarchy.svg b/aio/content/images/guide/toh/component-hierarchy.svg new file mode 100644 index 0000000000..68327c4885 --- /dev/null +++ b/aio/content/images/guide/toh/component-hierarchy.svg @@ -0,0 +1,119 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/aio/content/images/guide/toh/component-structure.gif b/aio/content/images/guide/toh/component-structure.gif new file mode 100644 index 0000000000..181128e629 Binary files /dev/null and b/aio/content/images/guide/toh/component-structure.gif differ diff --git a/aio/content/images/guide/toh/component-structure.png b/aio/content/images/guide/toh/component-structure.png new file mode 100644 index 0000000000..5e4b79ab1c Binary files /dev/null and b/aio/content/images/guide/toh/component-structure.png differ diff --git a/aio/content/navigation.json b/aio/content/navigation.json index 9d41a6c8d4..fbd60ad6fd 100644 --- a/aio/content/navigation.json +++ b/aio/content/navigation.json @@ -58,12 +58,43 @@ "hidden": true }, { - "url": "guide/quickstart", "title": "Getting Started", - "tooltip": "A brief introduction to Angular and Angular CLI essentials." + "tooltip": "Learn the basics by building your first Angular application.", + "children": [ + { + "url": "getting-started", + "title": "Your First App", + "tooltip": "Introduction to Angular's component model, template syntax, and component communication" + }, + { + "url": "getting-started/routing", + "title": "Routing", + "tooltip": "Introduction to routing between components using the browser's URL" + }, + { + "url": "getting-started/data", + "title": "Managing Data", + "tooltip": "Introduction to services, and accessing external data" + }, + { + "url": "getting-started/forms", + "title": "Forms", + "tooltip": "Learn about fetching and managing data from users with forms" + }, + { + "url": "getting-started/deployment", + "title": "Deployment", + "tooltip": "Share your application with the world by hosting it on Firebase or your own server" + } + ] }, { - "title": "Tutorial", + "url": "guide/quickstart", + "title": "Environment Quickstart", + "tooltip": "A brief introduction to local development with the Angular CLI." + }, + { + "title": "Tutorial: Tour of Heroes", "tooltip": "The Tour of Heroes tutorial takes you through the steps of creating an Angular application in TypeScript.", "children": [ { @@ -538,7 +569,7 @@ }, { "title": "Setup & Deployment", - "tooltip": "Setup, build, testing, and deployment environment and tool information.", + "tooltip": "Build, testing, and deployment environment, tool, and configuration information.", "children": [ { "url": "guide/setup", diff --git a/aio/content/tutorial/index.md b/aio/content/tutorial/index.md index 5e82584693..5d4eb677d9 100644 --- a/aio/content/tutorial/index.md +++ b/aio/content/tutorial/index.md @@ -1,12 +1,25 @@

Tutorial: Tour of Heroes

-The _Tour of Heroes_ tutorial covers the fundamentals of Angular. -In this tutorial you will build an app that helps a staffing agency manage its stable of heroes. +This _Tour of Heroes_ tutorial provides a deep dive into the fundamentals of Angular. +It shows you how to set up your local development environment and develop an app using the [Angular CLI tool](cli "CLI command reference"). -This basic app has many of the features you'd expect to find in a data-driven application. +
+
Getting Started - Stackblitz
+ + +We recently introduced a [**new Getting Started**](getting-started) that leverages the [StackBlitz](https://stackblitz.com/) online development environment. +We recommend the new Getting Started for anyone who wants to quickly learn the essentials of Angular, in the context of building an online store app. +The new Getting Started covers the same major topics as this Tour of Heroes—components, template syntax, routing, services, and accessing data via HTTP—in a condensed format. + + +
+ +In this _Tour of Heroes_ tutorial, you will build an app that helps a staffing agency manage its stable of heroes. + +This app has many of the features you'd expect to find in a data-driven application. It acquires and displays a list of heroes, edits a selected hero's detail, and navigates among different views of heroic data. -By the end of the tutorial you will be able to do the following: +By the end of this tutorial you will be able to do the following: * Use built-in Angular directives to show and hide elements and display lists of hero data. * Create Angular components to display hero details and show an array of heroes. @@ -21,7 +34,7 @@ By the end of the tutorial you will be able to do the following: You'll learn enough Angular to get started and gain confidence that Angular can do whatever you need it to do. -After completing all tutorial steps, the final app will look like this . +After completing all tutorial steps, the final app will look like this: . ## What you'll build diff --git a/aio/scripts/_payload-limits.json b/aio/scripts/_payload-limits.json index 7d1b3a4c69..fbf5bf4630 100755 --- a/aio/scripts/_payload-limits.json +++ b/aio/scripts/_payload-limits.json @@ -2,8 +2,8 @@ "aio": { "master": { "uncompressed": { - "runtime": 3713, - "main": 509261, + "runtime": 2972, + "main": 503779, "polyfills": 59197 } } diff --git a/aio/src/app/custom-elements/element-registry.ts b/aio/src/app/custom-elements/element-registry.ts index 784ee0332e..0a80932e9a 100644 --- a/aio/src/app/custom-elements/element-registry.ts +++ b/aio/src/app/custom-elements/element-registry.ts @@ -39,27 +39,7 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [ { selector: 'live-example', loadChildren: './live-example/live-example.module#LiveExampleModule' - }, - { - selector: 'aio-gs-interpolation', - loadChildren: './getting-started/interpolation/interpolation.module#InterpolationModule' - }, - { - selector: 'aio-gs-property-binding', - loadChildren: './getting-started/property-binding/property-binding.module#PropertyBindingModule' - }, - { - selector: 'aio-gs-event-binding', - loadChildren: './getting-started/event-binding/event-binding.module#EventBindingModule' - }, - { - selector: 'aio-gs-ng-if', - loadChildren: './getting-started/ng-if/ng-if.module#NgIfModule' - }, - { - selector: 'aio-gs-ng-for', - loadChildren: './getting-started/ng-for/ng-for.module#NgForModule' - }, + } ]; /** diff --git a/aio/src/app/custom-elements/getting-started/container/container.component.spec.ts b/aio/src/app/custom-elements/getting-started/container/container.component.spec.ts deleted file mode 100644 index c338f34c68..0000000000 --- a/aio/src/app/custom-elements/getting-started/container/container.component.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Component } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ContainerComponent } from './container.component'; - -@Component({ - template: ` - - Template - Data - Result - - ` -}) -export class TestComponent {} - -describe('Getting Started Container Component', () => { - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [ ContainerComponent, TestComponent ] - }); - - fixture = TestBed.createComponent(TestComponent); - fixture.detectChanges(); - }); - - it('should project the content into the appropriate areas', () => { - const compiled = fixture.debugElement.nativeElement; - const pre = compiled.querySelector('pre'); - const code = compiled.querySelector('code'); - const tabledata = compiled.querySelectorAll('td'); - - expect(pre.textContent).toContain('Template'); - expect(code.textContent).toContain('Data'); - expect(tabledata[2].textContent).toContain('Result'); - }); -}); diff --git a/aio/src/app/custom-elements/getting-started/container/container.component.ts b/aio/src/app/custom-elements/getting-started/container/container.component.ts deleted file mode 100644 index 7820a7326c..0000000000 --- a/aio/src/app/custom-elements/getting-started/container/container.component.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'aio-gs-container', - template: ` - - - - - - - - - - - - - - - -
TemplateDataResult
-
-
- -
- `, - styles: [ - ` - pre { - margin: 0; - } - - code { - display: flex; - align-items: center; - } - - @media only screen and (max-width: 760px), - (min-device-width: 768px) and (max-device-width: 1024px) { - /* Force table to not be like tables anymore */ - table, thead, tbody, th, td, tr { - display: block; - } - - /* Hide table headers (but not display: none;, for accessibility) */ - thead tr { - position: absolute; - top: -9999px; - left: -9999px; - } - - tr { border: 1px solid #ccc; } - - td { - /* Behave like a "row" */ - border: none; - border-bottom: 1px solid #eee; - position: relative; - padding-top: 10%; - } - - td:before { - /* Now like a table header */ - position: absolute; - /* Top/left values mimic padding */ - top: 6px; - left: 6px; - width: 45%; - padding-right: 10px; - } - - /* Label the data */ - td:nth-of-type(1):before { content: "Template"; } - td:nth-of-type(2):before { content: "Data"; } - td:nth-of-type(3):before { content: "Result"; } - } - ` - ] -}) -export class ContainerComponent { } diff --git a/aio/src/app/custom-elements/getting-started/container/container.module.ts b/aio/src/app/custom-elements/getting-started/container/container.module.ts deleted file mode 100644 index 478a851092..0000000000 --- a/aio/src/app/custom-elements/getting-started/container/container.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { ContainerComponent } from './container.component'; - -@NgModule({ - imports: [ CommonModule ], - declarations: [ ContainerComponent ], - exports: [ ContainerComponent ] -}) -export class ContainerModule { } diff --git a/aio/src/app/custom-elements/getting-started/event-binding/event-binding.component.spec.ts b/aio/src/app/custom-elements/getting-started/event-binding/event-binding.component.spec.ts deleted file mode 100644 index e8ca04caa7..0000000000 --- a/aio/src/app/custom-elements/getting-started/event-binding/event-binding.component.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ContainerModule } from '../container/container.module'; -import { EventBindingComponent } from './event-binding.component'; -import { createCustomEvent } from '../../../../testing/dom-utils'; - -describe('Getting Started Event Binding Component', () => { - let component: EventBindingComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ ContainerModule ], - declarations: [ EventBindingComponent ] - }); - - fixture = TestBed.createComponent(EventBindingComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - - spyOn(window, 'alert'); - }); - - it('should update the name property on input change', () => { - const text = 'Hello Angular'; - const compiled = fixture.debugElement.nativeElement; - const input: HTMLInputElement = compiled.querySelector('input'); - - - input.value = text; - input.dispatchEvent(createCustomEvent(document, 'input', '')); - - fixture.detectChanges(); - - expect(component.name).toBe(text); - }); - - it('should display an alert when the button is clicked', () => { - const compiled = fixture.debugElement.nativeElement; - const button: HTMLButtonElement = compiled.querySelector('button'); - - button.click(); - - expect(window.alert).toHaveBeenCalledWith('Hello, Angular!'); - }); -}); diff --git a/aio/src/app/custom-elements/getting-started/event-binding/event-binding.component.ts b/aio/src/app/custom-elements/getting-started/event-binding/event-binding.component.ts deleted file mode 100644 index 753d34fc5f..0000000000 --- a/aio/src/app/custom-elements/getting-started/event-binding/event-binding.component.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'aio-gs-event-binding', - template: ` - - <button (click)="greet(name)"> - Greet -</button> - - - name = ''; - - - - - - - `, - preserveWhitespaces: true -}) -export class EventBindingComponent { - name = 'Angular'; - - greet(name: string) { - window.alert(`Hello, ${name}!`); - } -} diff --git a/aio/src/app/custom-elements/getting-started/event-binding/event-binding.module.ts b/aio/src/app/custom-elements/getting-started/event-binding/event-binding.module.ts deleted file mode 100644 index ffd99e20ff..0000000000 --- a/aio/src/app/custom-elements/getting-started/event-binding/event-binding.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { NgModule, Type } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { WithCustomElementComponent } from '../../element-registry'; -import { EventBindingComponent } from './event-binding.component'; -import { ContainerModule } from '../container/container.module'; - -@NgModule({ - imports: [ CommonModule, ContainerModule ], - declarations: [ EventBindingComponent ], - exports: [ EventBindingComponent ], - entryComponents: [ EventBindingComponent ] -}) -export class EventBindingModule implements WithCustomElementComponent { - customElementComponent: Type = EventBindingComponent; -} diff --git a/aio/src/app/custom-elements/getting-started/interpolation/interpolation.component.spec.ts b/aio/src/app/custom-elements/getting-started/interpolation/interpolation.component.spec.ts deleted file mode 100644 index e7f3777ebf..0000000000 --- a/aio/src/app/custom-elements/getting-started/interpolation/interpolation.component.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { InterpolationComponent } from './interpolation.component'; -import { ContainerModule } from '../container/container.module'; -import { createCustomEvent } from '../../../../testing/dom-utils'; - -describe('Getting Started Interpolation Component', () => { - let component: InterpolationComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ ContainerModule ], - declarations: [ InterpolationComponent ] - }); - - fixture = TestBed.createComponent(InterpolationComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should update the siteName property on input change', () => { - const text = 'Hello Angular'; - const compiled = fixture.debugElement.nativeElement; - const input: HTMLInputElement = compiled.querySelector('input'); - - input.value = text; - input.dispatchEvent(createCustomEvent(document, 'input', '')); - - fixture.detectChanges(); - - expect(component.siteName).toBe(text); - }); - - it('should display the siteName', () => { - const compiled = fixture.debugElement.nativeElement; - const header: HTMLHeadingElement = compiled.querySelector('h1'); - - expect(header.textContent).toContain('My Store'); - }); -}); diff --git a/aio/src/app/custom-elements/getting-started/interpolation/interpolation.component.ts b/aio/src/app/custom-elements/getting-started/interpolation/interpolation.component.ts deleted file mode 100644 index 28af29c2de..0000000000 --- a/aio/src/app/custom-elements/getting-started/interpolation/interpolation.component.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Component } from '@angular/core'; - - -@Component({ - selector: 'aio-gs-interpolation', - template: ` - - <h1>Welcome to {{'{'+'{'}}siteName{{'}'+'}'}}<h1> - - - siteName = ''; - - -

Welcome to {{ siteName }}

-
- ` -}) -export class InterpolationComponent { - siteName = 'My Store'; -} diff --git a/aio/src/app/custom-elements/getting-started/interpolation/interpolation.module.ts b/aio/src/app/custom-elements/getting-started/interpolation/interpolation.module.ts deleted file mode 100644 index b25b2826a4..0000000000 --- a/aio/src/app/custom-elements/getting-started/interpolation/interpolation.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { NgModule, Type } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { WithCustomElementComponent } from '../../element-registry'; -import { InterpolationComponent } from './interpolation.component'; -import { ContainerModule } from '../container/container.module'; - -@NgModule({ - imports: [ CommonModule, ContainerModule ], - declarations: [ InterpolationComponent ], - exports: [ InterpolationComponent ], - entryComponents: [ InterpolationComponent ] -}) -export class InterpolationModule implements WithCustomElementComponent { - customElementComponent: Type = InterpolationComponent; -} diff --git a/aio/src/app/custom-elements/getting-started/ng-for/ng-for.component.spec.ts b/aio/src/app/custom-elements/getting-started/ng-for/ng-for.component.spec.ts deleted file mode 100644 index 3f7ba6d41b..0000000000 --- a/aio/src/app/custom-elements/getting-started/ng-for/ng-for.component.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { NgForComponent } from './ng-for.component'; -import { ContainerModule } from '../container/container.module'; -import { ProductService } from '../product.service'; - -describe('Getting Started NgFor Component', () => { - let component: NgForComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ ContainerModule ], - declarations: [ NgForComponent ], - providers: [ ProductService ] - }); - - fixture = TestBed.createComponent(NgForComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should display the products', () => { - const compiled = fixture.debugElement.nativeElement; - const spans = compiled.querySelectorAll('span'); - - expect(spans[0]!.textContent).toContain('Shoes'); - expect(spans[1]!.textContent).toContain('Phones'); - }); - - it('should display an error message if provided products JSON is invalid', () => { - fixture.detectChanges(); - - component.productsData$.next('bad'); - - fixture.detectChanges(); - - component.parseError$.subscribe(error => { - expect(error).toBeTruthy(); - }); - }); -}); diff --git a/aio/src/app/custom-elements/getting-started/ng-for/ng-for.component.ts b/aio/src/app/custom-elements/getting-started/ng-for/ng-for.component.ts deleted file mode 100644 index 66c72a82ea..0000000000 --- a/aio/src/app/custom-elements/getting-started/ng-for/ng-for.component.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; -import { Subscription } from 'rxjs'; - -import { ProductService } from '../product.service'; - -@Component({ - selector: 'aio-gs-ng-for', - template: ` - - <span *ngFor="let product of products"> - {{'{'+'{'}}product{{'}'+'}'}} -</span> - - - products = ; -
error_outline
-
- - - {{product}} - -
- `, - styles: [` - span::after { - content: ' '; - } - `], - preserveWhitespaces: true -}) -export class NgForComponent implements OnInit, OnDestroy { - productsData$ = this.productService.productsData$; - products$ = this.productService.products$; - parseError$ = this.productService.parseError$; - productsSub: Subscription; - - constructor(private productService: ProductService) {} - - ngOnInit() { - this.productsSub = this.productService.init().subscribe(); - } - - ngOnDestroy() { - this.productsSub.unsubscribe(); - } -} diff --git a/aio/src/app/custom-elements/getting-started/ng-for/ng-for.module.ts b/aio/src/app/custom-elements/getting-started/ng-for/ng-for.module.ts deleted file mode 100644 index aca4dadc00..0000000000 --- a/aio/src/app/custom-elements/getting-started/ng-for/ng-for.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { NgModule, Type } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { WithCustomElementComponent } from '../../element-registry'; -import { NgForComponent } from './ng-for.component'; -import { ContainerModule } from '../container/container.module'; -import { ProductService } from '../product.service'; - -@NgModule({ - imports: [ CommonModule, ContainerModule, MatTooltipModule ], - declarations: [ NgForComponent ], - exports: [ NgForComponent ], - entryComponents: [ NgForComponent ], - providers: [ ProductService ] -}) -export class NgForModule implements WithCustomElementComponent { - customElementComponent: Type = NgForComponent; -} diff --git a/aio/src/app/custom-elements/getting-started/ng-if/ng-if.component.spec.ts b/aio/src/app/custom-elements/getting-started/ng-if/ng-if.component.spec.ts deleted file mode 100644 index 09d297c5a7..0000000000 --- a/aio/src/app/custom-elements/getting-started/ng-if/ng-if.component.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { NgIfComponent } from './ng-if.component'; -import { ContainerModule } from '../container/container.module'; -import { ProductService } from '../product.service'; - -describe('Getting Started NgIf Component', () => { - let component: NgIfComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ ContainerModule ], - declarations: [ NgIfComponent ], - providers: [ ProductService ] - }); - - fixture = TestBed.createComponent(NgIfComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should display the message if products are listed', () => { - const compiled = fixture.debugElement.nativeElement; - const paragraph = compiled.querySelector('p'); - - expect(paragraph.textContent).toContain('available'); - }); - - it('should not display the message if products list is empty', () => { - component.products$.next([]); - fixture.detectChanges(); - - const compiled = fixture.debugElement.nativeElement; - const paragraph = compiled.querySelector('p'); - - expect(paragraph).toBeFalsy(); - }); - - it('should display an error message if provided products JSON is invalid', () => { - fixture.detectChanges(); - - component.productsData$.next('bad'); - - fixture.detectChanges(); - - component.parseError$.subscribe(error => { - expect(error).toBeTruthy(); - }); - }); -}); diff --git a/aio/src/app/custom-elements/getting-started/ng-if/ng-if.component.ts b/aio/src/app/custom-elements/getting-started/ng-if/ng-if.component.ts deleted file mode 100644 index 9d4faad5a1..0000000000 --- a/aio/src/app/custom-elements/getting-started/ng-if/ng-if.component.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; -import { Subscription } from 'rxjs'; - -import { ProductService } from '../product.service'; - -@Component({ - selector: 'aio-gs-ng-if', - template: ` - - <p *ngIf="products.length > 0"> - We still have products available. -</p> - - - products = ; -
error_outline
-
- - -

- We still have products available. -

-
-
- `, - preserveWhitespaces: true -}) -export class NgIfComponent implements OnInit, OnDestroy { - productsData$ = this.productService.productsData$; - products$ = this.productService.products$; - parseError$ = this.productService.parseError$; - productsSub: Subscription; - - constructor(private productService: ProductService) {} - - ngOnInit() { - this.productsSub = this.productService.init().subscribe(); - } - - ngOnDestroy() { - this.productsSub.unsubscribe(); - } -} diff --git a/aio/src/app/custom-elements/getting-started/ng-if/ng-if.module.ts b/aio/src/app/custom-elements/getting-started/ng-if/ng-if.module.ts deleted file mode 100644 index e49fce2b47..0000000000 --- a/aio/src/app/custom-elements/getting-started/ng-if/ng-if.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { NgModule, Type } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { WithCustomElementComponent } from '../../element-registry'; -import { NgIfComponent } from './ng-if.component'; -import { ContainerModule } from '../container/container.module'; -import { ProductService } from '../product.service'; - -@NgModule({ - imports: [ CommonModule, ContainerModule, MatTooltipModule ], - declarations: [ NgIfComponent ], - exports: [ NgIfComponent ], - entryComponents: [ NgIfComponent ], - providers: [ ProductService ] -}) -export class NgIfModule implements WithCustomElementComponent { - customElementComponent: Type = NgIfComponent; -} diff --git a/aio/src/app/custom-elements/getting-started/product.service.ts b/aio/src/app/custom-elements/getting-started/product.service.ts deleted file mode 100644 index 3f677f9842..0000000000 --- a/aio/src/app/custom-elements/getting-started/product.service.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Injectable } from '@angular/core'; -import { BehaviorSubject, Subject } from 'rxjs'; -import { tap, debounceTime } from 'rxjs/operators'; - -export const PRODUCTS = ['Shoes', 'Phones']; - -@Injectable() -export class ProductService { - productsData$ = new BehaviorSubject(JSON.stringify(PRODUCTS)); - products$ = new BehaviorSubject(PRODUCTS); - parseError$ = new Subject(); - - init() { - return this.productsData$.pipe( - debounceTime(250), - tap(data => { - let parsed; - - try { - parsed = JSON.parse(data); - } catch (e) { - parsed = null; - } - - if (parsed && Array.isArray(parsed)) { - this.products$.next(parsed); - this.parseError$.next(false); - } else { - this.parseError$.next(true); - } - })); - } -} diff --git a/aio/src/app/custom-elements/getting-started/property-binding/property-binding.component.spec.ts b/aio/src/app/custom-elements/getting-started/property-binding/property-binding.component.spec.ts deleted file mode 100644 index 6dc778f6a0..0000000000 --- a/aio/src/app/custom-elements/getting-started/property-binding/property-binding.component.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { PropertyBindingComponent } from './property-binding.component'; -import { ContainerModule } from '../container/container.module'; -import { createCustomEvent } from '../../../../testing/dom-utils'; - -describe('Getting Started Property Binding Component', () => { - let component: PropertyBindingComponent; - let fixture: ComponentFixture; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ ContainerModule ], - declarations: [ PropertyBindingComponent ] - }); - - fixture = TestBed.createComponent(PropertyBindingComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should update the image title on input change', () => { - const text = 'Hello Angular'; - const compiled = fixture.debugElement.nativeElement; - const input: HTMLInputElement = compiled.querySelector('input'); - - input.value = text; - input.dispatchEvent(createCustomEvent(document, 'input', '')); - - fixture.detectChanges(); - - expect(component.imageTitle).toBe(text); - }); - - it('should display the image title', () => { - const compiled = fixture.debugElement.nativeElement; - const image: HTMLImageElement = compiled.querySelector('img'); - - expect(image.getAttribute('title')).toContain('Angular Logo'); - }); - -}); diff --git a/aio/src/app/custom-elements/getting-started/property-binding/property-binding.component.ts b/aio/src/app/custom-elements/getting-started/property-binding/property-binding.component.ts deleted file mode 100644 index 1b469a527c..0000000000 --- a/aio/src/app/custom-elements/getting-started/property-binding/property-binding.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'aio-gs-property-binding', - template: ` - - <img ... [title]="imageTitle"> - - - imageTitle = ''; - - - - - - - ` -}) -export class PropertyBindingComponent { - imageTitle = 'Angular Logo'; -} diff --git a/aio/src/app/custom-elements/getting-started/property-binding/property-binding.module.ts b/aio/src/app/custom-elements/getting-started/property-binding/property-binding.module.ts deleted file mode 100644 index e0ca98427e..0000000000 --- a/aio/src/app/custom-elements/getting-started/property-binding/property-binding.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { NgModule, Type } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { WithCustomElementComponent } from '../../element-registry'; -import { PropertyBindingComponent } from './property-binding.component'; -import { ContainerModule } from '../container/container.module'; - -@NgModule({ - imports: [ CommonModule, ContainerModule ], - declarations: [ PropertyBindingComponent ], - exports: [ PropertyBindingComponent ], - entryComponents: [ PropertyBindingComponent ] -}) -export class PropertyBindingModule implements WithCustomElementComponent { - customElementComponent: Type = PropertyBindingComponent; -} diff --git a/aio/tools/example-zipper/customizer/package-json/getting-started.json b/aio/tools/example-zipper/customizer/package-json/getting-started.json new file mode 100644 index 0000000000..965bd82407 --- /dev/null +++ b/aio/tools/example-zipper/customizer/package-json/getting-started.json @@ -0,0 +1,19 @@ +{ + "scripts": [ + { "name": "ng", "command": "ng" }, + { "name": "build", "command": "ng build --prod" }, + { "name": "start", "command": "ng serve" }, + { "name": "test", "command": "ng test" }, + { "name": "lint", "command": "ng lint" }, + { "name": "e2e", "command": "ng e2e" } + ], + "dependencies": [], + "devDependencies": [ + "@angular-devkit/build-angular", + "@angular/cli", + "@types/jasminewd2", + "jasmine-spec-reporter", + "karma-coverage-istanbul-reporter", + "ts-node" + ] +} diff --git a/aio/tools/examples/example-boilerplate.js b/aio/tools/examples/example-boilerplate.js index 6304e05950..0bc5596607 100644 --- a/aio/tools/examples/example-boilerplate.js +++ b/aio/tools/examples/example-boilerplate.js @@ -39,6 +39,11 @@ BOILERPLATE_PATHS.testing = [...cliRelativePath, 'angular.json']; BOILERPLATE_PATHS.universal = [...cliRelativePath, 'angular.json', 'package.json']; +BOILERPLATE_PATHS['getting-started'] = [ + ...cliRelativePath, + 'src/styles.css' +]; + BOILERPLATE_PATHS.ivy = { systemjs: ['rollup-config.js', 'tsconfig-aot.json'], cli: ['src/tsconfig.app.json'] @@ -87,8 +92,11 @@ class ExampleBoilerPlate { filePath => this.copyFile(boilerPlateBasePath, exampleFolder, filePath)); // Copy the boilerplate common files - BOILERPLATE_PATHS.common.forEach( - filePath => this.copyFile(BOILERPLATE_COMMON_BASE_PATH, exampleFolder, filePath)); + const useCommonBoilerplate = exampleConfig.useCommonBoilerplate !== false; + + if (useCommonBoilerplate) { + BOILERPLATE_PATHS.common.forEach(filePath => this.copyFile(BOILERPLATE_COMMON_BASE_PATH, exampleFolder, filePath)); + } // Copy Ivy specific files if (ivy) { diff --git a/aio/tools/examples/shared/boilerplate/common/src/styles.css b/aio/tools/examples/shared/boilerplate/common/src/styles.css index a0496ae272..aa756e7d70 100644 --- a/aio/tools/examples/shared/boilerplate/common/src/styles.css +++ b/aio/tools/examples/shared/boilerplate/common/src/styles.css @@ -1,4 +1,4 @@ -/* Master Styles */ +/* Global Styles */ h1 { color: #369; font-family: Arial, Helvetica, sans-serif; diff --git a/aio/tools/examples/shared/boilerplate/getting-started/src/styles.css b/aio/tools/examples/shared/boilerplate/getting-started/src/styles.css new file mode 100644 index 0000000000..2908aaf861 --- /dev/null +++ b/aio/tools/examples/shared/boilerplate/getting-started/src/styles.css @@ -0,0 +1,138 @@ +/* Global Styles */ + +* { + font-family: 'Roboto', Arial, sans-serif; + color: #616161; + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + margin: 0; +} + +.container { + display: flex; + flex-direction: row; +} + +router-outlet + * { + padding: 0 16px; +} + +/* Text */ + +h1 { + font-size: 32px; +} + +h2 { + font-size: 20px; +} + +h1, h2 { + font-weight: lighter; +} + +p { + font-size: 14px; +} + +/* Hyperlink */ + +a { + cursor: pointer; + color: #1976d2; + text-decoration: none; +} + +a:hover { + opacity: 0.8; +} + +/* Input */ + +input { + font-size: 14px; + border-radius: 2px; + padding: 8px; + margin-bottom: 16px; + border: 1px solid #BDBDBD; +} + +label { + font-size: 12px; + font-weight: bold; + margin-bottom: 4px; + display: block; + text-transform: uppercase; +} + +/* Button */ +.button, button { + display: inline-flex; + align-items: center; + padding: 8px 16px; + border-radius: 2px; + font-size: 14px; + cursor: pointer; + background-color: #1976d2; + color: white; + border: none; +} + +.button:hover, button:hover { + opacity: 0.8; + font-weight: normal; +} + +.button:disabled, button:disabled { + opacity: 0.5; + cursor: auto; +} + +/* Fancy Button */ + +.fancy-button { + background-color: white; + color: #1976d2; +} + +.fancy-button i.material-icons { + color: #1976d2; + padding-right: 4px; +} + +/* Top Bar */ + +app-top-bar { + width: 100%; + height: 68px; + background-color: #1976d2; + padding: 16px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} + +app-top-bar h1 { + color: white; + margin: 0; +} + +/* Checkout Cart, Shipping Prices */ + +.cart-item, .shipping-item { + width: 100%; + min-width: 400px; + max-width: 450px; + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 16px 32px; + margin-bottom: 8px; + border-radius: 2px; + background-color: #EEEEEE; +} diff --git a/aio/tools/transforms/angular-content-package/index.js b/aio/tools/transforms/angular-content-package/index.js index b0635ae198..1775220085 100644 --- a/aio/tools/transforms/angular-content-package/index.js +++ b/aio/tools/transforms/angular-content-package/index.js @@ -43,7 +43,7 @@ module.exports = new Package('angular-content', [basePackage, contentPackage]) readFilesProcessor.sourceFiles = readFilesProcessor.sourceFiles.concat([ { basePath: CONTENTS_PATH, - include: CONTENTS_PATH + '/{guide,tutorial}/**/*.md', + include: CONTENTS_PATH + '/{getting-started,guide,tutorial}/**/*.md', fileReader: 'contentFileReader' }, { diff --git a/aio/tools/transforms/authors-package/getting-started-package.js b/aio/tools/transforms/authors-package/getting-started-package.js new file mode 100644 index 0000000000..d7a1277d00 --- /dev/null +++ b/aio/tools/transforms/authors-package/getting-started-package.js @@ -0,0 +1,47 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +const Package = require('dgeni').Package; +const contentPackage = require('../angular-content-package'); +const { readFileSync } = require('fs'); +const { resolve } = require('canonical-path'); +const { CONTENTS_PATH } = require('../config'); + +/* eslint no-console: "off" */ + +function createPackage(tutorialName) { + + const tutorialFilePath = `${CONTENTS_PATH}/getting-started/${tutorialName}.md`; + const tutorialFile = readFileSync(tutorialFilePath, 'utf8'); + const examples = []; + tutorialFile.replace(/]*path="([^"]+)"/g, (_, path) => examples.push('examples/' + path)); + + if (examples.length) { + console.log('The following example files are referenced in this getting-started:'); + console.log(examples.map(example => ' - ' + example).join('\n')); + } + + return new Package('author-getting-started', [contentPackage]) + .config(function(readFilesProcessor) { + readFilesProcessor.sourceFiles = [ + { + basePath: CONTENTS_PATH, + include: tutorialFilePath, + fileReader: 'contentFileReader' + }, + { + basePath: CONTENTS_PATH, + include: examples.map(example => resolve(CONTENTS_PATH, example)), + fileReader: 'exampleFileReader' + } + ]; + }); +} + + +module.exports = { createPackage }; \ No newline at end of file diff --git a/aio/tools/transforms/authors-package/index.js b/aio/tools/transforms/authors-package/index.js index 6fe25eb86b..b747bc8302 100644 --- a/aio/tools/transforms/authors-package/index.js +++ b/aio/tools/transforms/authors-package/index.js @@ -22,6 +22,14 @@ function createPackage(changedFile) { return require('./tutorial-package').createPackage(tutorialName); } + const gettingStartedMatch = /^aio\/content\/getting-started\/([^.]+)\.md/.exec(changedFile); + const gettingStartedExampleMatch = /^aio\/content\/examples\/getting-started\/([^\/]+)\//.exec(changedFile); + if (gettingStartedMatch || gettingStartedExampleMatch) { + const gettingStartedName = gettingStartedMatch && gettingStartedMatch[1] || 'index'; + console.log('Building getting started docs'); + return require('./getting-started-package').createPackage(gettingStartedName); + } + const guideMatch = /^aio\/content\/guide\/([^.]+)\.md/.exec(changedFile); const exampleMatch = /^aio\/content\/examples\/(?:cb-)?([^\/]+)\//.exec(changedFile); if (guideMatch || exampleMatch) {