diff --git a/src/addons/block/timeline/components/timeline/timeline.ts b/src/addons/block/timeline/components/timeline/timeline.ts index e61a52f5c..6969c63c3 100644 --- a/src/addons/block/timeline/components/timeline/timeline.ts +++ b/src/addons/block/timeline/components/timeline/timeline.ts @@ -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; sortOptions!: AddonBlockTimelineOption[]; - filter = new FormControl(); + filter = new FormControl(AddonBlockTimelineFilter.Next30Days); filter$!: Observable; statusFilterOptions!: AddonBlockTimelineOption[]; dateFilterOptions!: AddonBlockTimelineOption[]; diff --git a/src/addons/calendar/pages/edit-event/edit-event.ts b/src/addons/calendar/pages/edit-event/edit-event.ts index a30321547..653aa35b9 100644 --- a/src/addons/calendar/pages/edit-event/edit-event.ts +++ b/src/addons/calendar/pages/edit-event/edit-event.ts @@ -78,9 +78,9 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { // Form variables. form: FormGroup; - typeControl: FormControl; - groupControl: FormControl; - descriptionControl: FormControl; + typeControl: FormControl; + groupControl: FormControl; + descriptionControl: FormControl; // 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.'); } diff --git a/src/addons/mod/assign/feedback/comments/component/comments.ts b/src/addons/mod/assign/feedback/comments/component/comments.ts index 6271598e2..c5f268f23 100644 --- a/src/addons/mod/assign/feedback/comments/component/comments.ts +++ b/src/addons/mod/assign/feedback/comments/component/comments.ts @@ -34,7 +34,7 @@ import { AddonModAssignFeedbackPluginBaseComponent } from '@addons/mod/assign/cl }) export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedbackPluginBaseComponent implements OnInit { - control?: FormControl; + control?: FormControl; 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; diff --git a/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts b/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts index ca9afe172..f91d185c9 100644 --- a/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts +++ b/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts @@ -31,7 +31,7 @@ import { AddonModAssignSubmissionOnlineTextPluginData } from '../services/handle }) export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignSubmissionPluginBaseComponent implements OnInit { - control?: FormControl; + control?: FormControl; 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. diff --git a/src/addons/mod/forum/components/post/post.ts b/src/addons/mod/forum/components/post/post.ts index 63f44e1b1..ab834b428 100644 --- a/src/addons/mod/forum/components/post/post.ts +++ b/src/addons/mod/forum/components/post/post.ts @@ -83,7 +83,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges @ViewChild('replyFormEl') formElement!: ElementRef; - messageControl = new FormControl(); + messageControl = new FormControl(null); uniqueId!: string; defaultReplySubject!: string; diff --git a/src/addons/mod/forum/pages/new-discussion/new-discussion.ts b/src/addons/mod/forum/pages/new-discussion/new-discussion.ts index 6eb86ce9a..8dc2a1563 100644 --- a/src/addons/mod/forum/pages/new-discussion/new-discussion.ts +++ b/src/addons/mod/forum/pages/new-discussion/new-discussion.ts @@ -70,7 +70,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea @ViewChild(CoreEditorRichTextEditorComponent) messageEditor!: CoreEditorRichTextEditorComponent; component = AddonModForumProvider.COMPONENT; - messageControl = new FormControl(); + messageControl = new FormControl(null); groupsLoaded = false; showGroups = false; hasOffline = false; diff --git a/src/addons/mod/glossary/pages/edit/edit.ts b/src/addons/mod/glossary/pages/edit/edit.ts index d5d9a3ed8..5705cf257 100644 --- a/src/addons/mod/glossary/pages/edit/edit.ts +++ b/src/addons/mod/glossary/pages/edit/edit.ts @@ -58,7 +58,7 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave { courseId!: number; loaded = false; glossary?: AddonModGlossaryGlossary; - definitionControl = new FormControl(); + definitionControl = new FormControl(null); categories: AddonModGlossaryCategory[] = []; showAliases = true; editorExtraParams: Record = {}; diff --git a/src/addons/mod/lesson/services/lesson-helper.ts b/src/addons/mod/lesson/services/lesson-helper.ts index b12f4efc2..2fd998089 100644 --- a/src/addons/mod/lesson/services/lesson-helper.ts +++ b/src/addons/mod/lesson/services/lesson-helper.ts @@ -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; // Form control. }; /** diff --git a/src/addons/mod/wiki/pages/edit/edit.ts b/src/addons/mod/wiki/pages/edit/edit.ts index e1ab5857d..816bd2ffa 100644 --- a/src/addons/mod/wiki/pages/edit/edit.ts +++ b/src/addons/mod/wiki/pages/edit/edit.ts @@ -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; // 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({}); } diff --git a/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts b/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts index 2c53eea33..95f62fd6d 100644 --- a/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts +++ b/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts @@ -73,7 +73,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe assessmentStrategyLoaded = false; notSupported = false; feedbackText = ''; - feedbackControl = new FormControl(); + feedbackControl = new FormControl(null); overallFeedkback = false; overallFeedkbackRequired = false; component = ADDON_MOD_WORKSHOP_COMPONENT; diff --git a/src/addons/qtype/essay/component/essay.ts b/src/addons/qtype/essay/component/essay.ts index d216b87f4..197389dbd 100644 --- a/src/addons/qtype/essay/component/essay.ts +++ b/src/addons/qtype/essay/component/essay.ts @@ -32,7 +32,7 @@ import { CoreFileEntry } from '@services/file-helper'; }) export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent { - formControl?: FormControl; + formControl?: FormControl; attachments?: CoreFileEntry[]; uploadFilesSupported = false; @@ -52,7 +52,7 @@ export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent { /** * Create the Form control. * * @returns Form control. */ - protected createFormControl(field: AuthEmailSignupProfileField): FormControl { + protected createFormControl(field: AuthEmailSignupProfileField): FormControl { 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, + }); } } diff --git a/src/addons/userprofilefield/datetime/component/datetime.ts b/src/addons/userprofilefield/datetime/component/datetime.ts index c65fec86f..17e28354a 100644 --- a/src/addons/userprofilefield/datetime/component/datetime.ts +++ b/src/addons/userprofilefield/datetime/component/datetime.ts @@ -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 { 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 { 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, + }); } } diff --git a/src/core/components/input-errors/input-errors.ts b/src/core/components/input-errors/input-errors.ts index b43c55cdd..da973bd0d 100644 --- a/src/core/components/input-errors/input-errors.ts +++ b/src/core/components/input-errors/input-errors.ts @@ -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; // Needed to be able to check the validity of the input. @Input() errorMessages: Record = {}; // Error messages to show. Keys must be the name of the error. @Input() errorText = ''; // Set other non automatic errors. errorKeys: string[] = []; diff --git a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts index 48787de7f..1ef6e6429 100644 --- a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts +++ b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts @@ -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; // 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; // Extra params to identify the draft. - @Output() contentChanged: EventEmitter; + @Output() contentChanged: EventEmitter; @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. diff --git a/src/core/features/login/pages/email-signup/email-signup.ts b/src/core/features/login/pages/email-signup/email-signup.ts index 43797e233..45cb6f9fc 100644 --- a/src/core/features/login/pages/email-signup/email-signup.ts +++ b/src/core/features/login/pages/email-signup/email-signup.ts @@ -69,8 +69,8 @@ export class CoreLoginEmailSignupPage implements OnInit { // Data for age verification. ageVerificationForm: FormGroup; - countryControl: FormControl; - signUpCountryControl?: FormControl; + countryControl: FormControl; + signUpCountryControl?: FormControl; 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. diff --git a/src/core/features/user/classes/base-profilefield-component.ts b/src/core/features/user/classes/base-profilefield-component.ts index 78a7fea8c..9a61cf327 100644 --- a/src/core/features/user/classes/base-profilefield-component.ts +++ b/src/core/features/user/classes/base-profilefield-component.ts @@ -24,7 +24,7 @@ import { CoreUserProfileField } from '@features/user/services/user'; @Component({ template: '', }) -export abstract class CoreUserProfileFieldBaseComponent implements OnInit { +export abstract class CoreUserProfileFieldBaseComponent 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; 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 { 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, + }); } } diff --git a/src/core/utils/rxjs.ts b/src/core/utils/rxjs.ts index 63b959856..df1693191 100644 --- a/src/core/utils/rxjs.ts +++ b/src/core/utils/rxjs.ts @@ -22,10 +22,10 @@ import { catchError, filter } from 'rxjs/operators'; * @param control Form control. * @returns Form control value observable. */ -export function formControlValue(control: FormControl): Observable { +export function formControlValue(control: FormControl): Observable { return control.valueChanges.pipe( startWithOnSubscribed(() => control.value), - filter(value => value !== null), + filter((value): value is T => value !== null), ); }