[上課筆記] Angular: Getting Started

Angular: Getting Started

https://github.com/DeborahK/Angular-GettingStarted

“Angular: Getting Started” Problem Solver

 建立新專案 用Agnular CLI 
ng new => 會問專案名稱 輸入APM
路徑 C:\Test\ng new 這樣產生出來的會是 C:\Test\APM\src

 src資料夾下預設會有app資料夾
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';

@NgModule({
  imports: [BrowserModule],         //載入Module , 包含第三方套件 , 自己寫的 
  declarations: [AppComponent], //載入Component
  bootstrap: [AppComponent]      //設定啟動的Component
})

app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'APM';
}

..\index.html
<html>
<head></head>
<body>
  <app-root></app-root>
</body>
</html>

 用npm 安裝 ref NPM 新手教學 , npm init ,npm Installpackage-lock.json
npm i bootstrap font-awesome
可以在 ..\styles.css 加上
@import "~bootstrap/dist/css/bootstrap.min.css";
@import "~font-awesome/css/font-awesome.min.css";
未知 這樣可以找到路徑?

 用Agnular CLI 建立component ( 會自動加入參考 )
ng g c products\product-list
C:\Test\APM\src\app\products\
product-list.component.ts
product-list.component.html

建立 product-detail  Component
ng g c products/product-detail --flat  ( 加上 --flat 就不會建立product-detail資料夾 直接產生到products資料夾 )

 built-in directive
*ng**** 是屬於BrowserModule在app.module.ts時有載入 所以才能用
*ngIf 可以控制 div 是否顯示
*ngFor='let item of list' 類似foreach

 Property Binding
 <img
    [src]='item.imageUrl'
    title="name is {{ item.productName }}"
    [style.width.px]='imageWidth'
    [style.margin.px]='imageMargin'
 />

 Event Binding  ref Event reference
<button class="btn btn-primary" (click)="toggleImage()">
      {{showImage ? 'Hide' : 'Show'}} Image
</button>

Two-way Binding   [()] Banana in a Box --哪裡像了T_T
<input type="text" [(ngModel)]="listFilter" />
<h4>Filtered by: {{ listFilter }}</h4>
效果就是 輸入文字後 h4 同時改變
two-way binding 需要載入  app.module.ts
import { FormsModule } from "@angular/forms"; 

Pipe  ref  Pipe DatePipe CurrencyPipe
資料在顯示之前可以做處理 轉大小寫 ,日期 ,金錢格式 
<td>{{ item.productCode | lowercase }}</td>
<td>{{ item.releaseDate | date: "yyyy/MM/dd" }}</td>
<td>{{ item.price | currency: "USD":"symbol":"1.2-2" }}</td>

Custom Pipe ref Custom pipes , 
ng g pipe app\shared\ConvertToSpacesPipe

Interfaces 給物件強行別 這樣就有提示 包含html 

Component 的 style 是獨立的不會影響到別的Component

 Component Lifecycle ref lifecycle-hooks , Lifecycle Hooks 學習筆記 (一)  , [Angluar 大師之路] Day 04 - 認識 Angular 的生命週期​
Oninit 跟 建構子 的差異
建構子 就是拿來 注入服務
Oninit  拿來取得內容像是call api 拿下拉選單的資料

​​  Component 可以在套Component 也可以傳遞參數
外面Component
<td><app-star [rating]="item.starRating"></app-star></td>
裡面Component ( app-star )
@Input() rating: number; 
必須要加Input 才能接到外面傳來的值

裡面Component ( app-star ) 也可以傳值到外面的Component
@Output() ratingClicked: EventEmitter<string> = new EventEmitter<string>();
onClick(): void {
    this.ratingClicked.emit(`The rating ${this.rating} was clicked!`);
}
外面的Component 
<app-star  [rating]="item.starRating"  (ratingClicked)="onRatingClicked($event)"></app-star>
onRatingClicked(message: string): void {
    alert(message);
}
說明一下 EventEmitter 的泛型是string 因為回傳內容是字串 ( 訂閱事件的概念 觸發 emit 就會往上呼叫 )
當 onClick 被觸發後 ratingClicked emit 叫外面的Component onRatingClicked 接收 The rating ${this.rating} was clicked!
就會被alert

 建立Service
ng g s products\products
Angular 6 之後 可以用
@Injectable({
  providedIn: "root"
})

 用 HttpClient 跟 RxJS 跟API 拿資料
RxJS 是另一個要學習的請看 30 天精通 RxJSRxJS in Angular: Reactive Development
product-list.component.ts
ngOnInit() {
    this.productsService.getProducts().subscribe({
      next: products => {
        this.products = products;
        console.log("products length =>" + products.length);
        this.filteredProducts = this.products;
      },
      error: err => (this.errorMessage = err)
    });
    console.log("OnInit done");
  }
products.service.ts
getProducts(): Observable<IProduct[]> {
    return this.http.get<IProduct[]>(this.productUrl).pipe(
      tap(data => console.log("pipe pipe")),
      catchError(this.handleError)
    );
  }
結果如上 , 所以​RxJS是支援非同步的 這邊只是想說 這裡坑真的很大 

 Routing Basics
app.component.ts
import { RouterModule } from "@angular/router";
RouterModule.forRoot([
      { path: "products", component: ProductListComponent },
      { path: "products/:id", component: ProductDetailComponent },
      { path: "welcome", component: WelcomeComponent },
      { path: "", redirectTo: "welcome", pathMatch: "full" },
      { path: "**", redirectTo: "welcome", pathMatch: "full" }
    ])
注意先後順序 匹配到就不會往下囉
app.component.html
<nav class="navbar navbar-expand navbar-light bg-light">
  <ul class="nav nav-pills">
    <li><a class="nav-link" routerLinkActive="active" [routerLink]="['/welcome']">Home</a></li>
    <li><a class="nav-link" routerLinkActive="active" [routerLink]="['/products']">Product List</a></li>
  </ul>
</nav>
<div class="container">
  <router-outlet></router-outlet>
</div>
改成用Router後之前 @Component => selector 就不需要 ( 指的是頁面的進入點 因為被  router-outlet 取代)
設定好之後 流程如下
點連結 [routerLink]="['/products']"
RouterModule.forRoot 找到  { path: "products", component: ProductListComponent }
把ProductListComponent的內容放到<router-outlet></router-outlet>

直接打網址 localhost:4200/item
RouterModule.forRoot 找到  { path: "**", redirectTo: "welcome", pathMatch: "full" } => 因為沒有所以會被"**"匹配成功
WelcomeComponent 的內容放到<router-outlet></router-outlet>

 Routing Basics 2 
產品列表 連結到 產品明細頁
<a [routerLink]="['/products', item.productId]">
  {{ item.productName }}
</a>
RouterModule.forRoot([
 { path: "products/:id", component: ProductDetailComponent },
product-detail.component.ts
import { ActivatedRoute, Router } from '@angular/router';
constructor(private route: ActivatedRoute, private router: Router) { }
 ngOnInit() {
    const id = +this.route.snapshot.paramMap.get('id');
    this.pageTitle = 'Product Detail' + id;
  }
 onBack(): void {
    this.router.navigate(['/products']);
  }
要接受參數需要ActivatedRoute , 加入回上一頁的功能需要Router 

​​ Router Guards ref Router Guards
管理連結是否可進入 像是沒有登入不能進入後台....等
舉例 
CanActivate : 進入 頁面前檢查
CanDeactivate : 離開 頁面前檢查
CLI 產生
ng g g products/product-detail
product-detail.guard.ts
constructor(private router: Router) { }
canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    // product/1
    //  [0]   [1]
    const id = +next.url[1].path;
    if (isNaN(id) || id < 1) {
      alert('Invalid product Id');
      this.router.navigate(['/products']);
      return false;
    }
    return true;
  }
回傳簡單就是 true 可進入 , false不可以

 分開 Module  ref 透過Module組織管理你的程式
建立ProductModule
ng g m products/product --flat -m app
--flat 表示不建立product 資料夾
-m 表示import到 app.module

建立SharedModule
ng g m shared/shared --flat -m  products/product.module
--flat 表示不建立shared 資料夾
-m 表示import到 product.module
shared  module 可以把常用的module exports出來 這樣大家都可以用
像是 CommonModule, FormsModule , StarComponent
整理一下
 - 不要重複 imports component
 - 模組沒有繼承的概念 
 - CommonModule 可以用*ngIf ..等
 - 外面模組是不能用內部模組的 要放到exports
ProductModule import  SharedModule
ProductModule 預設是不能用 StarComponent 要用的話就要
exports: [ StarComponen t]
這裡挺亂的需要再加強

 CLI 技巧
ng g c hello -d
不會真的產生檔案 可以預覽產生的路徑是否是你想要的 需不需要 --flat

ng b --prod 會產生dist資料夾
遇到IIS 由root網址進去ok 但是F5就404
可以看
如何將 Angular 2 含有路由機制的 SPA 網頁應用程式部署到 IIS 網站伺服器
   

如果內容有誤請多鞭策謝謝