Учебники по программированию показывают нам многообещающую землю, где все происходит так, как вы думаете; как только вы думаете. Но реальный мир в большинстве случаев работает не так. Здесь; вы часами отлаживаете какую-то ошибку CORS или думаете, почему столбец идентификатора таблицы базы данных не увеличивается автоматически. За последние 2 дня; Я участвую в интервью по программированию, которое длится 2 дня, и эта серия блогов основана на этом опыте — о чем я думаю на каждом этапе; в чем проблема и как я их решаю. Это третья часть этого.

Разработка остальной части пользовательского интерфейса Angular

Осталось добавить навигацию вверху страницы; добавление страниц, которые будут отвечать за UI; и дизайн страниц в реактивной форме (да, мы ненавидим себя).

Реактивные формы:

Плюсы:

  • Они обеспечивают больший контроль над формой и ее элементами, поскольку вы можете программно манипулировать формой и ее элементами.
  • Они более эффективны, поскольку обновляют форму и ее элементы только тогда, когда это явно указано.
  • Они лучше подходят для обработки сложных форм с множеством полей и правил проверки.

Минусы:

  • Их может быть сложнее настроить, и для их реализации может потребоваться больше кода.
  • Они могут быть не такими интуитивно понятными для разработчиков, которые не знакомы с парадигмой реактивного программирования.

Пример:

this.form = this.fb.group({
  name: ['', Validators.required],
  email: ['', [Validators.required, Validators.email]],
  password: ['', Validators.required],
});

Формы на основе шаблонов:

Плюсы:

  • Их проще настроить и для реализации может потребоваться меньше кода.
  • Они более интуитивно понятны разработчикам, не знакомым с парадигмой реактивного программирования.

Минусы:

  • Они обеспечивают меньший контроль над формой и ее элементами, так как привязка данных осуществляется автоматически.
  • Они могут быть не такими эффективными, как реактивные формы, поскольку обновляют форму и ее элементы при каждом событии изменения.
  • Они менее подходят для обработки сложных форм с множеством полей и правил проверки.

Пример:

<form #form="ngForm" (ngSubmit)="submit(form)">
  <input name="name" ngModel required>
  <input name="email" ngModel required email>
  <input name="password" ngModel required>
  <button type="submit">Submit</button>
</form>

Почему реактивный, а не шаблонный

Реактивные формы обеспечивают больший контроль над обработкой сложных форм с множеством полей и правил проверки, поскольку они позволяют программно манипулировать формой и ее элементами. Это позволяет создавать структуру формы, которая является более модульной и многократно используемой, что упрощает управление и обслуживание.

Например, в сложной форме с множеством полей и правил проверки можно создать настраиваемый компонент управления формой для каждого поля и повторно использовать его во всей форме. Это позволяет сделать код формы более организованным и удобным для сопровождения, упрощая добавление, удаление или обновление полей и правил проверки.

Реактивные формы также предлагают мощный способ обработки проверки. С реактивными формами вы можете определить правила проверки для каждого элемента управления формы и даже для самой формы. Это позволяет вам централизовать логику проверки и поддерживать ее организованность. Вы также можете использовать встроенные директивы проверки Angular, такие как required, min, max и pattern, а также создавать собственные валидаторы для применения более сложных правил проверки.

Например, вот пример того, как создать собственный валидатор и использовать его с элементом управления формы:

import { FormControl } from '@angular/forms';
function validateEmail(c: FormControl) {
  let EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
   return EMAIL_REGEXP.test(c.value) ? null : {
    validateEmail: {
      valid: false
    }
  };
}
this.form = new FormGroup({
  email: new FormControl('', [validateEmail])
});

Одна проблема, с которой мы сталкиваемся, когда разрабатываем user-list.ts следующим образом:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HttpClientService } from 'src/app/http-client.service';
import { UserModel } from '../../../../models/user';
@Component({
  selector: 'app-use-list',
  templateUrl: './use-list.component.html',
  styleUrls: ['./use-list.component.scss']
})
export class UseListComponent implements OnInit {
  userList = UserModel[];
  constructor(client: HttpClientService) {
    client.getData("user").subscribe((res: UserModel[]) => {
      console.log(res);
      this.userList = res;
    });
  }
ngOnInit(): void {}
}

Где мы объявляем UserModel в файле модели; мы получаем следующую ошибку — Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'typeof UserModel'. Скорее всего, наше использование [] после UserModel вызывает проблему. Изменение кода, как показано ниже, устраняет проблему -

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HttpClientService } from 'src/app/http-client.service';
import { UserModel } from '../../../../models/user';
@Component({
  selector: 'app-use-list',
  templateUrl: './use-list.component.html',
  styleUrls: ['./use-list.component.scss']
})
export class UseListComponent implements OnInit {
  userList: UserModel[] = [];
  constructor(client: HttpClientService) {
    client.getData("user").subscribe((res) => {
      console.log(res);
      this.userList = res as UserModel[];
    });
  }
ngOnInit(): void {}
}

С тех пор мы изменили код, и наш файл user-create.html выглядит так:

<div class="container">
  <form [formGroup]="userForm">
    <div class="form-group">
      <label for="name">Name</label>
      <input type="text" class="form-control" id="name" formControlName="name">
    </div>
    <div class="form-group">
      <label for="email">Email</label>
      <input type="email" class="form-control" id="email" formControlName="email">
    </div>
    <div class="form-group">
      <label for="mobile">Mobile</label>
      <input type="tel" class="form-control" id="mobile" formControlName="mobile">
    </div>
    <div class="form-group form-check">
      <input type="checkbox" class="form-check-input" id="is_active" formControlName="isActive">
      <label class="form-check-label" for="is_active">Is Active</label>
    </div>
    <button type="submit" class="btn btn-primary" (click)="save()">Save</button>
    <!-- <button type="button" class="btn btn-secondary" (click)="cancel()">Cancel</button> -->
  </form>
</div>

который выдает ошибку — Невозможно выполнить привязку к «formGroup», так как это неизвестное свойство «формы».
Когда мы добавим
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
в файл app.module.ts и в список импорта добавим -
FormsModule,
ReactiveFormsModule

Однако эта проблема исчезнет.

Код к этому будет доступен в ветке — version_1.

Борьба начинается!

Когда мы отправляем форму; выдает ошибку — An error occurred while saving the entity changes. See the inner exception for details. & Cannot insert the value NULL into column 'id', table 'dev_test.dbo.user'; column does not allow nulls. INSERT fails.
Что и понятно! Я имею в виду; мы не присвоили объекту никакого значения идентификатора.
Но разве наша БД не должна об этом позаботиться?

Глядя на наш сценарий БД; мы видим, что мы сделали id первичным ключом; но мы забыли увеличить его; специально забыл добавить IDENTITY(1,1) NOT NULL к нашим таблицам.
Поэтому нам нужно добавить несколько способов удаления и создания таблиц и установки идентификатора для каждого столбца идентификатора.
И наш файл базы данных выглядит так -

IF OBJECT_ID(N'dbo.invitation', N'U') IS NOT NULL  
   DROP TABLE [dbo].invitation;  
GO
IF OBJECT_ID(N'dbo.submission', N'U') IS NOT NULL  
   DROP TABLE [dbo].submission;  
GO
IF OBJECT_ID(N'dbo.answer', N'U') IS NOT NULL  
   DROP TABLE [dbo].answer;  
GO
IF OBJECT_ID(N'dbo.question', N'U') IS NOT NULL  
   DROP TABLE [dbo].question;  
GO
IF OBJECT_ID(N'dbo.survey', N'U') IS NOT NULL  
   DROP TABLE [dbo].survey;  
GO
IF OBJECT_ID(N'dbo.[user]', N'U') IS NOT NULL  
   DROP TABLE [dbo].[user];  
GO
CREATE TABLE [user]
(
    id INT PRIMARY KEY  IDENTITY(1,1),
    email VARCHAR(255) NOT NULL UNIQUE,
    mobile VARCHAR(15) NOT NULL,
    isActive BIT NOT NULL,
    role VARCHAR(255) NOT NULL
);
CREATE TABLE survey
(
    id INT PRIMARY KEY IDENTITY(1,1),
    title VARCHAR(255) NOT NULL,
    created_date DATETIME NOT NULL,
    updated_date DATETIME NOT NULL
);
CREATE TABLE question
(
    id INT PRIMARY KEY IDENTITY(1,1),
    survey_id INT NOT NULL,
    text NVARCHAR(MAX) NOT NULL,
    FOREIGN KEY (survey_id) REFERENCES survey(id)
);
CREATE TABLE answer
(
    id INT PRIMARY KEY IDENTITY(1,1),
    question_id INT NOT NULL,
    text NVARCHAR(MAX) NOT NULL,
    is_correct BIT NOT NULL,
    FOREIGN KEY (question_id) REFERENCES question(id)
);
CREATE TABLE invitation
(
    id INT PRIMARY KEY IDENTITY(1,1),
    survey_id INT NOT NULL,
    user_id INT NOT NULL,
    invitation_link NVARCHAR(MAX) NOT NULL,
    FOREIGN KEY (survey_id) REFERENCES survey(id),
    FOREIGN KEY (user_id) REFERENCES [user](id)
);
CREATE TABLE submission
(
    id INT PRIMARY KEY IDENTITY(1,1),
    survey_id INT NOT NULL,
    user_id INT NOT NULL,
    answer1 INT NOT NULL,
    answer2 INT NOT NULL,
    answer3 INT NOT NULL,
    score INT NOT NULL,
    FOREIGN KEY (survey_id) REFERENCES survey(id),
    FOREIGN KEY (user_id) REFERENCES [user](id)
);

Затем мы приступаем к разработке остальной части пользовательского интерфейса. Полный код предоставлен version_2.
Вот одна из проблем, с которой мы столкнулись. Когда мы разрабатываем интерфейс опроса следующим образом:

<div class="container">
  <form [formGroup]="surveyForm" (ngSubmit)="save()">
    <div class="form-group">
      <label for="title">Survey Title</label>
      <input type="text" class="form-control" id="title" formControlName="title">
    </div>
    <div formArrayName="Questions" class="mt-3">
      <div *ngFor="let question of getQuestionControls().controls; let i = index" [formGroupName]="i">
        <label>Question {{i+1}}</label>
        <input type="text" formControlName="Text" class="form-control mb-3">
        <div formArrayName="Answers" class="d-flex flex-wrap">
          <div>{{getAnswerControls(question.value).controls}}</div>
          <div *ngFor="let answer of getAnswerControls(question.value).controls; let j = index" [formGroupName]="j"
            class="form-check mr-3 mb-3">
            <input class="form-check-input" type="radio" formControlName="isCorrect" [value]="j"
              (change)="selectAnswer(i, j)">
            <input type="text" formControlName="Text" class="form-control">
          </div>
        </div>
      </div>
    </div>
    <div class="d-flex justify-content-end">
      <button type="submit" class="btn btn-primary mr-3">Save</button>
      <button type="button" class="btn btn-secondary" (click)="cancel()">Cancel</button>
    </div>
  </form>
</div>

И такой файл ts -

import { Component, OnInit } from '@angular/core';
import { FormArray, FormControl, FormGroup } from '@angular/forms';
import { HttpClientService } from 'src/app/http-client.service';
@Component({
  selector: 'app-survey-add',
  templateUrl: './survey-add.component.html',
  styleUrls: ['./survey-add.component.scss']
})
export class SurveyAddComponent implements OnInit {
  _client: HttpClientService;
  surveyForm = new FormGroup({
    title: new FormControl(),
    Questions: new FormArray(
      [
        new FormGroup(
          {
            Text: new FormControl(),
            Answers: new FormArray(
              [
                new FormGroup(
                  {
                    Text: new FormControl(),
                    isCorrect: new FormControl(),
                  }
                ),
                new FormGroup(
                  {
                    Text: new FormControl(),
                    isCorrect: new FormControl(),
                  }
                ),
                new FormGroup(
                  {
                    Text: new FormControl(),
                    isCorrect: new FormControl(),
                  }
                ),
                new FormGroup(
                  {
                    Text: new FormControl(),
                    isCorrect: new FormControl(),
                  }
                ),
              ]
            )
          }
        ),
        new FormGroup(
          {
            Text: new FormControl(),
            Answers: new FormArray(
              [
                new FormGroup(
                  {
                    Text: new FormControl(),
                    isCorrect: new FormControl(),
                  }
                ),
                new FormGroup(
                  {
                    Text: new FormControl(),
                    isCorrect: new FormControl(),
                  }
                ),
                new FormGroup(
                  {
                    Text: new FormControl(),
                    isCorrect: new FormControl(),
                  }
                ),
                new FormGroup(
                  {
                    Text: new FormControl(),
                    isCorrect: new FormControl(),
                  }
                ),
              ]
            )
          }
        ),
        new FormGroup(
          {
            Text: new FormControl(),
            Answers: new FormArray(
              [
                new FormGroup(
                  {
                    Text: new FormControl(),
                    isCorrect: new FormControl(),
                  }
                ),
                new FormGroup(
                  {
                    Text: new FormControl(),
                    isCorrect: new FormControl(),
                  }
                ),
                new FormGroup(
                  {
                    Text: new FormControl(),
                    isCorrect: new FormControl(),
                  }
                ),
                new FormGroup(
                  {
                    Text: new FormControl(),
                    isCorrect: new FormControl(),
                  }
                ),
              ]
            )
          }
        ),
      ]
    ),
  });
constructor(client: HttpClientService) { this._client = client }
ngOnInit(): void {
  }
getQuestionControls() {
    return this.surveyForm.controls['Questions'] as FormArray;
  }
getAnswerControls(question: any) {
    console.log(question['Answers'] as FormArray);
    return question['Answers'] as FormArray;
  }
selectAnswer(i: number, j: number) {
  }
save() {
    this._client.postData("survey", this.surveyForm.value).subscribe((res) => {
      console.log(res);
    });
  }
cancel() {
    this.surveyForm = new FormGroup({
      title: new FormControl(),
      Questions: new FormArray(
        [
          new FormGroup(
            {
              Text: new FormControl(),
              Answers: new FormArray(
                [
                  new FormGroup(
                    {
                      Text: new FormControl(),
                      isCorrect: new FormControl(),
                    }
                  ),
                  new FormGroup(
                    {
                      Text: new FormControl(),
                      isCorrect: new FormControl(),
                    }
                  ),
                  new FormGroup(
                    {
                      Text: new FormControl(),
                      isCorrect: new FormControl(),
                    }
                  ),
                  new FormGroup(
                    {
                      Text: new FormControl(),
                      isCorrect: new FormControl(),
                    }
                  ),
                ]
              )
            }
          ),
          new FormGroup(
            {
              Text: new FormControl(),
              Answers: new FormArray(
                [
                  new FormGroup(
                    {
                      Text: new FormControl(),
                      isCorrect: new FormControl(),
                    }
                  ),
                  new FormGroup(
                    {
                      Text: new FormControl(),
                      isCorrect: new FormControl(),
                    }
                  ),
                  new FormGroup(
                    {
                      Text: new FormControl(),
                      isCorrect: new FormControl(),
                    }
                  ),
                  new FormGroup(
                    {
                      Text: new FormControl(),
                      isCorrect: new FormControl(),
                    }
                  ),
                ]
              )
            }
          ),
          new FormGroup(
            {
              Text: new FormControl(),
              Answers: new FormArray(
                [
                  new FormGroup(
                    {
                      Text: new FormControl(),
                      isCorrect: new FormControl(),
                    }
                  ),
                  new FormGroup(
                    {
                      Text: new FormControl(),
                      isCorrect: new FormControl(),
                    }
                  ),
                  new FormGroup(
                    {
                      Text: new FormControl(),
                      isCorrect: new FormControl(),
                    }
                  ),
                  new FormGroup(
                    {
                      Text: new FormControl(),
                      isCorrect: new FormControl(),
                    }
                  ),
                ]
              )
            }
          ),
        ]
      ),
    });
  }
}

Выход такой -

Как мы можем видеть; он не показывает ответы или что-либо, связанное с этим.
Переписав файл ts как -

getAnswerControls(question: any) {
    console.log(question['Answers'] as FormArray);
    return question['Answers'];
  }

И файл html как -

......
<div formArrayName="Questions" class="mt-3">
      <div *ngFor="let question of getQuestionControls().controls; let i = index" [formGroupName]="i">
        <label>Question {{i+1}}</label>
        <input type="text" formControlName="Text" class="form-control mb-3">
        <div formArrayName="Answers" class="d-flex flex-wrap">
          <div>{{getAnswerControls(question.value).controls}}</div>
          <div *ngFor="let answer of getAnswerControls(question.value); let j = index" [formGroupName]="j"
            class="form-check mr-3 mb-3">
            <input class="form-check-input" type="radio" formControlName="isCorrect" [value]="j"
              (change)="selectAnswer(i, j)">
            <input type="text" formControlName="Text" class="form-control">
          </div>
        </div>
      </div>
    </div>
.......

Исправлена ​​проблема.

Другие серии этого сериала:
Первая часть
Вторая часть
Третья часть
Четвертая часть
И код указан обновлен в —
Github