Angular-個人整理

chrisghb發表於2018-11-29
  1. 單向從資料來源到檢視
{{expression}}
[target]="expression"
bind-target="expression"
複製程式碼

單向從檢視到資料來源

(target)="statement"
on-target="statement"
複製程式碼

雙向

[(target)]="expression"
bindon-target="expression"
複製程式碼
  1. DOM property 的值可以改變;HTML attribute 的值不能改變。

    Angular 的世界中,attribute 唯一的作用是用來初始化元素和指令的狀態。

    當進行資料繫結時,只是在與”元素和指令”的 “property 和事件”打交道,而 attribute 就完全靠邊站了。

  2. 如果忘了加方括號Angular 會把這個表示式當做字串常量看待,並用該字串來初始化目標屬性。

    下面這個例子把 HeroDetailComponentprefix 屬性初始化為固定的字串”1+1″,而不是模板表示式”2″。Angular 設定它,然後忘記它。

<app-hero-detail prefix="1+1" [hero]="currentHero"></app-hero-detail>
複製程式碼

作為對比,[hero] 繫結是元件的 currentHero 屬性的活繫結,它會一直隨著更新

  1. 插值表示式 {{...}} ,先對雙花括號中的表示式求值,再把求值的結果轉換成字串

    實際上,在渲染檢視之前,Angular 把這些插值表示式翻譯成了相應的屬性繫結。
  • 注意:但資料型別不是字串時,就必須使用屬性繫結了。
<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>
複製程式碼
  1. 不管是插值表示式還是屬性繫結,都不會允許帶有 script 標籤的 HTML 洩漏到瀏覽器中,二者都只渲染沒有危害的內容。
src/app/app.component.ts
    evilTitle = `Template <script>alert("evil never sleeps")</script>Syntax`;
src/app/app.component.html  
    <p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
    <p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>

複製程式碼
  1. attribute 繫結

    一般情況下,我們通過屬性繫結來設定元素的屬性property,而不用字串設定元素的attribute

    但考慮 ARIASVGtable 中的 colspan/rowspanattribute。 它們是純粹的 attribute,沒有對應的屬性可供繫結。
    如果想寫出類似下面這樣的東西,就會暴露出痛點了:
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
複製程式碼

會得到這個錯誤:
Template parse errors: Can`t bind to `colspan` since it isn`t a known native property

方括號中的部分不是元素的屬性名,而是由attr字首,一個點 (.)attribute 的名字組成

<tr><td [attr.colspan]="1 + 1">One-Two</td></tr>
複製程式碼
  1. 雙向資料繫結 ( [(...)] )

    想象盒子裡的香蕉來記住方括號套圓括號[()]
    雙向繫結語法實際上是屬性繫結和事件繫結的語法糖。

  2. 內建屬性型指令

  • a.通過繫結到 NgClass,可以同時新增或移除多個類。
    ngClass 繫結到一個 key:value 形式的控制物件(valueboolean值)

src/app/app.component.html

        <div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>
複製程式碼

src/app/app.component.ts

    currentClasses: {};
    setCurrentClasses() {
      // CSS classes: added/removed per current state of component properties
      this.currentClasses =  {
        `saveable`: this.canSave,
        `modified`: !this.isUnchanged,
        `special`:  this.isSpecial
      };
    }
複製程式碼
  • b.通過繫結NgStyle設定多個內聯樣式。NgStyle 需要繫結到一個 key:value 控制物件。

src/app/app.component.ts

      currentStyles: {};
    setCurrentStyles() {
      // CSS styles: set per current state of component properties
      this.currentStyles = {
        `font-style`:  this.canSave      ? `italic` : `normal`,
        `font-weight`: !this.isUnchanged ? `bold`   : `normal`,
        `font-size`:   this.isSpecial    ? `24px`   : `12px`
      };
    }
複製程式碼

src/app/app.component.html

    <div [ngStyle]="currentStyles">
    This div is initially italic, normal weight, and extra large (24px).
    </div>
複製程式碼
  1. 模板輸入變數和模板引用變數
  • 模板輸入變數

    hero 前的 let 關鍵字建立了一個名叫 hero 的模板輸入變數。
    這個變數的範圍被限制在所重複模板的單一例項上。事實上,你可以在其它內建結構型指令中使用同樣的變數名。
  • 模板引用變數

    #phone 的意思就是宣告一個名叫 phone 的變數來引用 <input> 元素。
    模板引用變數的作用範圍是整個模板。不要在同一個模板中多次定義同一個變數名,否則它在執行期間的值是無法確定的。
<input #phone placeholder="phone number">
複製程式碼
  1. 你總是可以在”元件自己的模板”中繫結到元件的公共屬性,而不用管它們是否輸入(Input)屬性或輸出(Output)屬性。
    Angular 需要@Input()@Output()裝飾器來標記出那些允許被外部元件繫結到的屬性。
    宣告輸入與輸出屬性:
@Input()  hero: Hero;
@Output() deleteRequest = new EventEmitter<Hero>();
複製程式碼

另外:@Input get/set寫法

_oprType: string =`VIEW`;
@Input("oprType")
get oprType(){
    retrun this._oprType;
}
set oprType(oprType){
    this._oprType = oprType;
    ...//其他邏輯處理
}
複製程式碼
  1. json 管道對除錯繫結特別有用:
src/app/app.component.html (pipes-json)
      <div>{{currentHero | json}}</div>
複製程式碼

它生成的輸出是這樣的:

    { "id": 0, "name": "Hercules", "emotion": "happy",
  "birthdate": "1970-02-25T08:00:00.000Z",
  "url": "http://www.imdb.com/title/tt0065832/",
  "rate": 325 }
複製程式碼
  1. 生命週期
  • 注意:建構函式在所有的生命週期鉤子之前執行

ngOnChanges()-》ngOnInit()-》ngDoCheck()-》ngAfterContentInit()-》ngAfterContentChecked()-》ngAfterViewInit()-》ngAfterViewChecked()-》ngOnDestroy()

ngOnInit 生命週期鉤子會在 DOM 更新操作執行前觸發

  1. rxjs知識
var subject = new Subject<string>();
subject.next(1);
subject.subscribe({
  next: (v) => console.log(`observerA: ` + v)
});
//observerA: 1
複製程式碼
  1. 純(pure)管道與非純(impure)管道

    預設情況下,管道都是純的。
    Angular 只有在它檢測到輸入值發生了純變更時才會執行純管道。
    純變更:是指對原始型別值(String、Number、Boolean、Symbol)的更改,或者對物件引用(Date、Array、Function、Object)的更改。

    Angular 會忽略物件內部的更改。 如果你更改了輸入日期(Date)中的月份、往一個輸入陣列(Array)中新增新值或者更新了一個輸入物件(Object)的屬性,Angular 都不會呼叫純管道。

    Angular 會在每個元件的變更檢測週期中執行非純管道。 非純管道可能會被呼叫很多次,和每個按鍵或每次滑鼠移動一樣頻繁。

  2. ElementRef

import { Directive, ElementRef } from `@angular/core`;
@Directive({
  selector: `[appHighlight]`
})
export class HighlightDirective {
    constructor(el: ElementRef) {
       el.nativeElement.style.backgroundColor = `yellow`;
    }
}
複製程式碼

import 語句還從 Angularcore 庫中匯入了一個 ElementRef 符號。

你可以在指令的建構函式中注入 ElementRef,來引用宿主 DOM 元素。
ElementRef(對檢視中某個宿主元素的引用)通過其 nativeElement 屬性給了你直接訪問宿主 DOM 元素的能力。

  • 注意:允許直接訪問 DOM 會導致你的應用在 XSS 攻擊前面更加脆弱。當需要直接訪問
    當需要直接訪問 DOM 時,請把本 API 作為最後選擇

優先使用 Angular 提供的模板和資料繫結機制。
或者你還可以看看 Renderer2(實現自定義渲染器),它提供了可安全使用的 API —— 即使環境沒有提供直接訪問原生元素的功能。

import { Directive, ElementRef, Renderer2 } from `@angular/core`;
@Directive({
  selector: `[appHighlight]`
})
export class HighlightDirective {
    constructor(el: ElementRef, private renderer: Renderer2) {
       //el.nativeElement.style.backgroundColor = `yellow`;
       this.renderer.setStyle(this.el.nativeElement, "backgroundColor", `yellow`);
    }
}
複製程式碼

ViewChild和ElementRef

  1. HostListener把一個事件繫結到一個宿主監聽器,並提供配置後設資料。
@HostListener(`mousedown`) onMouseEnter() {
  this.highlight(this.highlightColor || this.defaultColor || `red`);
}
複製程式碼

mousedown要監聽的事件。
mousedown事件發生時,Angular 就會執行所提供的處理器方法onMouseEnter

  1. 你可以根據屬性名在繫結中出現的位置來判定是否要加 @Input

    當它出現在等號右側的模板表示式中時,它屬於模板所在的元件,不需要 @Input 裝飾器。

    當它出現在等號左邊的方括號([ ])中時,該屬性屬於其它元件或指令,它必須帶有 @Input 裝飾器。
<p [appHighlight]="color">Highlight me!</p>  
複製程式碼

color 屬性位於右側的繫結表示式中,它屬於模板所在的元件。 該模板和元件相互信任。因此 color 不需要 @Input 裝飾器。

appHighlight 屬性位於左側,它引用了 HighlightDirective 中一個帶別名的屬性,它不是模板所屬元件的一部分,因此存在信任問題。 所以,該屬性必須帶 @Input 裝飾器。

  1. bootstrap —— 根元件,Angular 建立它並插入 index.html 宿主頁面。
  2. 結構型指令中,星號(*)寫法是個語法糖,Angular會把它解開成一個 <ng-template> 標記,包裹著宿主元素及其子元素。
    Angular 會在真正渲染的時候填充 <ng-template> 的內容,並且把 <ng-template> 替換為一個供診斷用的註釋。
<ng-template let-hero="hero">
  <div *ngIf="hero" [class.odd]="odd">({{i}}) {{hero.name}}</div>
</ng-template>
複製程式碼
  • 注意:如果沒有使用結構型指令,而僅僅把一些別的元素包裝進 <ng-template> 中,那些元素就是不可見的(被註釋掉了)。
  1. 每個宿主元素上只能有一個結構型指令。比如:*ngFor*ngIf 不能放在同一個宿主元素上。

    有一個簡單的解決方案:把 *ngFor 放在一個”容器”元素上,再包裝進 *ngIf 元素。 這個元素可以使用ng-container,以免引入一個新的 HTML 層級。
<ul>
  <ng-container *ngFor="let menu of menus">
    <li *ngIf="!menu.children?.length">
    </li>
  </ng-container>
</ul>
複製程式碼

Angular<ng-container> 是一個分組元素,但它不會汙染樣式或元素佈局,因為 Angular 壓根不會把它放進 DOM 中。

template: <ng-template #toolbarTemplate [ngTemplateOutletContext]="{ $implicit: this}"></ng-template>               
class: @ViewChild("toolbarTemplate") toolbarTemplate: TemplateRef<any>
簡化寫法:
<ng-template [ngTemplateOutlet]="toolbarTemplate || defaultToolbarTemplate"
               [ngTemplateOutletContext]="{ $implicit: this}"></ng-template>
複製程式碼

該指令用於基於已有的 TemplateRef 物件,插入對應的內嵌檢視。在應用 NgTemplateOutlet 指令時,我們可以通過 [ngTemplateOutletContext] 屬性來設定 EmbeddedViewRef 的上下文物件。繫結的上下文應該是一個物件,此外可通過 let語法來宣告繫結上下文物件屬性名。

angular6.x中ngTemplateOutlet指令的使用示例

  1. 表示式中的上下文變數是由”模板引用變數#aa“、”模板輸入變數let bb“和”元件的成員cc“疊加而成的。

    優先順序:模板輸入變數>模板引用變數>指令的上下文變數>元件類的例項

  2. 模板表示式不能引用全域性名稱空間中的任何東西,比如windowdocument。它們也不能呼叫console.logMath.max。它們只能引用表示式上下文中的內容。

  3. 宣告式元件和入口元件:

    宣告式元件會在模板中通過元件宣告的selector(比如<cmb></cmb>)載入元件。

    入口元件entryComponents主要有3類:

  • @NgModule中的bootstrap宣告的根元件
  • 路由配置的元件
  • 動態元件
  1. exports

    在模組angModuleexports出一組元件、指令和管道

    -》模板b匯入了模組a

    -》模組b下的所有元件的模板,都可以使用模組angModuleexports出的元件、指令和管道
  2. 父子元件互動
<div class="seconds">{{timer.seconds}}</div>
<app-countdown-timer #timer></app-countdown-timer>
複製程式碼

這個本地變數方法#timer是個簡單便利的方法。但是它也有侷限性,因為父元件-子元件的連線必須全部在父元件的html模板中進行。父元件本身的ts程式碼對子元件沒有訪問權。

當父元件類需要這種訪問時,可以把子元件作為 ViewChild,注入到父元件裡面。

  1. 內容投影ng-content

父元件頁面parent.html

<attchment-upload>abcde</attchment-upload>
複製程式碼

被父元件包含的子元件標籤頁面attchment-upload.html

  <div>-- begins --</div>
    <ng-content></ng-content>
  <div>-- ends --</div>`
複製程式碼

輸出:

-- begins --
abcde
-- ends --
複製程式碼

以上<ng-content> 標籤是父元件頁面中外來內容的佔位符。 它指明在子元件標籤頁面的哪裡插入這些父元件的外來內容abcde

注意不要在元件標籤的內部放任何內容 —— 除非你想把這些內容投影進這個元件中。

  1. NgTemplateOutlet基於已有的TemplateRef 物件,插入對應的內嵌檢視。
    Angular 4.x NgTemplateOutlet
<ng-container *ngTemplateOutlet="greet"></ng-container>
<ng-template #greet><span>Hello</span></ng-template>
複製程式碼

或者

<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
contentTemplate:TemplateRef<any>
this.contentTemplate=...
//TemplateRef就是對應html中的ng-template標籤
複製程式碼
  1. ngIf寫法
<ng-container *ngIf="a.length==0; else elseTemplate">
    ...
</ng=container>
<ng-template #elseTemplate>
    ...
</ng-template>
複製程式碼

30.ngTemplateOutletContext

<ng-template let-rowData let-rowIndex="rowIndex" let-columns="columns">
    <tr>
        <td *ngFor="let col of columns;">
            // 通過 [ngTemplateOutletContext] 屬性來設定 EmbeddedViewRef:commonBodyTemplate的上下文物件
            <ng-container *ngTemplateOutlet="commonBodyTemplate;context:{$implicit:col,rowIndex:rowIndex,rowData:rowData,columns:columns}"></ng-container>
        </td>
    </tr>
</ng-template>

//若 let 語法未繫結任何屬性名(let-col),則上下文物件中 $implicit 屬性,對應的值col將作為預設值。
<ng-template #commonBodyTemplate let-col let-rowIndex="rowIndex" let-rowData="rowData">
    <ng-container *ngIf="col.field==`idx`">
        {{rowIndex+1}}
    </ng-container>
    <ng-container *ngIf="col.field==`attachSize`">
        {{(rowData[col.field]||"--")+(rowData["attachSizeUnit"]||"")}}
    </ng-container>
    <ng-container *ngIf="editable;else elseTemplate">
        <ng-container *ngIf="col.field==`fileName`">
            <ng-container *ngIf="rowData[`attachmentId`]">
                <a [href]="attachUrls[rowData[`attachmentId`]]" [download]="rowData[col.field]">{{rowData[col.field]||""}}</a>
            </ng-container>
        </ng-container>
        <ng-container *ngIf="[`idx`,`attachSize`,`fileName`].indexOf(col.field)==-1">
            {{rowData[col.field]||"--"}}
        </ng-container>
    </ng-container>
    <ng-template #elseTemplate>
        <ng-container *ngIf="[`idx`,`attachSize`].indexOf(col.field)==-1">
            {{rowData[col.field]||"--"}}
        </ng-container>
    </ng-template>
</ng-template>
複製程式碼

在父頁面中嵌入<app-attachment></app-attachment>

報錯:"app-attachment" is not a unknown element.

原因:app-attachment子頁面模板對應的ts元件沒有包括在父頁面所在的module中。

32.@Input(`companyId`) companyId: string;
子元件的初始化constructor取不到companyId值,ngOnInit()可以
所以初始化放在ngOnInit中。

33.響應式表單

this.formGroup=fb.group({
        contractid:["123",[Validators.required]],
});

this.theDetailGroup.get("finProductName").setValue(this.finProductName);
this.formGroup.get("docmentId").valueChanges.subscribe(value=>{
          if(this.existList.findIndex(item=>item.docmentId==value)>-1){
            this.formGroup.get("docmentId").setErrors({duplicate:true});
          }
        });
        
this.formGroup.get("docmentId").value; 
複製程式碼

34.this.fb.group({})
響應式表單控制元件繫結,應該在生命週期之前,即constructor建構函式中初始化。因為如果放在ngOnInit中繫結,在表單初始化前對錶單進行賦值操作,會報錯。

35.自定義模組中exports使用

將附件宣告到公用模組

import { NgModule } from `@angular/core`;
import { CommonModule} from `@angular/common`;
import { AttachmentComponent } from `./attachment.component`;

@NgModule({
  imports: [
    CommonModule,
  ],
  exports:[
    CommonModule,
    AttachmentComponent
  ],
  providers:[],
  declarations: [
    AttachmentComponent
  ],
  entryComponents:[
    AttachmentComponent
  ]
})
export class SharedModule { }

複製程式碼

宿主元件所在的module中引入公共模組

import { NgModule } from `@angular/core`;
import { CommonModule } from `@angular/common`;
import { SharedModule } from `src/app/shared`;



@NgModule({
  declarations: [
     ],
  imports: [
    CommonModule,
    SharedModule
  ],
  entryComponents:[]
})
export class MineModule { }

複製程式碼

36.自定義雙向資料繫結

  • 父元件
 <app-base [(companyId)]="companyId"></app-base>
複製程式碼
  • 子元件app-base.ts
 _companyId = ``;
  @Output() companyIdChange = new EventEmitter();
  @Input(`companyId`)
  get companyId() {
    return this._companyId;
  }
  set companyId(companyId) {
    this._companyId = companyId;
    this.companyIdChange.emit(companyId);
  }
複製程式碼

注意:屬性名 + Change是雙向繫結中Output的固定寫法

相關文章