Учебники по программированию показывают нам многообещающую землю, где все происходит так, как вы думаете; как только вы думаете. Но реальный мир в большинстве случаев работает не так. Здесь; вы часами отлаживаете какую-то ошибку 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