diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts
index 27ea76157..dfb7b2b51 100644
--- a/src/app/components/components.module.ts
+++ b/src/app/components/components.module.ts
@@ -13,18 +13,24 @@
// limitations under the License.
import { NgModule } from '@angular/core';
+import { IonicModule } from '@ionic/angular';
import { CoreIconComponent } from './icon/icon';
+import { CoreLoadingComponent } from './loading/loading';
import { CoreShowPasswordComponent } from './show-password/show-password';
@NgModule({
declarations: [
CoreIconComponent,
+ CoreLoadingComponent,
CoreShowPasswordComponent,
],
- imports: [],
+ imports: [
+ IonicModule,
+ ],
exports: [
CoreIconComponent,
+ CoreLoadingComponent,
CoreShowPasswordComponent,
],
})
diff --git a/src/app/components/loading/core-loading.html b/src/app/components/loading/core-loading.html
new file mode 100644
index 000000000..da4887db0
--- /dev/null
+++ b/src/app/components/loading/core-loading.html
@@ -0,0 +1,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/components/loading/loading.scss b/src/app/components/loading/loading.scss
new file mode 100644
index 000000000..afe335a58
--- /dev/null
+++ b/src/app/components/loading/loading.scss
@@ -0,0 +1,67 @@
+ion-app.app-root {
+ core-loading {
+ // @todo @include core-transition(height, 200ms);
+
+ .core-loading-container {
+ width: 100%;
+ text-align: center;
+ padding-top: 10px;
+ clear: both;
+ /* @todo @include darkmode() {
+ color: $core-dark-text-color;
+ } */
+ }
+
+ .core-loading-content {
+ display: inline;
+ padding-bottom: 1px; /* This makes height be real */
+ }
+
+ &.core-loading-noheight .core-loading-content {
+ height: auto;
+ }
+
+ &.safe-area-page {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+
+ > .core-loading-content > *:not[padding],
+ > .core-loading-content-loading > *:not[padding] {
+ // @todo @include safe-area-padding-horizontal(0px, 0px);
+ }
+ }
+ }
+
+ .scroll-content > core-loading,
+ ion-content > .scroll-content > core-loading,
+ core-tab core-loading,
+ .core-loading-center {
+ position: static !important;
+ }
+
+ .scroll-content > core-loading,
+ ion-content > .scroll-content > core-loading,
+ core-tab core-loading,
+ .core-loading-center,
+ core-loading.core-loading-loaded {
+ position: relative;
+
+ > .core-loading-container {
+ position: absolute;
+ // @todo @include position(0, 0, 0, 0);
+ display: table;
+ height: 100%;
+ width: 100%;
+ z-index: 1;
+ margin: 0;
+ padding: 0;
+ clear: both;
+
+ .core-loading-spinner {
+ display: table-cell;
+ text-align: center;
+ vertical-align: middle;
+ }
+ }
+ }
+}
diff --git a/src/app/components/loading/loading.ts b/src/app/components/loading/loading.ts
new file mode 100644
index 000000000..d4d0d0e22
--- /dev/null
+++ b/src/app/components/loading/loading.ts
@@ -0,0 +1,120 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Component, Input, OnInit, OnChanges, SimpleChange, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
+
+import { CoreEvents, CoreEventsProvider } from '@services/events';
+import { CoreUtils } from '@services/utils/utils';
+import { Translate } from '@singletons/core.singletons';
+
+/**
+ * Component to show a loading spinner and message while data is being loaded.
+ *
+ * It will show a spinner with a message and hide all the content until 'hideUntil' variable is set to a truthy value (!!hideUntil).
+ * If 'message' isn't set, default message "Loading" is shown.
+ * 'message' attribute accepts hardcoded strings, variables, filters, etc. E.g. [message]="'core.loading' | translate".
+ *
+ * Usage:
+ *
+ *
+ *
+ *
+ * IMPORTANT: Due to how ng-content works in Angular, the content of core-loading will be executed as soon as your view
+ * is loaded, even if the content hidden. So if you have the following code:
+ *
+ *
+ * The component "my-component" will be initialized immediately, even if dataLoaded is false, but it will be hidden. If you want
+ * your component to be initialized only if dataLoaded is true, then you should use ngIf:
+ *
+ */
+@Component({
+ selector: 'core-loading',
+ templateUrl: 'core-loading.html',
+ styleUrls: ['loading.scss'],
+ // @todo animations: [coreShowHideAnimation],
+})
+export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit {
+
+ @Input() hideUntil: unknown; // Determine when should the contents be shown.
+ @Input() message?: string; // Message to show while loading.
+ @ViewChild('content') content: ElementRef;
+
+ protected uniqueId: string;
+ protected element: HTMLElement; // Current element.
+
+ constructor(element: ElementRef) {
+ this.element = element.nativeElement;
+ }
+
+ /**
+ * Component being initialized.
+ */
+ ngOnInit(): void {
+ // Calculate the unique ID.
+ this.uniqueId = 'core-loading-content-' + CoreUtils.instance.getUniqueId('CoreLoadingComponent');
+
+ if (!this.message) {
+ // Default loading message.
+ this.message = Translate.instance.instant('core.loading');
+ }
+ }
+
+ /**
+ * View has been initialized.
+ */
+ ngAfterViewInit(): void {
+ // Add class if loaded on init.
+ if (this.hideUntil) {
+ this.element.classList.add('core-loading-loaded');
+ this.content?.nativeElement.classList.add('core-loading-content');
+ } else {
+ this.content?.nativeElement.classList.remove('core-loading-content');
+ this.content?.nativeElement.classList.add('core-loading-content-loading');
+ }
+ }
+
+ /**
+ * Component input changed.
+ *
+ * @param changes Changes.
+ */
+ ngOnChanges(changes: { [name: string]: SimpleChange }): void {
+ if (changes.hideUntil) {
+ if (this.hideUntil) {
+ setTimeout(() => {
+ // Content is loaded so, center the spinner on the content itself.
+ this.element.classList.add('core-loading-loaded');
+ setTimeout(() => {
+ // Change CSS to force calculate height.
+ this.content?.nativeElement.classList.add('core-loading-content');
+ this.content?.nativeElement.classList.remove('core-loading-content-loading');
+ }, 500);
+ });
+ } else {
+ this.element.classList.remove('core-loading-loaded');
+ this.content?.nativeElement.classList.remove('core-loading-content');
+ this.content?.nativeElement.classList.add('core-loading-content-loading');
+ }
+
+ // Trigger the event after a timeout since the elements inside ngIf haven't been added to DOM yet.
+ setTimeout(() => {
+ CoreEvents.instance.trigger(CoreEventsProvider.CORE_LOADING_CHANGED, {
+ loaded: !!this.hideUntil,
+ uniqueId: this.uniqueId,
+ });
+ });
+ }
+ }
+
+}