Materiały na warsztat #1 2020.11.12

Sprawdź wersje bibliotek

node -v
ng version
firebase -v

Zaczynamy

ng new Start-Shop
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS

cd Start-Shop
code .

ng serve --open

Style

Plik: index.html

<link
      href="https://fonts.googleapis.com/icon?family=Material+Icons"
      rel="stylesheet"
    />

Plik: styles.scss

app-header {
  width: 100%;
  height: 68px;
  background-color: #1976d2;
  padding: 16px;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
}

* {
  font-family: 'Roboto', Arial, sans-serif;
  color: #616161;
  box-sizing: border-box;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  margin: 0;
}

.container {
  display: flex;
  flex-direction: row;
}

router-outlet + *  {
  padding: 0 16px;
}

/* Button */
.button, button {
  display: inline-flex;
  align-items: center;
  padding: 8px 16px;
  border-radius: 2px;
  font-size: 14px;
  cursor: pointer;
  background-color: #1976d2;
  color: white;
  border: none;
}

.button:hover, button:hover {
  opacity: 0.8;
  font-weight: normal;
}

.button:disabled, button:disabled {
  opacity: 0.5;
  cursor: auto;
}

a {
  cursor: pointer;
  color: #1976d2;
  text-decoration: none;
}

a:hover {
  opacity: 0.8;
}

Pierwszy komponent

ng generate component header

Plik: header.component.html

<a [routerLink]="['/']">
  <h1>Sklep z antykami</h1>
</a>

<a class="button shopping-button"><i class="material-icons">shopping_cart</i>Koszyk</a>

Plik: app.component.html

<app-header></app-header>

<div class="container">
  <router-outlet></router-outlet>
</div>

Plik: header.component.scss

h1 {
  color: white;
  margin: 0;
}

.shopping-button {
  background-color: white;
  color: #1976d2;
}

.shopping-button i.material-icons {
  color: #1976d2;
  padding-right: 4px;
}

Produkty do sklepu

Plik: antics.ts

export const antics = [
  {
    name: 'Claude Monet - Kobieta z parasolem',
    price: 1450,
    description: 'Madame Monet i jej syn, czasami znana jako Spacer, to obraz olejny na płótnie autorstwa Claude'a Moneta z 1875 roku.'
  },
  {
    name: 'Claude Monet - Impresja, wschód słońca',
    price: 5800,
    description: 'Impresja, wschód słońca – obraz namalowany przez Claude\’a Moneta, od tytułu którego nazwano kierunek w malarstwie – impresjonizm. Datowany na 1872 rok'
  },
  {
    name: 'Claude Monet - Maki',
    price: 7600,
    description: 'Maki, obraz olejny.'
  }
];

Lista obrazów

ng g c antic-list

Plik: app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AnticListComponent } from './antic-list/antic-list.component';

const routes: Routes = [
  { path: '', component: AnticListComponent },
];

Plik: antic-list.component.ts

import { Component, OnInit } from '@angular/core';
import { antics } from '../antics';

@Component({
  selector: 'app-antic-list',
  templateUrl: './antic-list.component.html',
  styleUrls: ['./antic-list.component.scss']
})
export class AnticListComponent implements OnInit {

  antics = antics;

  constructor() { }

  ngOnInit(): void {
  }

}

Plik: antic-list.component.html

<h2>Obrazy</h2>

<div *ngFor="let antic of antics">
  <h3>
    <a [title]="'Szczegóły' + antic.name ">
      {{ antic.name }}
    </a>
  </h3>

  <p *ngIf="antic.description">Opis: {{ antic.description }}</p>
</div>

Strona produktu

ng g c antic-details

Plik: app-routing.module.ts

import { AnticDetailsComponent } from './antic-details/antic-details.component';
import { AnticListComponent } from './antic-list/antic-list.component';

const routes: Routes = [
  { path: '', component: AnticListComponent },
  { path: 'antics/:anticId', component: AnticDetailsComponent },
];

Plik: antic-list.component.html

<div *ngFor="let antic of antics; index as anticId">
  <h3>
    <a [title]="antic.name + ' details'" [routerLink]="['/antics', anticId]">

Plik: antic-details.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

import { antics } from '../antics';

@Component({
  selector: 'app-antic-details',
  templateUrl: './antic-details.component.html',
  styleUrls: ['./antic-details.component.scss'],
})
export class AnticDetailsComponent implements OnInit {
  antic;

  constructor(private route: ActivatedRoute) {}

  ngOnInit(): void {
    this.route.paramMap.subscribe((params) => {
      this.antic = antics[+params.get('anticId')];
    });
  }
}

Plik: antic-details.component.html

<h2>Opis obrazu</h2>

<div *ngIf="antic">
  <h3>{{ antic.name }}</h3>
  <h4>{{ antic.price | currency }}</h4>
  <p>{{ antic.description }}</p>
</div>

Koszyk zakupów

ng g s cart

Plik: cart.service.ts

export class CartService {

  items = [];

  addToCart(antic) {
    this.items.push(antic);
  }

  getItems() {
    return this.items;
  }

  clearCart() {
    this.items = [];
    return this.items;
  }
}

Plik: antic-details.component.ts

  constructor(
    private route: ActivatedRoute,
    private cartService: CartService
  ) { }
  addToCart(antic) {
    this.cartService.addToCart(antic);
    window.alert('Antyk został dodany do koszyka zakupów!');
  }

Plik: antic-details.component.html

 <button (click)="addToCart(antic)">Kup</button>
ng g c cart

Plik: app-routing.module.ts

const routes: Routes = [
  { path: '', component: AnticListComponent },
  { path: 'antics/:anticId', component: AnticDetailsComponent },
  { path: 'cart', component: CartComponent },
];

Plik: header.component.html

<a class="button shopping-button" routerLink="/cart">
  <i class="material-icons">shopping_cart</i>
  Koszyk
</a>

Plik: cart.component.ts

export class CartComponent implements OnInit {

  items;

  constructor(private cartService: CartService) { }

  ngOnInit(): void {
    this.items = this.cartService.getItems();
  }

}

Plik: cart.component.html

<h3>Koszyk zakupów</h3>

<div class="cart-item" *ngFor="let item of items">
  <span>{{ item.name }}</span>
   
  <span>{{ item.price | currency }}</span>
</div>

Opcje dostawy

Plik: src/assets/shipping.json

[
  {
    "type": "Poczta",
    "price": 9.9
  },
  {
    "type": "Paczkomat",
    "price": 11.0
  },
  {
    "type": "Kurier",
    "price": 14.0
  }
]

Plik: app.module.ts

...
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';
....
  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule
  ],
...

Plik: cart.service.ts

  constructor(
    private http: HttpClient
  ) { }

...

  getShippingPrices() {
    return this.http.get('/assets/shipping.json');
  }
ng g c shipping

Plik: app-routing.module.ts

const routes: Routes = [
  { path: '', component: AnticListComponent },
  { path: 'antics/:anticId', component: AnticDetailsComponent },
  { path: 'cart', component: CartComponent },
  { path: 'shipping', component: ShippingComponent },
];

Plik: shipping.component.ts

import { Component, OnInit } from '@angular/core';
import { CartService } from '../cart.service';

@Component({
  selector: 'app-shipping',
  templateUrl: './shipping.component.html',
  styleUrls: ['./shipping.component.scss'],
})
export class ShippingComponent implements OnInit {
  shippingCosts;

  constructor(private cartService: CartService) { }

  ngOnInit(): void {
    this.shippingCosts = this.cartService.getShippingPrices();
  }
}

Plik: shipping.component.html

<h3>Koszty dostawy</h3>

<div class="shipping-item" *ngFor="let shipping of shippingCosts | async">
  <span>{{ shipping.type }}</span>
  <span>{{ shipping.price | currency }}</span>
</div>

Plik: cart.component.html

...
<p>
  <a routerLink="/shipping">Koszty dostawy</a>
</p>

Adres dostawy

Plik: app.module.ts

import { ReactiveFormsModule } from '@angular/forms';
...

  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule,
    ReactiveFormsModule,
  ],

Plik: cart.component.ts

export class CartComponent implements OnInit {
  items;
  checkoutForm;

  constructor(
    private cartService: CartService,
    private formBuilder: FormBuilder
  ) {
    this.checkoutForm = this.formBuilder.group({
      name: '',
      address: '',
    });
  }

  ....

  onSubmit(customerData) {
    this.items = this.cartService.clearCart();
    this.checkoutForm.reset();

    alert(`Twoje zamówienie ${customerData.name} zostało wysłane do ${customerData.address}.`);
  }
}

Plik: cart.component.html

...
<form [formGroup]="checkoutForm" (ngSubmit)="onSubmit(checkoutForm.value)">
  <div>
    <label for="name"> Nazwisko </label>
    <input id="name" type="text" formControlName="name" />
  </div>

  <div>
    <label for="address"> Adres </label>
    <input id="address" type="text" formControlName="address" />
  </div>

  <button class="button" type="submit">Kup</button>
</form>

Publikacja

https://console.firebase.google.com

firebase projects:list
ng add @angular/fire
ng deploy

Idź do adresu „Hosting URL:”

Praca domowa

  1. Dodać komponent CartSummary i przenieść do niego przycisk „Kup”. Ma on wyświetlać podsumowanie zakupów i adres dostawy.
  2. Utwórz wybieranie sposoby dostawy. Można dodać do każdej opcji dostawy przycisk „wybierz” w komponencie shipping. Dane przesłać do cart.service.ts i pokazywać w CartSummary.
  3. Dopisać style dla klas shipping-item, cart-item.

Video z webinaria

Szafrański Michał
Architekt oprogramowania, bujający w chmurach obliczeniowych.