MOBILE-3947 core: Move from ViewEngine to Ivy and fix plugins
parent
22dbd6ad99
commit
4053e2d741
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { APP_INITIALIZER, COMPILER_OPTIONS, NgModule } from '@angular/core';
|
||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { RouteReuseStrategy } from '@angular/router';
|
||||
|
@ -28,7 +28,7 @@ import { AddonsModule } from '@addons/addons.module';
|
|||
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { CoreCronDelegate } from '@services/cron';
|
||||
import { CoreSiteInfoCronHandler } from '@services/handlers/site-info-cron';
|
||||
import { moodleTransitionAnimation } from '@classes/page-transition';
|
||||
|
@ -71,8 +71,6 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
|
|||
],
|
||||
providers: [
|
||||
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
|
||||
{ provide: COMPILER_OPTIONS, useValue: {}, multi: true },
|
||||
{ provide: JitCompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] },
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
|
|
|
@ -69,7 +69,8 @@ export class CoreDynamicComponent<ComponentClass> implements OnChanges, DoCheck
|
|||
@Input() data?: Record<string | number, unknown>;
|
||||
|
||||
// Get the container where to put the dynamic component.
|
||||
@ViewChild('dynamicComponent', { read: ViewContainerRef }) set dynamicComponent(el: ViewContainerRef) {
|
||||
@ViewChild('dynamicComponent', { read: ViewContainerRef })
|
||||
set dynamicComponent(el: ViewContainerRef) {
|
||||
this.container = el;
|
||||
|
||||
// Use a timeout to avoid ExpressionChangedAfterItHasBeenCheckedError.
|
||||
|
@ -94,7 +95,7 @@ export class CoreDynamicComponent<ComponentClass> implements OnChanges, DoCheck
|
|||
}
|
||||
|
||||
/**
|
||||
* Detect changes on input properties.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
if (changes.component && !this.component) {
|
||||
|
@ -108,7 +109,7 @@ export class CoreDynamicComponent<ComponentClass> implements OnChanges, DoCheck
|
|||
}
|
||||
|
||||
/**
|
||||
* Detect and act upon changes that Angular can’t or won’t detect on its own (objects and arrays).
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngDoCheck(): void {
|
||||
if (this.instance) {
|
||||
|
|
|
@ -56,7 +56,7 @@ import { CoreUtils } from '@services/utils/utils';
|
|||
*/
|
||||
@Component({
|
||||
selector: 'core-compile-html',
|
||||
template: '<core-loading [hideUntil]="loaded"><ng-container #dynamicComponent></ng-container></core-loading>',
|
||||
template: '<core-loading [hideUntil]="loaded"><ng-container #dynamicComponent /></core-loading>',
|
||||
styles: [':host { display: contents; }'],
|
||||
})
|
||||
export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
||||
|
@ -66,16 +66,16 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
|||
@Input() jsData?: Record<string, unknown>; // Data to pass to the fake component.
|
||||
@Input() extraImports: unknown[] = []; // Extra import modules.
|
||||
@Input() extraProviders: Type<unknown>[] = []; // Extra providers.
|
||||
@Input() forceCompile?: boolean; // Set it to true to force compile even if the text/javascript hasn't changed.
|
||||
@Input() forceCompile = false; // Set it to true to force compile even if the text/javascript hasn't changed.
|
||||
@Output() created = new EventEmitter<unknown>(); // Will emit an event when the component is instantiated.
|
||||
@Output() compiling = new EventEmitter<boolean>(); // Event that indicates whether the template is being compiled.
|
||||
|
||||
loaded = false;
|
||||
componentInstance?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
// Get the container where to put the content.
|
||||
@ViewChild('dynamicComponent', { read: ViewContainerRef }) container?: ViewContainerRef;
|
||||
|
||||
loaded?: boolean;
|
||||
componentInstance?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
protected componentRef?: ComponentRef<unknown>;
|
||||
protected element: HTMLElement;
|
||||
protected differ: KeyValueDiffer<unknown, unknown>; // To detect changes in the jsData input.
|
||||
|
@ -114,6 +114,10 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
|||
* @inheritdoc
|
||||
*/
|
||||
async ngOnChanges(changes: Record<string, SimpleChange>): Promise<void> {
|
||||
if (!this.container) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only compile if text/javascript has changed or the forceCompile flag has been set to true.
|
||||
if (this.text !== undefined && (changes.text || changes.javascript ||
|
||||
(changes.forceCompile && CoreUtils.isTrueOrOne(this.forceCompile)))) {
|
||||
|
@ -124,16 +128,18 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
|||
|
||||
try {
|
||||
const componentClass = await this.getComponentClass();
|
||||
const factory = await CoreCompile.createAndCompileComponent(this.text, componentClass, this.extraImports);
|
||||
|
||||
// Destroy previous components.
|
||||
this.componentRef?.destroy();
|
||||
|
||||
if (factory) {
|
||||
// Create the component.
|
||||
this.componentRef = this.container?.createComponent(factory);
|
||||
this.componentRef && this.created.emit(this.componentRef.instance);
|
||||
}
|
||||
// Create the component.
|
||||
this.componentRef = await CoreCompile.createAndCompileComponent(
|
||||
this.text,
|
||||
componentClass,
|
||||
this.container,
|
||||
this.extraImports,
|
||||
);
|
||||
this.componentRef && this.created.emit(this.componentRef.instance);
|
||||
|
||||
this.loaded = true;
|
||||
} catch (error) {
|
||||
|
@ -192,7 +198,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
|||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// If there is some javascript to run, do it now.
|
||||
|
@ -204,7 +210,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
|||
for (const name in compileInstance.pendingCalls) {
|
||||
const pendingCall = compileInstance.pendingCalls[name];
|
||||
|
||||
if (typeof this[name] == 'function') {
|
||||
if (typeof this[name] === 'function') {
|
||||
// Call the function.
|
||||
Promise.resolve(this[name].apply(this, pendingCall.params)).then(pendingCall.defer.resolve)
|
||||
.catch(pendingCall.defer.reject);
|
||||
|
@ -218,21 +224,21 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
|||
}
|
||||
|
||||
/**
|
||||
* Content has been initialized.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngAfterContentInit(): void {
|
||||
this.callLifecycleHookOverride('ngAfterContentInit');
|
||||
}
|
||||
|
||||
/**
|
||||
* View has been initialized.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngAfterViewInit(): void {
|
||||
this.callLifecycleHookOverride('ngAfterViewInit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.callLifecycleHookOverride('ngOnDestroy');
|
||||
|
@ -283,9 +289,9 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
|||
* once the component has been created.
|
||||
* @returns Result of the call. Undefined if no component instance or the function doesn't exist.
|
||||
*/
|
||||
callComponentFunction(name: string, params?: unknown[], callWhenCreated: boolean = true): unknown {
|
||||
callComponentFunction(name: string, params?: unknown[], callWhenCreated = true): unknown {
|
||||
if (this.componentInstance) {
|
||||
if (typeof this.componentInstance[name] == 'function') {
|
||||
if (typeof this.componentInstance[name] === 'function') {
|
||||
return this.componentInstance[name].apply(this.componentInstance, params);
|
||||
}
|
||||
} else if (callWhenCreated) {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
// (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 { Injectable, Pipe } from '@angular/core';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* Copy of translate pipe to use when compiling a dynamic component.
|
||||
* For some reason, when compiling a dynamic component the original translate pipe isn't found so we use this copy instead.
|
||||
*/
|
||||
@Injectable()
|
||||
@Pipe({
|
||||
name: 'translate',
|
||||
pure: false, // required to update the value when the promise is resolved
|
||||
standalone: true,
|
||||
})
|
||||
export class TranslatePipeForCompile extends TranslatePipe {}
|
|
@ -17,14 +17,13 @@ import {
|
|||
Injector,
|
||||
Component,
|
||||
NgModule,
|
||||
Compiler,
|
||||
ComponentFactory,
|
||||
ComponentRef,
|
||||
NgModuleRef,
|
||||
NO_ERRORS_SCHEMA,
|
||||
Type,
|
||||
Provider,
|
||||
createNgModule,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
|
||||
import {
|
||||
ActionSheetController,
|
||||
AlertController,
|
||||
|
@ -34,6 +33,7 @@ import {
|
|||
ToastController,
|
||||
} from '@ionic/angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { TranslatePipeForCompile } from '../pipes/translate';
|
||||
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
|
@ -160,6 +160,8 @@ import { CorePromisedValue } from '@classes/promised-value';
|
|||
import { CorePlatform } from '@services/platform';
|
||||
import { CoreAutoLogoutService } from '@features/autologout/services/autologout';
|
||||
|
||||
import '@angular/compiler';
|
||||
|
||||
/**
|
||||
* Service to provide functionalities regarding compiling dynamic HTML and Javascript.
|
||||
*/
|
||||
|
@ -167,7 +169,6 @@ import { CoreAutoLogoutService } from '@features/autologout/services/autologout'
|
|||
export class CoreCompileProvider {
|
||||
|
||||
protected logger: CoreLogger;
|
||||
protected compiler: Compiler;
|
||||
|
||||
// Other Ionic/Angular providers that don't depend on where they are injected.
|
||||
protected readonly OTHER_SERVICES: unknown[] = [
|
||||
|
@ -186,10 +187,8 @@ export class CoreCompileProvider {
|
|||
getWorkshopComponentModules,
|
||||
];
|
||||
|
||||
constructor(protected injector: Injector, compilerFactory: JitCompilerFactory) {
|
||||
constructor(protected injector: Injector) {
|
||||
this.logger = CoreLogger.getInstance('CoreCompileProvider');
|
||||
|
||||
this.compiler = compilerFactory.createCompiler();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -197,14 +196,17 @@ export class CoreCompileProvider {
|
|||
*
|
||||
* @param template The template of the component.
|
||||
* @param componentClass The JS class of the component.
|
||||
* @param viewContainerRef View container reference to inject the component.
|
||||
* @param extraImports Extra imported modules if needed and not imported by this class.
|
||||
* @returns Promise resolved with the factory to instantiate the component.
|
||||
* @returns Promise resolved with the component reference.
|
||||
*/
|
||||
async createAndCompileComponent<T = unknown>(
|
||||
template: string,
|
||||
componentClass: Type<T>,
|
||||
viewContainerRef: ViewContainerRef,
|
||||
extraImports: any[] = [], // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): Promise<ComponentFactory<T> | undefined> {
|
||||
): Promise<ComponentRef<T> | undefined> {
|
||||
|
||||
// Create the component using the template and the class.
|
||||
const component = Component({ template })(componentClass);
|
||||
|
||||
|
@ -213,17 +215,24 @@ export class CoreCompileProvider {
|
|||
...CoreArray.flatten(lazyImports),
|
||||
...this.IMPORTS,
|
||||
...extraImports,
|
||||
TranslatePipeForCompile,
|
||||
];
|
||||
|
||||
// Now create the module containing the component.
|
||||
const module = NgModule({ imports, declarations: [component], schemas: [NO_ERRORS_SCHEMA] })(class {});
|
||||
|
||||
try {
|
||||
// Compile the module and the component.
|
||||
const factories = await this.compiler.compileModuleAndAllComponentsAsync(module);
|
||||
viewContainerRef.clear();
|
||||
|
||||
// Search and return the factory of the component we just created.
|
||||
return factories.componentFactories.find(factory => factory.componentType == component);
|
||||
// Now create the module containing the component.
|
||||
const ngModuleRef = createNgModule(
|
||||
NgModule({ imports, declarations: [component], schemas: [NO_ERRORS_SCHEMA] })(class {}),
|
||||
this.injector,
|
||||
);
|
||||
|
||||
return viewContainerRef.createComponent(
|
||||
component,
|
||||
{
|
||||
environmentInjector: ngModuleRef,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error('Error compiling template', template);
|
||||
this.logger.error(error);
|
||||
|
@ -331,10 +340,10 @@ export class CoreCompileProvider {
|
|||
// We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance.
|
||||
for (const i in providers) {
|
||||
const providerDef = providers[i];
|
||||
if (typeof providerDef == 'function' && providerDef.name) {
|
||||
if (typeof providerDef === 'function' && providerDef.name) {
|
||||
try {
|
||||
// Inject the provider to the instance. We use the class name as the property name.
|
||||
instance[providerDef.name.replace(/DelegateService$/, 'Delegate')] = this.injector.get(providerDef);
|
||||
instance[providerDef.name.replace(/DelegateService$/, 'Delegate')] = this.injector.get<Provider>(providerDef);
|
||||
} catch (ex) {
|
||||
this.logger.error('Error injecting provider', providerDef.name, ex);
|
||||
}
|
||||
|
@ -407,29 +416,6 @@ export class CoreCompileProvider {
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a dynamic component.
|
||||
*
|
||||
* @param template The template of the component.
|
||||
* @param componentClass The JS class of the component.
|
||||
* @param injector The injector to use. It's recommended to pass it so NavController and similar can be injected.
|
||||
* @returns Promise resolved with the component instance.
|
||||
*/
|
||||
async instantiateDynamicComponent<T = unknown>(
|
||||
template: string,
|
||||
componentClass: Type<T>,
|
||||
injector?: Injector,
|
||||
): Promise<ComponentRef<T> | undefined> {
|
||||
injector = injector || this.injector;
|
||||
|
||||
const factory = await this.createAndCompileComponent(template, componentClass);
|
||||
|
||||
if (factory) {
|
||||
// Create and return the component.
|
||||
return factory.create(injector, undefined, undefined, injector.get(NgModuleRef));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const CoreCompile = makeSingleton(CoreCompileProvider);
|
||||
|
|
|
@ -48,18 +48,18 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck {
|
|||
// Get the compile element. Don't set the right type to prevent circular dependencies.
|
||||
@ViewChild('compile') compileComponent?: CoreCompileHtmlComponent;
|
||||
|
||||
@HostBinding('class') @Input() component!: string;
|
||||
@HostBinding('class') @Input() component = '';
|
||||
@Input() method!: string;
|
||||
@Input() args?: Record<string, unknown>;
|
||||
@Input() initResult?: CoreSitePluginsContent | null; // Result of the init WS call of the handler.
|
||||
@Input() data?: Record<string, unknown>; // Data to pass to the component.
|
||||
@Input() data: Record<string, unknown> = {}; // Data to pass to the component.
|
||||
@Input() preSets?: CoreSiteWSPreSets; // The preSets for the WS call.
|
||||
@Input() pageTitle?: string; // Current page title. It can be used by the "new-content" directives.
|
||||
@Output() onContentLoaded = new EventEmitter<CoreSitePluginsPluginContentLoadedData>(); // Emits event when content is loaded.
|
||||
@Output() onLoadingContent = new EventEmitter<boolean>(); // Emits an event when starts to load the content.
|
||||
|
||||
content?: string; // Content.
|
||||
javascript?: string; // Javascript to execute.
|
||||
content = ''; // Content.
|
||||
javascript = ''; // Javascript to execute.
|
||||
otherData?: Record<string, unknown>; // Other data of the content.
|
||||
dataLoaded = false;
|
||||
invalidateObservable = new Subject<void>(); // An observable to notify observers when to invalidate data.
|
||||
|
@ -120,13 +120,27 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck {
|
|||
this.jsData = Object.assign(this.data, CoreSitePlugins.createDataForJS(this.initResult, result));
|
||||
|
||||
// Pass some methods as jsData so they can be called from the template too.
|
||||
this.jsData.fetchContent = refresh => this.fetchContent(refresh);
|
||||
this.jsData.openContent = (title, args, component, method, jsData, preSets, ptrEnabled) =>
|
||||
this.openContent(title, args, component, method, jsData, preSets, ptrEnabled);
|
||||
this.jsData.refreshContent = showSpinner => this.refreshContent(showSpinner);
|
||||
this.jsData.updateContent = (args, component, method, jsData, preSets) =>
|
||||
this.updateContent(args, component, method, jsData, preSets);
|
||||
this.jsData.updateModuleCourseContent = (cmId, alreadyFetched) => this.updateModuleCourseContent(cmId, alreadyFetched);
|
||||
this.jsData.fetchContent = (refresh?: boolean) => this.fetchContent(refresh);
|
||||
this.jsData.openContent = (
|
||||
title: string,
|
||||
args?: Record<string, unknown>,
|
||||
component?: string,
|
||||
method?: string,
|
||||
jsData?: Record<string, unknown> | boolean,
|
||||
preSets?: CoreSiteWSPreSets,
|
||||
ptrEnabled?: boolean,
|
||||
) => this.openContent(title, args, component, method, jsData, preSets, ptrEnabled);
|
||||
this.jsData.refreshContent = (showSpinner?: boolean) => this.refreshContent(showSpinner);
|
||||
this.jsData.updateContent = (
|
||||
args?: Record<string, unknown>,
|
||||
component?: string,
|
||||
method?: string,
|
||||
jsData?: Record<string, unknown>,
|
||||
preSets?: CoreSiteWSPreSets,
|
||||
) => this.updateContent(args, component, method, jsData, preSets);
|
||||
this.jsData.updateModuleCourseContent = (cmId: number, alreadyFetched?: boolean) =>
|
||||
this.updateModuleCourseContent(cmId, alreadyFetched);
|
||||
this.jsData.updateCachedContent = () => this.updateCachedContent();
|
||||
|
||||
this.onContentLoaded.emit({ refresh: !!refresh, success: true });
|
||||
} catch (error) {
|
||||
|
@ -154,7 +168,7 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck {
|
|||
*/
|
||||
openContent(
|
||||
title: string,
|
||||
args?: Record<string, unknown>,
|
||||
args: Record<string, unknown> = {},
|
||||
component?: string,
|
||||
method?: string,
|
||||
jsData?: Record<string, unknown> | boolean,
|
||||
|
@ -167,7 +181,6 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck {
|
|||
|
||||
component = component || this.component;
|
||||
method = method || this.method;
|
||||
args = args || {};
|
||||
const hash = <string> Md5.hashAsciiStr(JSON.stringify(args));
|
||||
|
||||
CoreNavigator.navigateToSitePath(`siteplugins/content/${component}/${method}/${hash}`, {
|
||||
|
@ -187,7 +200,7 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck {
|
|||
*
|
||||
* @param showSpinner Whether to show spinner while refreshing.
|
||||
*/
|
||||
async refreshContent(showSpinner: boolean = true): Promise<void> {
|
||||
async refreshContent(showSpinner = true): Promise<void> {
|
||||
if (showSpinner) {
|
||||
this.dataLoaded = false;
|
||||
}
|
||||
|
|
|
@ -226,7 +226,6 @@ export const Translate: Omit<CoreSingletonProxy<TranslateService>, 'instant'> &
|
|||
// Async singletons.
|
||||
export const AngularFrameworkDelegate = asyncInstance(async () => {
|
||||
const injector = await singletonsInjector;
|
||||
const environmentInjector = await injector.get(EnvironmentInjector);
|
||||
|
||||
return AngularDelegate.create(environmentInjector, injector);
|
||||
return AngularDelegate.create(injector.get(EnvironmentInjector), injector);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue