備忘錄_20160105(定位) 修改 回首頁

程式 2020-02-19 17:15:02 1582103702 100
閱讀 Angular 的筆記 PART 01

閱讀 Angular 的筆記 PART 01
參考書籍 Lim, Greg. Beginning Angular with Typescript (updated to Angular 6). Kindle Edition. 

Angular is a framework for creating Single Page Applications (SPA).

The four main building blocks of an Angular app are modules, components, directives and services.

App
  Module_i (i=1~M)      (e.q. store front, post, message, followers module)
    Component_j (j=1~N) (e.q. search bar, sidebar, products component)
      [1].html template (*.html) (template 是動態的,依據 directives 的不同,會轉換成不同的 DOM,包含 appearance 與 behavior)
      [2].component class (*.ts) (內部連到 .html 與 .css) (data + logic => control => html template)
      [3].other componets
      [4].驗證、讀取、儲存請透過 Service 來處理,Component 純粹 render 就好

Directives
  Angular has a bunch of directives that alter the layout (e.g. ngIf), modify classes (ngClass) or styles (ngStyles) of DOM elements. You can also write your own custom directives.
  (e.g. ngIf, ngClass, ngStyle, ......)

Services
  A service is a class with a well defined specific function that your application needs.
  (e.q. logging, talking to backend servers (e.g., Node, ASP.NET, Ruby on Rails) to get or save data, validating user input and so on.)
  (Services provide their functionality to be consumed by components.)

●installation

[01].安裝 Node
  從 nodejs.org 下載安裝 NodeJS (裡頭有我們要的 npm - Node Package Manager)
  [cmd] node -v 可看 nodejs 版本
  [cmd] npm -v 可看 npm 版本

[02].安裝 TypeScript
  [cmd] npm install -g typescript (g 代表 global,在任何路徑都能使用)

[03].安裝 Typings (似乎已經是 不推薦使用/deprecated)
  Typings is a module that allows us to bring in Javascript libraries into TypeScript.
  [cmd] npm install -g typings

[04].安裝 Angular CLI 
  CLI:Command Line Interface
  讓建立專案、增加檔案、測試、佈署等工作可以輕鬆一些的工具
  [cmd] npm install -g @angular/cli

[05].安裝 Visual Studio Code

[06].安裝瀏覽器 (Chrome, Firefox, ......)

●test

[01].建立一專案
  [cmd] git config --global user.email "you@example.com" (忽略 global 參數的話,此設定只在此專案適用)
  [cmd] git config --global user.name "Your Name" (忽略 global 參數的話,此設定只在此專案適用)
  [cmd] ng new PROJECT_NAME

[02].測試一專案
  [cmd] cd PROJECT_NAME
  [cmd] ng serve --open (http://localhost:4200/)

[03].編譯一專案
  [cmd] cd PROJECT_NAME
  [cmd] ng build --prod (編譯後的資料夾在 dist/)(prod 代表 production)(注意,產出的 index.html 會看不到東西,修改 base href="",就能看到東西。改成正確路徑更好)

[04].用 Vsiaul Studio Code 開啟專案資料夾
  Terminal->New Terminal 開啟內部的 cmd 視窗
  ./src/main.ts 應用程式的入口
  ./src/app/app.module.ts 應用程式的根模組,讓 Angular 知道怎麼組合我們的應用程式
  ./tsconfig.json 本應用程式的TypescriptCompiler組態設定檔
  ./package.json 本應用程式的NodePackage組態設定檔
  ./node_modules 若在package.json中有用到第三方模組,則Node.js會建立本資料夾,並將第三方模組通通放進來

[05].修飾子/decorator
  *.ts 裡面的修飾子(如 @NgModule, @Component)會讓 Angular 知道現在是 module 或是 component

  app.module.ts 修飾子的重要屬性略述
    declarations - to declare which components, directives or pipes belong to this module.
    exports – to specify which components should be visible and useable by other modules.
    imports - to specify what other modules whose exported classes are needed by components declared in this module.
      簡介內建模組
      BrowserModule -- 瀏覽器相關功能,ngIf、ngFor 等功能
      FormsModule -- 表單相關功能
      HttpClientModule -- http 存取
    providers - to specify any application wide services we want to use

  app.component.ts 修飾子的重要屬性略述
    templateUrl - app.component.html 樣板
    styleUrls - app.component.sass 或 app.component.css 樣式
    這裡面的 class 透過 屬性/properties 和 方法/methods 與 view 互動

[06].多個 component 練習(逐漸了解 angular 的用法)

  基礎目錄 ./src

  index.html (custom tag 與 selector 相關聯)
  main.ts (import AppModule)

    app/app.module.ts (import+declaration+bootstrap AppComponent,ProductsComponent)

      app/app.component.ts (selector, template, properties)
      app/products.component.ts (selector, template, properties)

[07].整理思緒,重點在 template

  在 class 的 constructor 函數中,運用 console.log 來觀察 Angular 的行為。
  Angular 依著 template 的內容,把會用到的 class 全部都先實體化,然後再依 template 的內容,逐步產生出 DOM。
  此外,template 裡面 tag 的先後順序,影響物件實體化的順序。

  假如您有用到 singleton 物件的 public 變數(如把 service 相依注入 Dependency injection 給各個 component 使用),
  千萬記得,全部都先實體化的過程,變數已經變來變去無數回。
  最後 Angular 才會依著 template 的內容來產生 DOM,取得的變數已經是最後的值了。

  ※Angular 不是一邊解析 template, 一邊實體化物件來產生相應的 DOM,
  而是解析會用到的物件,全部實體化,
  之後再慢慢的把 template 轉成 DOM。

[08].property binding, class binding 
  (變數值一改變(在click事件下), innerHTML, value 跟著變,class套用的樣式也跟著變)

  import { Component } from '@angular/core';
  
  @Component({
      selector: 'app-root',
      template: `
          <h3 class="jiajeDefault"
              [class.jiajeRed]="booRed==true"
              [class.jiajeYellow]="booRed==false"
              >This is a test! booRed is {{booRed}}</h3>
          <button (click)="onClickMe($event)">Toggle</button>
          <input type="text" value="{{booRed}}">
      `,
      styles:[
          '.jiajeDefault { color: #dddddd; }',
          '.jiajeRed { background-color: red; }',
          '.jiajeYellow { background-color: yellow; }'
      ]
  })
  export class AppComponent {
      booRed:boolean = false;
      onClickMe($event)
      {
          this.booRed = !this.booRed;
      }
  }

[09].Input技術

  ● app.component.ts
  
  import { Component } from '@angular/core';
  
  @Component({
    selector: 'app-root',
    template: `<h1>Example</h1>
      <children 
        pv1="staticValue" 
        [pv2]="str2"
        pv3={{str3}}
        pv4="ANOTHER_METHOD"
        passValue-005="alias"
      >
      </children>`
  })
  export class AppComponent {
    str2:string="PARENT";
    str3:string="STILL_PARENT";
  }
  
  
  ● children.component.ts
  
  import { Component, Input } from '@angular/core'
  
  @Component({
    selector: 'children',
    template: `
      <div>pass value 1 = {{pv1}}</div>
      <div>pass value 2 = {{pv2}}</div>
      <div>pass value 3 = {{pv3}}</div>
      <div>pass value 4 = {{pv4}}</div>
      <div>pass value 5 = {{pv5}}</div>`,
    inputs: ['pv4']
  })
  export class ChildrenComponent{
    @Input() pv1:string="defaultValue1";
    @Input() pv2:string="defaultValue2";
    @Input() pv3:string="defaultValue3";
    pv4:string="defaultValue4";
    @Input('passValue-005') pv5:string="default5"
  }
  
  ● 結果如下
  
  Example
  pass value 1 = staticValue
  pass value 2 = PARENT
  pass value 3 = STILL_PARENT
  pass value 4 = ANOTHER_METHOD
  pass value 5 = alias

  ● 備註
    ChildrenComponent.pv1 讀取靜態值
    ChildrenComponent.pv2 與 AppComponent.str2 互相綁定
    ChildrenComponent.pv3 與 AppComponent.str3 另類綁定 ^.^
    ChildrenComponent.pv4 讀取靜態值,這是另一種 input 的宣告方式
    ChildrenComponent.pv5 讀取靜態值,這次用到別名機制,原始名稱 passValue-005,新的名稱 pv5
  
    P.S.若您在 index.html 的 app-root 標籤加入屬性,
      是沒有辦法在 AppComponent 用 Input 技術讀入的。
      個人解讀,沒有放入 template 的解析,無法產生作用。

[10].一些 directives 的參考(ngIf, ngIfElse, ngSwitch)

    <h2>產品</h2>
    <div *ngIf="products.length==0; else loading">
      沒有產品可以展示
    </div>
    <ng-template #loading>
      <div *ngIf="products.length>0">
        <div *ngFor="let product of products">
          <product [data]="product"></product>
        </div>
      </div>
    </ng-template>

    ------------------------------

    <div [ngSwitch]="data.rating">
      <div *ngSwitchCase="1">Poor</div>
      <div *ngSwitchCase="2">Fair</div>
      <div *ngSwitchCase="3">Good</div>
      <div *ngSwitchCase="4">Very Good</div>
      <div *ngSwitchCase="5">Excellent</div>
      <div *ngSwitchDefault>Not Rated</div>
    </div>

[11].一些 pipes 的參考 (格式化輸出)

  先設定日期物件 releaseDate=new Date(2020, 3-1, 8); // 2020-03-08

  ●DatePipe,用冒號可以設定參數
  <div>{{releaseDate | date}}</div>
  <div>{{releaseDate | date:"yyyy-MM-dd"}}</div>

  ●串聯多個 pipe,如 uppercase, lowercase (從左邊執行到右邊)
  <div>{{releaseDate | date | uppercase}}</div>

  ●自定 pipe

  ※truncate.pipe.ts

    import { Pipe, PipeTransform } from '@angular/core';
    
    @Pipe({ name:'truncate' })
    
    export class TruncatePipe implements PipeTransform {
      transform(value:string, limit:number, tailingCharacter:string="..."):string {
        return value.substring(0,limit)+tailingCharacter;
      }
    }

  ※app.module.ts
    import { TruncatePipe } from './truncate.pipe';
    declarations: [..., TruncatePipe]

  ※<div>{{string001 | truncate:20}}</div>
  ※<div>{{string001 | truncate:20:"~~~"}}</div>

[12].ngContent 讓 component 可以透過 class 抓自己裡面(innerHTML)的資料

  ※ngContentExample.component.ts

    import { Component } from '@angular/core';
    
    @Component({
      selector:'ngContentExample',
      template:`
        <div>
          <p>GET INNERHTML's DATA</p>
          <hr>
          <div><ng-content select=".sectionA"></ng-content></div>
          <hr>
          <div><ng-content select=".sectionB"></ng-content></div>
          <hr>
          <div><ng-content select=".sectionC"></ng-content></div>
        </div>
      `
    })
    export class NgContentExampleComponent {
    }
    
  ※app.module.ts
    import { NgContentExampleComponent } from './ngContentExample.component';
    declarations: [..., NgContentExampleComponent]

  ※app.component.html 裡面,或是 template 裡面

    <ngContentExample>
      <div class="sectionA">This is section A</div>
      <div class="sectionB">
        <h1>This is section B</h1>
      </div>
      <div class="sectionC">
        <h3>This is section C<br>second line</h3>
      </div>
    </ngContentExample>

[13].template driven forms (記得 import FormsModule)

  <form>

    <div class="form-group">
      <label for="elementUniqueId">某一個欄位的名稱</label>
      <input type="text" id="elementUniqueId" class="form-control" required
        [(ngModel)]="variableNameInJS" name="elementNameInHtml">
    </div>

  </form>

  ● class="form-group" 與 class="form-control" 會讓畫面好看
  ● for 與 id 的值相同,click在label上面,會讓input取得focus (這是 html5 的功能)
  ● 用 [(mgModel)] 搭配 name 屬性,會讓 variableNameInJS 與 input 的值雙向繫結

  -----------------------------------

  CASE 1:在 ta1 裡面輸入文字,他會被放到 ta2 裡面。
         若到 ta2 裡面輸入文字,則 ta1 的內文與 ta2 的內文就不同步;
         之後再到 ta1 裡面輸入文字,ta2 的內文再也不會改變了。

  <textarea [(ngModel)]="var1" name="ta1"></textarea>
  <textarea                    name="ta2">{{var1}}</textarea>

  -----------------------------------

  CASE 2:在 ta1 裡面輸入文字,他會被放到 ta2 裡面。
         若到 ta2 裡面輸入文字,他會被放到 ta1 裡面。
         一直都會同步;因為兩個都是雙向繫結。

  <textarea [(ngModel)]="var1" name="ta1"></textarea>
  <textarea [(ngModel)]="var1" name="ta2"></textarea>
  
  -----------------------------------

  CASE 3:若用 input 標籤,當使用者在第一個輸入時,剩下三個跟著同步動作。
    若之後在第二或第三個修改內容,第一個、第四個紋風不動。
    再之後來到第一個輸入,則第二個、第三個、第四個又會再次同步。
    因此,用 {{var1}} 的綁定,input與textarea的行為,不盡相同。

    <input type="text" [(ngModel)]="var1" name="inp1"><br>
    <input type="text" value="{{var1}}"><br>
    <input type="text" [value]="var1"><br>
    <input type="text" [(ngModel)]="var1" name="inp4"><br>

[14].template driven forms PART 02 - validation (記得 import FormsModule)

  <form>

    <div class="form-group">

      <label for="elementUniqueIdInHtml">某一個欄位的名稱</label>
      <input type="text" 
             class="form-control"
             id="elementUniqueIdInHtml" required
             [(ngModel)]="variableNameInJS" 
             name="elementNameInHtml"
             #variableNameInsertIntoJS="ngModel">

      <div class="alert alert-danger"
           *ngIf="variableNameInsertIntoJS.touched==true 
               && variableNameInsertIntoJS.valid==false">
           這個欄位是必填的喔!
      </div>

    </div>

  </form>

  ※欄位的檢查有 ng-touched, ng-untouched; ng-dirty, ng-pristine; ng-valid, ng-invalid 可用

  ※注意這邊有
  elementUniqueIdInHtml - 給 label 與 input 標籤使用,主要是 click 與 focus 動作。
  variableNameInJS, elementNameInHtml - 這是雙向繫結要用到的
  variableNameInsertIntoJS - 這是用來 validate 的,請注意 ="ngModel" 的宣告,
      若原 class 中有此同名稱變數,則會被此宣告覆蓋掉!
      若原 class 中有此同名稱變數,且已被雙向繫結,則會發生編譯錯誤!
      此變數是一個 NgModel 物件!

[15].template driven forms PART 03 - validation (記得 import FormsModule)

  若要驗證 required, minlength, maxlength 可參考下面用法

  寫法一

  <input name="userName" [ngModel]="user.userName" minlength="5" maxlength="10" #uname="ngModel" required>
  <div *ngIf="uname.touched==true && uname.errors">
    <div *ngIf="uname.errors.required">名字為必填</div>
    <div *ngIf="uname.errors.minlength">名字最少要五個字</div>
    <div *ngIf="uname.errors.maxlength">名字最多為十個字</div>
  </div>
  

  寫法二

  <input name="userName" [ngModel]="user.userName" minlength="5" maxlength="10" #uname="ngModel" required>
  <div *ngIf="uname.touched==true">
    <div *ngIf="uname.errors?.required">名字為必填</div>
    <div *ngIf="uname.errors?.minlength">名字最少要五個字</div>
    <div *ngIf="uname.errors?.maxlength">名字最多為十個字</div>
  </div>
  
  ※ 寫法一、寫法二效果相同。寫法二的問號代表,先判斷是否有引發錯誤,若有,再往下判斷。

  ※ uname.errors 代表目前的狀態是否違反 html 內建語法的規範(required, minlength, maxlength, ......)

  ※ uname.errors, uname.errors.required, uname.errors.minlength, uname.errors.maxlength 
     都不是 boolean 型態,所以不能用 true/false 來判斷。

  ※ maxlength 的判斷不會被觸發,因為瀏覽器會限制你的輸入,不讓他超過限定長度。


[16].template driven forms PART 04 - 純類別 (記得 import FormsModule)

  ※若是宣告純類別要來裝載資料的話

  export class User 
  {
    constructor
    (
      public strFirstName:string,
      public strEmail:string,
      public strCountry?:string
    ) {}
  }

  問號代表可選填欄位 (非必填)

[17].template driven forms PART 05 - 驗證用法 (記得 import FormsModule)

  <form #newVarInsertIntoJS="ngForm">

    <div>......</div>

    <button type="submit" class="btn btn-default"
            [disabled]="!newVarInsertIntoJS.form.valid">送出</button>

  </form>

  ※ 新加入的變數 newVarInsertIntoJS,可以取得表單的所有內容。
  ※ newVarInsertIntoJS.form.valid 有效與否的驗證,
     似乎僅支援到 html5 內建的語法 (required, minlength, ......)
  ※ class="btn btn-defalut" 會讓失效的效果看起來比較正式。

[18].template driven forms PART 06 - submit (記得 import FormsModule)

  <form (ngSubmit)="myFunc01()">
    ......
    <button type="submit">送出</button>
  </form>
  
  ※ (ngSubmit) 會將 type="submit" 的 button 的 click 事件導到 myFunc01() 上面

[19].template driven forms PART 07 - 一些 html tag's attributes 

  <div    hidden>  </div>    整個隱藏
  <button disabled></button> 按鈕失效

  <div    [hidden]  ="booVar1==true"></div>    看變數的值,決定是否隱藏
  <button [disabled]="booVar1==true"></button> 看變數的值,決定是否失效



底下是讀 ionic 的簡單記錄

Win 7 64bit 底下

安裝 Java 8 Update 201 (64bit)
安裝 JDK 8u241 (64bit) 要有 Oracle 的帳密

安裝 Android Studio
設定系統變數 ANDROID_SDK_ROOT=C:\Users\jiaje\AppData\Local\Android\Sdk
  (可從 Android Studio 的 SDK Manager 看到路徑)

從 AVD Manager 新增虛擬裝置 (對應 Android 7.0 無用,Android 8.0 才可以,猜測是專案組態設定的的問題)

從 gradle.org 下載 binary-only 的檔案,解壓縮變成 C:\Gradle\gradle-6.2
設定系統變數 path 加在最後面 C:\Gradle\gradle-6.2\bin
可用 [cmd] gradle -v 測試是否已安裝

[cmd] npm i -g cordova
[cmd] npm i -g native-run

[cmd] ionic start app04 blank (選 angular 架構)
[cmd] ionic cordova emulate android --livereload
[cmd] ionic cordova build android
※路徑或是檔案名稱有中文的話,ionic 編譯時會出錯。