|
@ -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-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-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/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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"useCommonBoilerplate": false,
|
||||||
|
"projectType": "getting-started"
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
p {
|
||||||
|
font-family: Lato;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<app-top-bar></app-top-bar>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: [ './app.component.css' ]
|
||||||
|
})
|
||||||
|
export class AppComponent {}
|
|
@ -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 { }
|
|
@ -0,0 +1 @@
|
||||||
|
<h2>Products</h2>
|
|
@ -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!');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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: ''
|
||||||
|
}
|
||||||
|
];
|
|
@ -0,0 +1,5 @@
|
||||||
|
<a [routerLink]="['/']">
|
||||||
|
<h1>My Store</h1>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a [routerLink]="['/cart']" class="button fancy-button"><i class="material-icons">shopping_cart</i>Checkout</a>
|
|
@ -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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "Overnight",
|
||||||
|
"price": 25.99
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "2-Day",
|
||||||
|
"price": 9.99
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Postal",
|
||||||
|
"price": 2.99
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Angular Getting Started</title>
|
||||||
|
<base href="/" />
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -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);
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"description": "Getting Started",
|
||||||
|
"files":[
|
||||||
|
"!**/*.d.ts",
|
||||||
|
"!**/*.js",
|
||||||
|
"!**/*.[0-9].*"
|
||||||
|
],
|
||||||
|
"tags": ["Angular", "getting started", "tutorial"]
|
||||||
|
}
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"useCommonBoilerplate": false,
|
||||||
|
"projectType": "getting-started"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<app-top-bar></app-top-bar>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.css']
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
}
|
|
@ -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 { }
|
|
@ -0,0 +1,13 @@
|
||||||
|
// #docplaster
|
||||||
|
// #docregion
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
// #docregion v1
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class CartService {
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
<!-- #docplaster -->
|
||||||
|
<!-- #docregion prices -->
|
||||||
|
<h3>Cart</h3>
|
||||||
|
<!-- #enddocregion prices -->
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a routerLink="/shipping">Shipping Prices</a>
|
||||||
|
</p>
|
||||||
|
<!-- #docregion prices -->
|
||||||
|
|
||||||
|
<div class="cart-item" *ngFor="let item of items">
|
||||||
|
<span>{{ item.name }} </span>
|
||||||
|
<span>{{ item.price | currency }}</span>
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion prices -->
|
|
@ -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
|
||||||
|
) { }
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<!-- #docplaster -->
|
||||||
|
<!-- #docregion checkout-form-1, checkout-form-2 -->
|
||||||
|
<h3>Cart</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a routerLink="/shipping">Shipping Prices</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="cart-item" *ngFor="let item of items">
|
||||||
|
<span>{{ item.name }} </span>
|
||||||
|
<span>{{ item.price | currency }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form [formGroup]="checkoutForm" (ngSubmit)="onSubmit(checkoutForm.value)">
|
||||||
|
<!-- #enddocregion checkout-form-1 -->
|
||||||
|
<div>
|
||||||
|
<label>Name</label>
|
||||||
|
<input type="text" formControlName="name">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label>Address</label>
|
||||||
|
<input type="text" formControlName="address">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="button" type="submit">Purchase</button>
|
||||||
|
<!-- #docregion checkout-form-1 -->
|
||||||
|
</form>
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
<p *ngIf="product.price > 700">
|
||||||
|
<button>Notify Me</button>
|
||||||
|
</p>
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
<p *ngIf="product.price > 700">
|
||||||
|
<button (click)="notify.emit()">Notify Me</button>
|
||||||
|
</p>
|
|
@ -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();
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!-- #docplaster -->
|
||||||
|
<!-- #docregion details -->
|
||||||
|
<h2>Product Details</h2>
|
||||||
|
|
||||||
|
<div *ngIf="product">
|
||||||
|
<h3>{{ product.name }}</h3>
|
||||||
|
<h4>{{ product.price | currency }}</h4>
|
||||||
|
<p>{{ product.description }}</p>
|
||||||
|
|
||||||
|
<!-- #enddocregion details -->
|
||||||
|
<button (click)="addToCart(product)">Buy</button>
|
||||||
|
<!-- #docregion details -->
|
||||||
|
</div>
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<h2>Products</h2>
|
|
@ -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!');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!-- #docplaster -->
|
||||||
|
<!-- #docregion ngfor, interpolation -->
|
||||||
|
<h2>Products</h2>
|
||||||
|
|
||||||
|
<div *ngFor="let product of products">
|
||||||
|
<!-- #enddocregion ngfor -->
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
<!-- #enddocregion interpolation -->
|
||||||
|
<a [title]="product.name + ' details'">
|
||||||
|
<!-- #docregion interpolation -->
|
||||||
|
{{ product.name }}
|
||||||
|
<!-- #enddocregion interpolation -->
|
||||||
|
</a>
|
||||||
|
<!-- #docregion interpolation -->
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<!-- #docregion ngfor -->
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion ngfor, interpolation -->
|
|
@ -0,0 +1,15 @@
|
||||||
|
<h2>Products</h2>
|
||||||
|
|
||||||
|
<div *ngFor="let product of products">
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
<a [title]="product.name + ' details'">
|
||||||
|
{{ product.name }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p *ngIf="product.description">
|
||||||
|
Description: {{ product.description }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
|
@ -0,0 +1,19 @@
|
||||||
|
<h2>Products</h2>
|
||||||
|
|
||||||
|
<div *ngFor="let product of products">
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
<a [title]="product.name + ' details'">
|
||||||
|
{{ product.name }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p *ngIf="product.description">
|
||||||
|
Description: {{ product.description }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button (click)="share()">
|
||||||
|
Share
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
|
@ -0,0 +1,28 @@
|
||||||
|
<h2>Products</h2>
|
||||||
|
|
||||||
|
<div *ngFor="let product of products">
|
||||||
|
|
||||||
|
<!-- #docregion product-details -->
|
||||||
|
<h3>
|
||||||
|
<a [title]="product.name + ' details'">
|
||||||
|
{{ product.name }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<!-- #enddocregion product-details -->
|
||||||
|
|
||||||
|
<p *ngIf="product.description">
|
||||||
|
Description: {{ product.description }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- #docregion app-product-alerts -->
|
||||||
|
<button (click)="share()">
|
||||||
|
Share
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<app-product-alerts
|
||||||
|
[product]="product">
|
||||||
|
</app-product-alerts>
|
||||||
|
<!-- #enddocregion app-product-alerts -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<h2>Products</h2>
|
||||||
|
|
||||||
|
<div *ngFor="let product of products">
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
<a [title]="product.name + ' details'">
|
||||||
|
{{ product.name }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p *ngIf="product.description">
|
||||||
|
Description: {{ product.description }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- #docregion on-notify -->
|
||||||
|
<button (click)="share()">
|
||||||
|
Share
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<app-product-alerts
|
||||||
|
[product]="product"
|
||||||
|
(notify)="onNotify()">
|
||||||
|
</app-product-alerts>
|
||||||
|
<!-- #enddocregion on-notify -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<h2>Products</h2>
|
||||||
|
|
||||||
|
<!-- #docregion router-link -->
|
||||||
|
<div *ngFor="let product of products; index as productId">
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
<a [title]="product.name + ' details'" [routerLink]="['/products', productId]">
|
||||||
|
{{ product.name }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<!-- #enddocregion router-link -->
|
||||||
|
<p *ngIf="product.description">
|
||||||
|
Description: {{ product.description }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button (click)="share()">
|
||||||
|
Share
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<app-product-alerts
|
||||||
|
[product]="product"
|
||||||
|
(notify)="onNotify()">
|
||||||
|
</app-product-alerts>
|
||||||
|
<!-- #docregion router-link -->
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion router-link -->
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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: ''
|
||||||
|
}
|
||||||
|
];
|
|
@ -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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
<h3>Shipping Prices</h3>
|
||||||
|
|
||||||
|
<div class="shipping-item" *ngFor="let shipping of shippingCosts | async">
|
||||||
|
<span>{{ shipping.type }} </span>
|
||||||
|
<span>{{ shipping.price | currency }}</span>
|
||||||
|
</div>
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
<a>
|
||||||
|
<h1>My Store</h1>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="button fancy-button">
|
||||||
|
<i class="material-icons">shopping_cart</i>Checkout
|
||||||
|
</a>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<a [routerLink]="['/']">
|
||||||
|
<h1>My Store</h1>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a [routerLink]="['/cart']" class="button fancy-button">
|
||||||
|
<i class="material-icons">shopping_cart</i>Checkout
|
||||||
|
</a>
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "Overnight",
|
||||||
|
"price": 25.99
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "2-Day",
|
||||||
|
"price": 9.99
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Postal",
|
||||||
|
"price": 2.99
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Angular Getting Started</title>
|
||||||
|
<base href="/" />
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -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));
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"description": "Getting Started",
|
||||||
|
"files":[
|
||||||
|
"!**/*.d.ts",
|
||||||
|
"!**/*.js",
|
||||||
|
"!**/*.[0-9].*"
|
||||||
|
],
|
||||||
|
"tags": ["Angular", "getting started", "tutorial"]
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
||||||
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{@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`.
|
||||||
|
|
||||||
|
<code-example header="src/app/cart.service.ts" path="getting-started/src/app/cart.service.1.ts"></code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/cart.service.ts" region="props"></code-example>
|
||||||
|
|
||||||
|
1. Define methods to add items to the cart, return cart items, and clear the cart items:
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/cart.service.ts" region="methods"></code-example>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
To check: StackBlitz includes the constructor. If it's important (and not obvious) that the methods be below the constructor, then we should show it or say something.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
* The `addToCart()` method appends a product to an array of `items`.
|
||||||
|
|
||||||
|
* The `getItems()` method collects the items added to the cart and returns each item with its associated quantity.
|
||||||
|
|
||||||
|
* The `clearCart()` method returns an empty array of 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.
|
||||||
|
|
||||||
|
<code-example header="src/app/product-details/product-details.component.ts" path="getting-started/src/app/product-details/product-details.component.ts" region="cart-service">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
1. Inject the cart service.
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/product-details/product-details.component.ts" region="inject-cart-service">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
To do: Consider defining "inject" and describing the concept of "dependency injection"
|
||||||
|
-->
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/product-details/product-details.component.ts" region="add-to-cart"></code-example>
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<code-example header="src/app/product-details/product-details.component.html" path="getting-started/src/app/product-details/product-details.component.html">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
1. To see the new "Buy" button, refresh the application and click on a product's name to display its details.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src='generated/images/guide/getting-started/product-details-buy.png' alt="Display details for selected product with a Buy button">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
1. Click the "Buy" button. The product is added to the stored list of items in the cart, and a message is displayed.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src='generated/images/guide/getting-started/buy-alert.png' alt="Display details for selected product with a Buy button">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
|
||||||
|
<code-example header="src/app/cart/cart.component.ts" path="getting-started/src/app/cart/cart.component.1.ts"></code-example>
|
||||||
|
|
||||||
|
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`:
|
||||||
|
|
||||||
|
<code-example header="src/app/app.module.ts" path="getting-started/src/app/app.module.ts" region="cart-route">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
To do: Can we shorten the example code to remove the extra at the bottom?
|
||||||
|
-->
|
||||||
|
|
||||||
|
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`.)
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src='generated/images/guide/getting-started/cart-works.png' alt="Display cart page before customizing">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
<code-example header="src/app/cart/cart.component.ts" path="getting-started/src/app/cart/cart.component.2.ts" region="imports">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
1. Inject the `CartService` to manage cart information.
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/cart/cart.component.2.ts" region="inject-cart">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
1. Define the `items` property to store the products in the cart.
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/cart/cart.component.2.ts" region="items">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/cart/cart.component.3.ts" region="props-services">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
1. Update the template with a header ("Cart"), and use a `<div>` with an `*ngFor` to display each of the cart items with its name and price.
|
||||||
|
|
||||||
|
The resulting `CartComponent` template should look like this:
|
||||||
|
|
||||||
|
<code-example header="src/app/cart/cart.component.html" path="getting-started/src/app/cart/cart.component.2.html" region="prices">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src='generated/images/guide/getting-started/cart-page-full.png' alt="Cart page with products added">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
To do: New screen shot. No shipping prices link yet. Show a few products in the cart.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
|
Learn more: See [Introduction to Services and Dependency Injection](guide/architecture-services "Architecture > Intro to Services and DI") for more information about services.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Retrieve shipping prices
|
||||||
|
<!-- Accessing data with the HTTP client -->
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<code-example header="src/assets/shipping.json" path="getting-started/src/assets/shipping.json">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
<code-example header="src/app/app.module.ts" path="getting-started/src/app/app.module.ts" region="http-client-module-import">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
1. Add `HttpClientModule` to the `imports` array of the app module (`@NgModule`).
|
||||||
|
|
||||||
|
This registers Angular's `HttpClient` providers globally.
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/app.module.ts" region="http-client-module">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
To do: Should ReactiveFormsModule already be here? Currently, it is in the starter stackblitz, so this doc assumes it is already included and not added in the forms section.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
To do: Should ReactiveFormsModule already be here?
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Enable HttpClient for cart service
|
||||||
|
|
||||||
|
1. Open `cart.service.ts`.
|
||||||
|
|
||||||
|
1. Import `HttpClient` from the `@angular/common/http` package.
|
||||||
|
|
||||||
|
<code-example header="src/app/cart.service.ts" path="getting-started/src/app/cart.service.ts" region="import-http">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
1. Inject `HttpClient` into the constructor of the `CartService` component class:
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/cart.service.ts" region="inject-http">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
### 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).
|
||||||
|
|
||||||
|
<code-example header="src/app/cart.service.ts" path="getting-started/src/app/cart.service.ts" region="get-shipping"></code-example>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
|
Learn more: See the [HttpClient guide](guide/http "HttpClient guide") for more information about Angular's HttpClient.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
|
||||||
|
<code-example header="src/app/shipping/shipping.component.ts" path="getting-started/src/app/shipping/shipping.component.1.ts"></code-example>
|
||||||
|
|
||||||
|
1. In `app.module.ts`, add a route for shipping. Specify a `path` of `shipping` and a component of `ShippingComponent`.
|
||||||
|
|
||||||
|
<code-example header="src/app/app.module.ts" path="getting-started/src/app/app.module.ts" region="shipping-route"></code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<code-example header="src/app/shipping/shipping.component.ts" path="getting-started/src/app/shipping/shipping.component.ts" region="imports"></code-example>
|
||||||
|
|
||||||
|
1. Define a `shippingCosts` property.
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/shipping/shipping.component.ts" region="props"></code-example>
|
||||||
|
|
||||||
|
1. Inject the cart service into the `ShippingComponent` class:
|
||||||
|
|
||||||
|
```
|
||||||
|
constructor(
|
||||||
|
private cartService: CartService
|
||||||
|
) { }
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
To do: Create docregion
|
||||||
|
-->
|
||||||
|
|
||||||
|
1. Set the `shippingCosts` property using the `getShippingPrices()` method from cart service.
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/shipping/shipping.component.ts" region="ctor"></code-example>
|
||||||
|
|
||||||
|
1. Update the shipping component's template to display the shipping types and prices using async pipe:
|
||||||
|
|
||||||
|
<code-example header="src/app/shipping/shipping.component.html" path="getting-started/src/app/shipping/shipping.component.html"></code-example>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
To decide: Should we describe async pipe
|
||||||
|
-->
|
||||||
|
|
||||||
|
1. Add a link from the cart page to the shipping page:
|
||||||
|
|
||||||
|
<code-example header="src/app/cart/cart.component.html" path="getting-started/src/app/cart/cart.component.2.html"></code-example>
|
||||||
|
|
||||||
|
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.)
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src='generated/images/guide/getting-started/cart-empty-with-shipping-prices.png' alt="Cart with link to shipping prices">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Click on the link to navigate to the shipping prices.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src='generated/images/guide/getting-started/shipping-prices.png' alt="Display shipping prices">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 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").
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
<code-example header="src/app/cart/cart.component.ts" path="getting-started/src/app/cart/cart.component.ts" region="imports">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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,
|
||||||
|
) { }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
To do: Replace with docregion
|
||||||
|
-->
|
||||||
|
|
||||||
|
1. In the `CartComponent` class, define the `checkoutForm` property to store the form model.
|
||||||
|
|
||||||
|
```
|
||||||
|
export class CartComponent {
|
||||||
|
items;
|
||||||
|
checkoutForm;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<!--
|
||||||
|
To do: Replace with docregion
|
||||||
|
-->
|
||||||
|
|
||||||
|
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: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<!--
|
||||||
|
To do: Replace with docregion
|
||||||
|
--->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The resulting `CartComponent` class should look like this:
|
||||||
|
|
||||||
|
<code-example header="src/app/cart/cart.component.ts" path="getting-started/src/app/cart/cart.component.ts" region="props-services">
|
||||||
|
</code-example>
|
||||||
|
-->
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<code-example header="src/app/cart/cart.component.ts" path="getting-started/src/app/cart/cart.component.ts">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```
|
||||||
|
<form [formGroup]="checkoutForm">
|
||||||
|
|
||||||
|
<button class="button" type="submit">Purchase</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Note: The preview might contain an error message, which will be resolved by the following steps.
|
||||||
|
To do: Replace with docregion
|
||||||
|
If you define the name and address fields here, it generates and error in the preview.
|
||||||
|
I had to add the formGroup property before the message would resolve.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
1. Use a `formGroup` property binding to bind the `checkoutForm` to the `form` tag in the template.
|
||||||
|
|
||||||
|
```
|
||||||
|
<form [formGroup]="checkoutForm">
|
||||||
|
...
|
||||||
|
</form>
|
||||||
|
```
|
||||||
|
|
||||||
|
To do: Replace with docregion
|
||||||
|
-->
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```
|
||||||
|
<form [formGroup]="checkoutForm" (ngSubmit)="onSubmit(checkoutForm.value)">
|
||||||
|
...
|
||||||
|
</form>
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
To do: Replace with docregion
|
||||||
|
-->
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<code-example header="src/app/cart/cart.component.html" path="getting-started/src/app/cart/cart.component.html" region="checkout-form-2">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
After putting a few items in the cart, users can now review their items, enter name and address, and submit their purchase:
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src='generated/images/guide/getting-started/cart-with-items-and-form.png' alt="Cart page with checkout form">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
<div class="callout is-helpful">
|
||||||
|
<header>New to web development?</header>
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@a new-project}
|
||||||
|
## Create a new project
|
||||||
|
|
||||||
|
<h4>
|
||||||
|
<live-example name="getting-started-v0" noDownload>Click here to create new project in StackBlitz.</live-example>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<img src="generated/images/guide/getting-started/new-project.png" alt="New Angular project in StackBlitz">
|
||||||
|
-->
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="generated/images/guide/getting-started/new-app.png" alt="Starter online store app">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="callout is-helpful">
|
||||||
|
<header>StackBlitz tips</header>
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{@a template-syntax}
|
||||||
|
## Template syntax
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Angular extends HTML with a template syntax that gives components control over the display of content.
|
||||||
|
This section introduces five things you can do in an Angular template to affect what your user sees, based on the component's state and behavior:
|
||||||
|
-->
|
||||||
|
|
||||||
|
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 `<div>`, as shown below:
|
||||||
|
|
||||||
|
<code-example header="src/app/product-list/product-list.component.html" path="getting-started/src/app/product-list/product-list.component.2.html" region="ngfor">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
`*ngFor` causes the `<div>` to be repeated for each product in the list.
|
||||||
|
|
||||||
|
<div class="alert is-helpful">
|
||||||
|
`*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.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
1. To display the names of the products, use the interpolation syntax {{ }}. Interpolation renders a property's value as text. Inside the `<div>`, add an `<h3>` heading to display the interpolation of the product's name property:
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/product-list/product-list.component.2.html" region="interpolation">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
The preview pane immediately updates to display the name of each product in the list.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="generated/images/guide/getting-started/template-syntax-product-names.png" alt="Product names added to list">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/product-list/product-list.component.2.html">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
To do: Description and code don't match exactly. Do we want to just use product name as the anchor hover text to show a simple property or append "details" to show an expression? Also affects screen shot.
|
||||||
|
-->
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="generated/images/guide/getting-started/template-syntax-product-anchor.png" alt="Product name anchor text is product name property">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/product-list/product-list.component.3.html">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="generated/images/guide/getting-started/template-syntax-product-description.png" alt="Product descriptions added to list">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/product-list/product-list.component.4.html">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Each product now has a "Share" button:
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="generated/images/guide/getting-started/template-syntax-product-share-button.png" alt="Share button added for each product">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Test the "Share" button:
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="generated/images/guide/getting-started/template-syntax-product-share-alert.png" alt="Alert box indicating product has been shared">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
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 ( )
|
||||||
|
|
||||||
|
|
||||||
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
|
Learn more: See the [Template Syntax guide](guide/template-syntax "Template Syntax") for information about the full capabilities of Angular's template syntax.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{@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.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
### Class definition
|
||||||
|
|
||||||
|
Let's take a quick look a the product list component's class definition:
|
||||||
|
|
||||||
|
1. In the `product-list` directory, open `product-list.component.ts`.
|
||||||
|
|
||||||
|
1. Notice the `@Component` decorator. This 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 such as `app-`, followed by the component name.
|
||||||
|
|
||||||
|
* The template and style filename also are provided here. By convention each of the component's parts is in a separate file, all in the same directory and with the same prefix.
|
||||||
|
|
||||||
|
1. The component definition also includes an exported class, which handles functionality for the component. This is where the product list data and `Share()` method are defined.
|
||||||
|
|
||||||
|
### Composition
|
||||||
|
-->
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="generated/images/guide/getting-started/app-components.png" alt="Online store with three components">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
* `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.
|
||||||
|
|
||||||
|
|
||||||
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
|
Learn more: See [Introduction to Components](guide/architecture-components "Architecture > Introduction to Components") for more information about components and how they interact with templates.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{@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`.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="generated/images/guide/getting-started/generate-component.png" alt="StackBlitz command to generate component">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
<code-example header="src/app/product-alerts/product-alerts.component.ts" path="getting-started/src/app/product-alerts/product-alerts.component.1.ts" region="as-generated"></code-example>
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/product-alerts/product-alerts.component.1.ts" region="imports"></code-example>
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/product-alerts/product-alerts.component.1.ts" region="input-decorator"></code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<code-example header="src/app/product-alerts/product-alerts.component.html" path="getting-started/src/app/product-alerts/product-alerts.component.1.html"></code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<code-example header="src/app/product-list/product-list.component.html" path="getting-started/src/app/product-list/product-list.component.5.html" region="app-product-alerts"></code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="generated/images/guide/getting-started/product-alert-button.png" alt="Product alert button added to products over $700">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{@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`:
|
||||||
|
|
||||||
|
<code-example header="src/app/product-alerts/product-alerts.component.ts" path="getting-started/src/app/product-alerts/product-alerts.component.ts" region="imports"></code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/product-alerts/product-alerts.component.ts" region="input-output"></code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<code-example header="src/app/product-alerts/product-alerts.component.html" path="getting-started/src/app/product-alerts/product-alerts.component.html"></code-example>
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<code-example header="src/app/product-list/product-list.component.ts" path="getting-started/src/app/product-list/product-list.component.ts" region="on-notify"></code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<code-example header="src/app/product-list/product-list.component.html" path="getting-started/src/app/product-list/product-list.component.6.html" region="on-notify"></code-example>
|
||||||
|
|
||||||
|
1. Try out the "Notify Me" button:
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="generated/images/guide/getting-started/product-alert-notification.png" alt="Product alert notification confirmation dialog">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{@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.
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
||||||
|
<code-example header="src/app/app.module.ts" path="getting-started/src/app/app.module.ts" region="product-details-route">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
<code-example header="src/app/product-list/product-list.component.html" path="getting-started/src/app/product-list/product-list.component.html" region="router-link">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
To do: I see a comment line with ellipses between the closing of h3 and div. It's an interesting way to show that we've clipped out some code. Should we use this elsewhere?
|
||||||
|
-->
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="generated/images/guide/getting-started/product-details-works.png" alt="Product details page with updated URL">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
|
||||||
|
<code-example header="src/app/product-details/product-details.component.1.ts" path="getting-started/src/app/product-details/product-details.component.1.ts" region="imports">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
1. Define the `product` property and inject the `ActivatedRoute` into the constructor.
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/product-details/product-details.component.1.ts" region="props-methods">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
To do: This is the first time we inject anything into a component. Should we mention it here? There's also a comment about maybe explaining it a bit in the services section (in data.md).
|
||||||
|
-->
|
||||||
|
|
||||||
|
1. In the `ngOnInit()` method, _subscribe_ to route params and fetch the product based on the `productId`.
|
||||||
|
|
||||||
|
<code-example path="getting-started/src/app/product-details/product-details.component.1.ts" region="get-product">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
<code-example header="src/app/product-details/product-details.component.html" path="getting-started/src/app/product-details/product-details.component.html" region="details">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="generated/images/guide/getting-started/product-details-routed.png" alt="Product details page with updated URL and full details displayed">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
|
Learn more: See [Routing & Navigation](guide/router "Routing & Navigation") for more information about the Angular router.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
npm install -g @angular/cli
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{@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:
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
ng new my-app
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
cd my-app
|
||||||
|
ng serve --open
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src='generated/images/guide/cli-quickstart/app-works.png' alt="Welcome to my-app!">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
|
@ -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.
|
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.
|
|
||||||
|
|
||||||
|
<div class="callout is-helpful">
|
||||||
|
<header>Getting Started - Stackblitz</header>
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
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}
|
{@a devenv}
|
||||||
|
|
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 23 KiB |