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 { export class AddonBlockTimelineComponent implements OnInit, ICoreBlockComponent {
sort = new FormControl(); sort = new FormControl(AddonBlockTimelineSort.ByDates);
sort$!: Observable<AddonBlockTimelineSort>; sort$!: Observable<AddonBlockTimelineSort>;
sortOptions!: AddonBlockTimelineOption<AddonBlockTimelineSort>[]; sortOptions!: AddonBlockTimelineOption<AddonBlockTimelineSort>[];
filter = new FormControl(); filter = new FormControl(AddonBlockTimelineFilter.Next30Days);
filter$!: Observable<AddonBlockTimelineFilter>; filter$!: Observable<AddonBlockTimelineFilter>;
statusFilterOptions!: AddonBlockTimelineOption<AddonBlockTimelineFilter>[]; statusFilterOptions!: AddonBlockTimelineOption<AddonBlockTimelineFilter>[];
dateFilterOptions!: AddonBlockTimelineOption<AddonBlockTimelineFilter>[]; dateFilterOptions!: AddonBlockTimelineOption<AddonBlockTimelineFilter>[];

View File

@ -78,9 +78,9 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
// Form variables. // Form variables.
form: FormGroup; form: FormGroup;
typeControl: FormControl; typeControl: FormControl<AddonCalendarEventType | null>;
groupControl: FormControl; groupControl: FormControl<number | null>;
descriptionControl: FormControl; descriptionControl: FormControl<string>;
// Reminders. // Reminders.
remindersEnabled = false; remindersEnabled = false;
@ -103,9 +103,9 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
this.form = new FormGroup({}); this.form = new FormGroup({});
// Initialize form variables. // Initialize form variables.
this.typeControl = this.fb.control('', Validators.required); this.typeControl = this.fb.control(null, Validators.required);
this.groupControl = this.fb.control(''); this.groupControl = this.fb.control(null);
this.descriptionControl = this.fb.control(''); this.descriptionControl = this.fb.control('', { nonNullable: true });
this.form.addControl('name', this.fb.control('', Validators.required)); this.form.addControl('name', this.fb.control('', Validators.required));
this.form.addControl('eventtype', this.typeControl); this.form.addControl('eventtype', this.typeControl);
this.form.addControl('categoryid', this.fb.control('')); 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.name.setValue(event.name);
this.form.controls.timestart.setValue(CoreTimeUtils.toDatetimeFormat(event.timestart * 1000)); 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.categoryid.setValue(event.categoryid || '');
this.form.controls.courseid.setValue(courseId || ''); this.form.controls.courseid.setValue(courseId || '');
this.form.controls.groupcourseid.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.description.setValue(event.description);
this.form.controls.location.setValue(event.location); this.form.controls.location.setValue(event.location);
@ -410,7 +410,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
try { try {
await this.loadGroups(courseId); await this.loadGroups(courseId);
this.groupControl.setValue(''); this.groupControl.setValue(null);
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error getting data.'); 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 { export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedbackPluginBaseComponent implements OnInit {
control?: FormControl; control?: FormControl<string>;
component = AddonModAssignProvider.COMPONENT; component = AddonModAssignProvider.COMPONENT;
text = ''; text = '';
isSent = false; isSent = false;
@ -76,7 +76,7 @@ export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedb
} }
}); });
} else if (this.edit) { } else if (this.edit) {
this.control = this.fb.control(this.text); this.control = this.fb.control(this.text, { nonNullable: true });
} }
} finally { } finally {
this.loaded = true; this.loaded = true;

View File

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

View File

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

View File

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

View File

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

View File

@ -369,7 +369,7 @@ export class AddonModLessonHelperProvider {
}; };
// Init the control. // Init the control.
essayQuestion.control = this.formBuilder.control(''); essayQuestion.control = this.formBuilder.control('', { nonNullable: true });
questionForm.addControl(essayQuestion.textarea.name, essayQuestion.control); questionForm.addControl(essayQuestion.textarea.name, essayQuestion.control);
} }
@ -635,7 +635,7 @@ export type AddonModLessonInputQuestion = AddonModLessonQuestionBasicData & {
export type AddonModLessonEssayQuestion = AddonModLessonQuestionBasicData & { export type AddonModLessonEssayQuestion = AddonModLessonQuestionBasicData & {
useranswer?: string; // User answer, for reviewing. useranswer?: string; // User answer, for reviewing.
textarea?: AddonModLessonTextareaData; // Data for the textarea. 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. courseId?: number; // Course the wiki belongs to.
title?: string; // Title to display. title?: string; // Title to display.
pageForm: FormGroup; // The form group. 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. canEditTitle = false; // Whether title can be edited.
loaded = false; // Whether the data has been loaded. loaded = false; // Whether the data has been loaded.
component = AddonModWikiProvider.COMPONENT; // Component to link the files to. component = AddonModWikiProvider.COMPONENT; // Component to link the files to.
@ -74,7 +74,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
constructor( constructor(
protected formBuilder: FormBuilder, protected formBuilder: FormBuilder,
) { ) {
this.contentControl = this.formBuilder.control(''); this.contentControl = this.formBuilder.control('', { nonNullable: true });
this.pageForm = this.formBuilder.group({}); this.pageForm = this.formBuilder.group({});
} }

View File

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

View File

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

View File

@ -27,20 +27,23 @@ import { CoreUtils } from '@services/utils/utils';
templateUrl: 'addon-user-profile-field-checkbox.html', templateUrl: 'addon-user-profile-field-checkbox.html',
styleUrls: ['./checkbox.scss'], styleUrls: ['./checkbox.scss'],
}) })
export class AddonUserProfileFieldCheckboxComponent extends CoreUserProfileFieldBaseComponent { export class AddonUserProfileFieldCheckboxComponent extends CoreUserProfileFieldBaseComponent<boolean> {
/** /**
* Create the Form control. * Create the Form control.
* *
* @returns Form control. * @returns Form control.
*/ */
protected createFormControl(field: AuthEmailSignupProfileField): FormControl { protected createFormControl(field: AuthEmailSignupProfileField): FormControl<boolean> {
const formData = { const formData = {
value: CoreUtils.isTrueOrOne(field.defaultdata), value: CoreUtils.isTrueOrOne(field.defaultdata),
disabled: this.disabled, 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', selector: 'addon-user-profile-field-datetime',
templateUrl: 'addon-user-profile-field-datetime.html', templateUrl: 'addon-user-profile-field-datetime.html',
}) })
export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileFieldBaseComponent { export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileFieldBaseComponent<string | undefined> {
ionDateTimePresentation = 'date'; ionDateTimePresentation = 'date';
min?: string; min?: string;
@ -84,13 +84,16 @@ export class AddonUserProfileFieldDatetimeComponent extends CoreUserProfileField
* *
* @returns Form control. * @returns Form control.
*/ */
protected createFormControl(field: AuthEmailSignupProfileField): FormControl { protected createFormControl(field: AuthEmailSignupProfileField): FormControl<string | undefined> {
const formData = { const formData = {
value: field.defaultdata != '0' ? field.defaultdata : undefined, value: field.defaultdata != '0' ? field.defaultdata : undefined,
disabled: this.disabled, 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 { 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() errorMessages: Record<string, string> = {}; // Error messages to show. Keys must be the name of the error.
@Input() errorText = ''; // Set other non automatic errors. @Input() errorText = ''; // Set other non automatic errors.
errorKeys: string[] = []; errorKeys: string[] = [];

View File

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

View File

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

View File

@ -24,7 +24,7 @@ import { CoreUserProfileField } from '@features/user/services/user';
@Component({ @Component({
template: '', 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() field?: AuthEmailSignupProfileField | CoreUserProfileField; // The profile field to be rendered.
@Input() signup = false; // True if editing the field in signup. Defaults to false. @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() 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. @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 = ''; modelName = '';
value?: string; value?: string;
required?: boolean; required?: boolean;
@ -91,13 +91,16 @@ export abstract class CoreUserProfileFieldBaseComponent implements OnInit {
* *
* @returns Form control. * @returns Form control.
*/ */
protected createFormControl(field: AuthEmailSignupProfileField): FormControl { protected createFormControl(field: AuthEmailSignupProfileField): FormControl<T> {
const formData = { const formData = {
value: field.defaultdata, value: (field.defaultdata ?? '') as T,
disabled: this.disabled, 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. * @param control Form control.
* @returns Form control value observable. * @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( return control.valueChanges.pipe(
startWithOnSubscribed(() => control.value), startWithOnSubscribed(() => control.value),
filter(value => value !== null), filter((value): value is T => value !== null),
); );
} }