2018 年 7 月

第 33 卷,第 7 期

本文章是由機器翻譯。

程式設計師雜談 - 如何使用 MEAN:靈活運用 Angular

藉由Ted Neward |2018 年 7 月

Ted Neward歡迎回來,同樣地,MEANers。

在 [我的上一篇專欄中,「 如何為 MEAN:回應式程式設計 」 (msdn.com/magazine/mt846724),我重新檢視了 Angular、 提供不同的方法來建構表單和回應事件的使用者會在其中建立的回應式表單模組。在這篇文章結束時,我會造成問題:如果我的情況下,其中有相當大量的控制項,或建立的控制項需要變更取決於下方的模型物件的變更本質嗎?這是一個例子,其中,坦白說,Angular 會採用傳統的 「 範本 」 系統上的信賴憑證者並不會減少。如果我必須為每個可能的可能值組合建立範本,它將會很長的一天。

讓我們假設,目前為止,會議想要建置輪詢系統評估喇叭、 演講、 會議地點、 網站的出席者,您將它命名。他們想要本身應該需要能夠推出新的問題很快速,且可能甚至在會議期間發生。這表示,接著,我想知道如何產生欄位被人問的問題類型為基礎的網頁,這些問題類型將來自外部來源 (例如 JSON 服務或甚至檔)。

它不是魔法。GUI (網頁或其他) 的系統支援的控制項,透過執行階段建構 (例如能夠 「 new 」 控制項本身) 建構,這是合理和相當可行的功能。它是絕對可行 Angular 中:我可以建立其中建置完全從模型物件的表單和相關聯的 (隱含或明確) 中繼資料系統。

因此,若要繼續問卷的範例,如果我建立一個簡單的 Angular 服務知道濆爧髍孮一系列 「 問題 」 物件,相關聯的 Angular 表單可以採用部分或所有這些物件,建構表單項目的對應的集合呈現的問題和擷取答案,大概是某處的儲存體。這個金鑰會 FormGroup 和 FormControls Angular 用來代表這些控制項在執行階段。

動態模型

讓我們開始所有問題,可協助針對任何問題和其相關的控制項擷取一些我預期 (並需要) 的通用行為的基底類別。以下是針對該程式碼:

export type ControlType = "textbox" | "dropdown";
export abstract class Question {
  constructor(public value: string = '',
    public key: string = '',
    public label: string = '',
    public required: boolean = false,
    public controlType: ControlType = 'textbox')
  { }
}

大部分內容都將會非常簡單,因為大部分的類別只是屬性 (模式人有時所謂的 DTO 或資料傳輸物件),但是索引鍵的項目將會 [controlType] 欄位。它會對應到哪些 HTML 的描述元我所產生的建構。目前,它具有所有兩種可能性: textbox (允許開放式的文字項目) 或下拉式清單中 (單一-從選取項目繫結範圍的可能性)。

同樣很明顯,問題是問題的抽象類別,因為我預期衍生的型別建立,一個用於每種類型。TextboxQuestion 的程式碼看起來像這樣:

export class TextboxQuestion extends Question {
  constructor(value: string = '',
    key: string = '',
    label: string = '',
    required: boolean = false,
    public type: string = '') {
    super(value, key, label, required, 'textbox');
  }
}

以及 DropdownQuestion 的程式碼,就像這樣:

export class DropdownQuestion extends Question {
  constructor(value: string = '',
    key: string = '',
    label: string = '',
    required: boolean = false,
    public options: {key: string, value: string}[] = [])
  {
    super(value, key, label, required, 'dropdown');
  }
}

每個問題會傳遞至其父代,基底的參數集合,每個在混合加入一件事。如果 TextboxQuestion,它會新增 textbox 的型別參數,萬一我想要表示這是密碼或電子郵件文字方塊。如果 DropdownQuestion,它會新增要做為下拉式清單中的可能值的索引鍵/值組的陣列。

接下來,不過,我必須了解如何開啟這些 FormControl 和 FormGroup 物件。在論證上,Angular 思考可能是以獨立服務的設計方式,但較為合理我讓它成為問題類別,做為靜態方法的一部分。(我曾新增新的問題類型,它需要更新,就會向我把它們全部位於相同的模組內的群組中提出更有意義,因此這個方法。) 從程式碼端,建立必要的 FormControl 物件非常簡單,如下所示:

export abstract class Question {
  public static toFormGroup(questions: Question[]): FormGroup {
    let group: any = {};
    questions.forEach(question => {
      group[question.key] =
        question.required ? new FormControl(question.value, Validators.required)
                          : new FormControl(question.value);
    });
    return new FormGroup(group);
  }
  // ...
}

這個方法基本上會採用陣列的問題,並把它們轉換成位在 FormGroup 物件內的 FormControl 物件的陣列。從這一端的項目中,請注意,唯一真正的問題的控制項是否需要;任何其他顯示邏輯需要擷取範本內。

動態顯示

我還需要; 在這裡開始思考相關的 Angular UI 元件基本上,輪詢或問卷組成一或多個問題,因此我將使用,做為工作模型: QuestionnaireComponent 使用多個 QuestionComponents,而且每個 QuestionComponent 必須做為輸入問題的物件。

這樣較容易從頂端開始,而且我方式運作,讓我們這麼做。首先,我必須將會顯示問卷調查,在此情況下在其自己的圖片,如下所示在 AppComponent [圖 1

[圖 1 AppComponent

@Component({
  selector: 'app-root',
  template: `
    <div>
      <h2>How was our conference?</h2>
      <app-questionnaire [questions]="questions"></app-questionnaire>
    </div>
  `,
  providers:  [QuestionService]
})
export class AppComponent {
  questions: Question[];
  constructor(service: QuestionService) {
    this.questions = service.getQuestions();
  }
}

此程式碼提供完美的元件狀況增加。我只要使用它,並已知道如何提供的輸入元件的需求,因此程式碼保持亮色、 簡單及任何 Angular 的開發人員輕鬆直覺化的服務。

接下來,讓我們看看 QuestionnaireComponent,如中所示**[圖 2**。

[圖 2 QuestionnaireComponent

@Component({
  selector: 'app-questionnaire',
  templateUrl: './questionnaire.component.html'
})
export class QuestionnaireComponent implements OnInit {
  @Input() questions: Question[] = [];
  form: FormGroup;
  payload = '';
  ngOnInit() {
    this.form = Question.toFormGroup(this.questions);
  }
  onSubmit() {
    this.payload = JSON.stringify(this.form.value);
  }
}

同樣地,方法是很簡單明瞭。QuestionnaireComponent 接受做為輸入,陣列的問題,並使用 FormGroup 比對要在範本中建置表單。[圖 3會示範這點。

[圖 3 來建立與 FormGroup 表單準備

<div>
  <form (ngSubmit)="onSubmit()" [formGroup]="form">
    <div *ngFor="let question of questions" class="form-row">
      <app-question [question]="question" [form]="form"></app-question>
    </div>
    <div class="form-row">
      <button type="submit" [disabled]="!form.valid">Save</button>
    </div>
  </form>
  <div *ngIf="payload" class="form-row">
    <strong>Saved the following values</strong><br>{{payload}}
  </div>
</div>

一般來說,承載會透過上傳透過 HTTP Angular 服務,可能是儲存在資料庫中,但會花費範例有點超出範圍。在這裡,顯示用來證明資料會進行驗證、 擷取並備妥可供散發。

當然,仍然必須建立個別的問題中的項目表單中,並落在 QuestionComponent 的程式碼,如下方所示:

@Component({
  selector: 'app-question',
  templateUrl: './question.component.html'
})
export class QuestionComponent {
  @Input() question: Question;
  @Input() form: FormGroup;
  get isValid() { return this.form.controls[this.question.key].valid; }
}

請注意 QuestionComponent 將輸入 (邏輯) 所屬; FormGroup我可以嘗試尋找不同的方法,用來取得 FormControl (適用於的 isValid 屬性實作),但這種方式運作,並協助讓事情更簡單。

此元件的範本是真正的神奇的動態表單建立發生的地方。由於在問題物件的 controlType 審慎 ngSwitch,我可以很簡單,如中所示建置 HTML 項目**[圖 4**。

[圖 4 建置 HTML 項目

<div [formGroup]="form">
  <label [attr.for]="question.key">{{question.label}}</label>
  <div [ngSwitch]="question.controlType">
    <input *ngSwitchCase="'textbox'" [formControlName]="question.key"
            [id]="question.key" [type]="question.type">
    <select *ngSwitchCase="'dropdown'" [formControlName]="question.key"
            [id]="question.key">
      <option *ngFor="let opt of question.options" [value]="opt.key">
        {{opt.value}}
      </option>
    </select>
  </div>
  <div class="errorMessage" *ngIf="!isValid">{{question.label}} is required</div>
</div>

如您所見,它是很簡潔做法。上 [controlType] 屬性中,切換,並根據這種作法是否,下拉式清單或文字方塊中輸入問題,建立不同的 HTML。

最後,我只需要提供一些問題,這同樣地,通常是一樣,從某些外部的資源,例如檔案或伺服器端 API QuestionService。在此範例中,服務會提取的記憶體,問題中所述**[圖 5**。

[圖 5 從 QuestionService 取得問題

@Injectable()
export class QuestionService {
  getQuestions() {
    return [
      new TextboxQuestion('', 'firstName',
        'Speaker\'s First name', true),
      new DropdownQuestion('', 'enjoyment',
        'How much did you enjoy this speaker\'s talk?',
        false,
        [
          {key: 'great', value: 'Great'},
          {key: 'good', value: 'Good'},
          {key: 'solid', value: 'Solid'},
          {key: 'uninspiring', value: 'Uninspiring'},
          {key: 'wwyt', value: 'What Were You Thinking?'}
        ]),
    ];
  }
}

很明顯地,在實際的問卷,少數的其他問題有可能,但此範例中取得的點。

總結

屬於任何一種這類系統的實際問題是其擴充性:可以加入新的功能而不需要大幅的修改嗎?QuestionnaireService 很明顯地,會有的金鑰,只要它可以產生不同物件陣列的問題,我有無限個我可以詢問我們會議出席者的功能。唯一的限制是何種我可以詢問,受限於多重選擇或 open ended 文字解答的問題。

引發的第二個問題:它是困難會加入新類型的系統,例如評等控制項使用離散數字值的問題?若要這樣做需要建立一個新的問題子類別 (RatingsQuestion) 若要使用,新的 ControlType 列舉值的範本,以切換成開啟,並修改 QuestionComponent 範本來開啟新的列舉值的數值範圍和(不過,看起來),請據以顯示 HTML。所有其他項目會保持不變,這是任何元件技術的目標,維持用戶端不知道的任何結構性變更,除非他們選擇以善用新功能。

Angular 的讀取器會使用提供給此概念研究一番,因此我把關閉這裡的項目。不過,還有一個更必要的位元,我們必須通盤之前我們可以總結我們 Angular 的涵蓋範圍,所以我們就會叫用該下一次。直到然後,高興撰寫程式碼 !


Ted Neward是西雅圖的 polytechnology 顧問、 演說家和 mentor,目前從事的工程設計和開發人員關係在總監Smartsheet.com。他已撰寫了無數的文章,撰寫與合著過十幾個書籍,並在世界各地發表演說。與他連絡ted@tedneward.com或閱讀他的部落格blogs.tedneward.com

感謝下列技術專家:Garvice Eakins (Smartsheet)


MSDN Magazine 論壇中的這篇文章的討論