MOBILE-3947 form: Add types to FormControl

main
Dani Palou 2024-01-11 14:50:25 +01:00
parent 350f5792ca
commit 27b980d710
18 changed files with 63 additions and 54 deletions

View File

@ -39,10 +39,10 @@ import { CoreLogger } from '@singletons/logger';
})
export class AddonBlockTimelineComponent implements OnInit, ICoreBlockComponent {
sort = new FormControl();
sort = new FormControl(AddonBlockTimelineSort.ByDates);
sort$!: Observable<AddonBlockTimelineSort>;
sortOptions!: AddonBlockTimelineOption<AddonBlockTimelineSort>[];
filter = new FormControl();
filter = new FormControl(AddonBlockTimelineFilter.Next30Days);
filter$!: Observable<AddonBlockTimelineFilter>;
statusFilterOptions!: AddonBlockTimelineOption<AddonBlockTimelineFilter>[];
dateFilterOptions!: AddonBlockTimelineOption<AddonBlockTimelineFilter>[];

View File

@ -78,9 +78,9 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
// Form variables.
form: FormGroup;
typeControl: FormControl;
groupControl: FormControl;
descriptionControl: FormControl;
typeControl: FormControl<AddonCalendarEventType | null>;
groupControl: FormControl<number | null>;
descriptionControl: FormControl<string>;
// Reminders.
remindersEnabled = false;
@ -103,9 +103,9 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
this.form = new FormGroup({});
// Initialize form variables.
this.typeControl = this.fb.control('', Validators.required);
this.groupControl = this.fb.control('');
this.descriptionControl = this.fb.control('');
this.typeControl = this.fb.control(null, Validators.required);
this.groupControl = this.fb.control(null);
this.descriptionControl = this.fb.control('', { nonNullable: true });
this.form.addControl('name', this.fb.control('', Validators.required));
this.form.addControl('eventtype', this.typeControl);
this.form.addControl('categoryid', this.fb.control(''));
@ -322,11 +322,11 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
this.form.controls.name.setValue(event.name);
this.form.controls.timestart.setValue(CoreTimeUtils.toDatetimeFormat(event.timestart * 1000));
this.form.controls.eventtype.setValue(event.eventtype);
this.typeControl.setValue(event.eventtype as AddonCalendarEventType);
this.form.controls.categoryid.setValue(event.categoryid || '');
this.form.controls.courseid.setValue(courseId || '');
this.form.controls.groupcourseid.setValue(courseId || '');
this.form.controls.groupid.setValue(event.groupid || '');
this.groupControl.setValue(event.groupid || null);
this.form.controls.description.setValue(event.description);
this.form.controls.location.setValue(event.location);
@ -410,7 +410,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
try {
await this.loadGroups(courseId);
this.groupControl.setValue('');
this.groupControl.setValue(null);
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting data.');
}

View File

@ -34,7 +34,7 @@ import { AddonModAssignFeedbackPluginBaseComponent } from '@addons/mod/assign/cl
})
export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedbackPluginBaseComponent implements OnInit {
control?: FormControl;
control?: FormControl<string>;
component = AddonModAssignProvider.COMPONENT;
text = '';
isSent = false;
@ -76,7 +76,7 @@ export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedb
}
});
} else if (this.edit) {
this.control = this.fb.control(this.text);
this.control = this.fb.control(this.text, { nonNullable: true });
}
} finally {
this.loaded = true;

View File

@ -31,7 +31,7 @@ import { AddonModAssignSubmissionOnlineTextPluginData } from '../services/handle
})
export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignSubmissionPluginBaseComponent implements OnInit {
control?: FormControl;
control?: FormControl<string>;
words = 0;
component = AddonModAssignProvider.COMPONENT;
text = '';
@ -94,7 +94,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS
});
} else {
// Create and add the control.
this.control = this.fb.control(this.text);
this.control = this.fb.control(this.text, { nonNullable: true });
}
// Calculate initial words.

View File

@ -83,7 +83,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
@ViewChild('replyFormEl') formElement!: ElementRef;
messageControl = new FormControl();
messageControl = new FormControl<string | null>(null);
uniqueId!: string;
defaultReplySubject!: string;

View File

@ -70,7 +70,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
@ViewChild(CoreEditorRichTextEditorComponent) messageEditor!: CoreEditorRichTextEditorComponent;
component = AddonModForumProvider.COMPONENT;
messageControl = new FormControl();
messageControl = new FormControl<string | null>(null);
groupsLoaded = false;
showGroups = false;
hasOffline = false;

View File

@ -58,7 +58,7 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave {
courseId!: number;
loaded = false;
glossary?: AddonModGlossaryGlossary;
definitionControl = new FormControl();
definitionControl = new FormControl<string | null>(null);
categories: AddonModGlossaryCategory[] = [];
showAliases = true;
editorExtraParams: Record<string, unknown> = {};

View File

@ -369,7 +369,7 @@ export class AddonModLessonHelperProvider {
};
// Init the control.
essayQuestion.control = this.formBuilder.control('');
essayQuestion.control = this.formBuilder.control('', { nonNullable: true });
questionForm.addControl(essayQuestion.textarea.name, essayQuestion.control);
}
@ -635,7 +635,7 @@ export type AddonModLessonInputQuestion = AddonModLessonQuestionBasicData & {
export type AddonModLessonEssayQuestion = AddonModLessonQuestionBasicData & {
useranswer?: string; // User answer, for reviewing.
textarea?: AddonModLessonTextareaData; // Data for the textarea.
control?: FormControl; // Form control.
control?: FormControl<string>; // Form control.
};
/**

View File

@ -47,7 +47,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
courseId?: number; // Course the wiki belongs to.
title?: string; // Title to display.
pageForm: FormGroup; // The form group.
contentControl: FormControl; // The FormControl for the page content.
contentControl: FormControl<string>; // The FormControl for the page content.
canEditTitle = false; // Whether title can be edited.
loaded = false; // Whether the data has been loaded.
component = AddonModWikiProvider.COMPONENT; // Component to link the files to.
@ -74,7 +74,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
constructor(
protected formBuilder: FormBuilder,
) {
this.contentControl = this.formBuilder.control('');
this.contentControl = this.formBuilder.control('', { nonNullable: true });
this.pageForm = this.formBuilder.group({});
}

View File

@ -73,7 +73,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe
assessmentStrategyLoaded = false;
notSupported = false;
feedbackText = '';
feedbackControl = new FormControl();
feedbackControl = new FormControl<string | null>(null);
overallFeedkback = false;
overallFeedkbackRequired = false;
component = ADDON_MOD_WORKSHOP_COMPONENT;

View File

@ -32,7 +32,7 @@ import { CoreFileEntry } from '@services/file-helper';
})
export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent<AddonModQuizEssayQuestion> {
formControl?: FormControl;
formControl?: FormControl<string | null>;
attachments?: CoreFileEntry[];
uploadFilesSupported = false;
@ -52,7 +52,7 @@ export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent<AddonMod
this.initEssayComponent(this.review);
this.formControl = this.fb.control(this.question?.textarea?.text);
this.formControl = this.fb.control(this.question?.textarea?.text ?? null);
if (this.question?.allowsAttachments && this.uploadFilesSupported && !this.review) {
this.loadAttachments();

View File

@ -27,20 +27,23 @@ import { CoreUtils } from '@services/utils/utils';
templateUrl: 'addon-user-profile-field-checkbox.html',
styleUrls: ['./checkbox.scss'],
})
export class AddonUserProfileFieldCheckboxComponent extends CoreUserProfileFieldBaseComponent {
export class AddonUserProfileFieldCheckboxComponent extends CoreUserProfileFieldBaseComponent<boolean> {
/**
* Create the Form control.
*
* @returns Form control.
*/
protected createFormControl(field: AuthEmailSignupProfileField): FormControl {
protected createFormControl(field: AuthEmailSignupProfileField): FormControl<boolean> {
const formData = {
value: CoreUtils.isTrueOrOne(field.defaultdata),
disabled: this.disabled,
};
return new FormControl(formData, this.required && !field.locked ? Validators.requiredTrue : null);
return new FormControl(formData, {
validators: this.required && !field.locked ? Validators.requiredTrue : null,
nonNullable: true,
});
}
}

View File

@ -28,7 +28,7 @@ import { CoreUserProfileFieldBaseComponent } from '@features/user/classes/base-p
selector: 'addon-user-profile-field-datetime',
templateUrl: 'addon-user-profile-field-datetime.html',
})
export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileFieldBaseComponent {
export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileFieldBaseComponent<string | undefined> {
ionDateTimePresentation = 'date';
min?: string;
@ -84,13 +84,16 @@ export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileField
*
* @returns Form control.
*/
protected createFormControl(field: AuthEmailSignupProfileField): FormControl {
protected createFormControl(field: AuthEmailSignupProfileField): FormControl<string | undefined> {
const formData = {
value: field.defaultdata != '0' ? field.defaultdata : undefined,
disabled: this.disabled,
};
return new FormControl(formData, this.required && !field.locked ? Validators.required : null);
return new FormControl(formData, {
validators: this.required && !field.locked ? Validators.required : null,
nonNullable: true,
});
}
}

View File

@ -40,7 +40,7 @@ import { FormControl } from '@angular/forms';
})
export class CoreInputErrorsComponent implements OnInit, OnChanges {
@Input() control?: FormControl; // Needed to be able to check the validity of the input.
@Input() control?: FormControl<unknown>; // Needed to be able to check the validity of the input.
@Input() errorMessages: Record<string, string> = {}; // Error messages to show. Keys must be the name of the error.
@Input() errorText = ''; // Set other non automatic errors.
errorKeys: string[] = [];

View File

@ -66,7 +66,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
// @todo Implement ControlValueAccessor https://angular.io/api/forms/ControlValueAccessor.
@Input() placeholder = ''; // Placeholder to set in textarea.
@Input() control?: FormControl; // Form control.
@Input() control?: FormControl<string | undefined | null>; // Form control.
@Input() name = 'core-rich-text-editor'; // Name to set to the textarea.
@Input() component?: string; // The component to link the files to.
@Input() componentId?: number; // An ID to use in conjunction with the component.
@ -75,7 +75,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
@Input() contextInstanceId?: number; // The instance ID related to the context.
@Input() elementId?: string; // An ID to set to the element.
@Input() draftExtraParams?: Record<string, unknown>; // Extra params to identify the draft.
@Output() contentChanged: EventEmitter<string>;
@Output() contentChanged: EventEmitter<string | undefined | null>;
@ViewChild('editor') editor?: ElementRef; // WYSIWYG editor.
@ViewChild('textarea') textarea?: IonTextarea; // Textarea editor.
@ -190,8 +190,8 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
// Setup the editor.
this.editorElement = this.editor?.nativeElement as HTMLDivElement;
this.setContent(this.control?.value);
this.originalContent = this.control?.value;
this.lastDraft = this.control?.value;
this.originalContent = this.control?.value ?? undefined;
this.lastDraft = this.control?.value ?? '';
// Use paragraph on enter.
// eslint-disable-next-line deprecation/deprecation
@ -261,19 +261,19 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
// Apply the new content.
this.setContent(newValue);
this.originalContent = newValue;
this.originalContent = newValue ?? undefined;
this.infoMessage = undefined;
// Save a draft so the original content is saved.
this.lastDraft = newValue;
this.lastDraft = newValue ?? '';
CoreEditorOffline.saveDraft(
this.contextLevel || '',
this.contextInstanceId || 0,
this.elementId || '',
this.draftExtraParams || {},
this.pageInstance,
newValue,
newValue,
this.lastDraft,
this.originalContent,
);
});
@ -579,7 +579,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
* @param value text
* @returns If value is null only a white space.
*/
protected isNullOrWhiteSpace(value: string | null): boolean {
protected isNullOrWhiteSpace(value: string | null | undefined): boolean {
if (value == null || value === undefined) {
return true;
}
@ -595,7 +595,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
*
* @param value New content.
*/
protected setContent(value: string | null): void {
protected setContent(value: string | null | undefined): void {
if (!this.editorElement || !this.textarea) {
return;
}
@ -974,7 +974,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
return;
}
const newText = this.control.value;
const newText = this.control.value ?? '';
if (this.lastDraft == newText) {
// Text hasn't changed, nothing to save.

View File

@ -69,8 +69,8 @@ export class CoreLoginEmailSignupPage implements OnInit {
// Data for age verification.
ageVerificationForm: FormGroup;
countryControl: FormControl;
signUpCountryControl?: FormControl;
countryControl: FormControl<string>;
signUpCountryControl?: FormControl<string>;
isMinor = false; // Whether the user is minor age.
ageDigitalConsentVerification?: boolean; // Whether the age verification is enabled.
supportName?: string;
@ -93,7 +93,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
this.ageVerificationForm = this.fb.group({
age: ['', Validators.required],
});
this.countryControl = this.fb.control('', Validators.required);
this.countryControl = this.fb.control('', { validators: Validators.required, nonNullable: true });
this.ageVerificationForm.addControl('country', this.countryControl);
// Create the signupForm with the basic controls. More controls will be added later.
@ -141,7 +141,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
*/
protected completeFormGroup(): void {
this.signupForm.addControl('city', this.fb.control(this.settings?.defaultcity || ''));
this.signUpCountryControl = this.fb.control(this.settings?.country || '');
this.signUpCountryControl = this.fb.control(this.settings?.country || '', { nonNullable: true });
this.signupForm.addControl('country', this.signUpCountryControl);
// Add the name fields.

View File

@ -24,7 +24,7 @@ import { CoreUserProfileField } from '@features/user/services/user';
@Component({
template: '',
})
export abstract class CoreUserProfileFieldBaseComponent implements OnInit {
export abstract class CoreUserProfileFieldBaseComponent<T = string> implements OnInit {
@Input() field?: AuthEmailSignupProfileField | CoreUserProfileField; // The profile field to be rendered.
@Input() signup = false; // True if editing the field in signup. Defaults to false.
@ -36,7 +36,7 @@ export abstract class CoreUserProfileFieldBaseComponent implements OnInit {
@Input() contextInstanceId?: number; // The instance ID related to the context.
@Input() courseId?: number; // Course ID the field belongs to (if any). It can be used to improve performance with filters.
control?: FormControl;
control?: FormControl<T>;
modelName = '';
value?: string;
required?: boolean;
@ -91,13 +91,16 @@ export abstract class CoreUserProfileFieldBaseComponent implements OnInit {
*
* @returns Form control.
*/
protected createFormControl(field: AuthEmailSignupProfileField): FormControl {
protected createFormControl(field: AuthEmailSignupProfileField): FormControl<T> {
const formData = {
value: field.defaultdata,
value: (field.defaultdata ?? '') as T,
disabled: this.disabled,
};
return new FormControl(formData, this.required && !field.locked ? Validators.required : null);
return new FormControl(formData, {
validators: this.required && !field.locked ? Validators.required : null,
nonNullable: true,
});
}
}

View File

@ -22,10 +22,10 @@ import { catchError, filter } from 'rxjs/operators';
* @param control Form control.
* @returns Form control value observable.
*/
export function formControlValue<T = unknown>(control: FormControl): Observable<T> {
export function formControlValue<T = unknown>(control: FormControl<T | null>): Observable<T> {
return control.valueChanges.pipe(
startWithOnSubscribed(() => control.value),
filter(value => value !== null),
filter((value): value is T => value !== null),
);
}