Skip to content

Commit

Permalink
feat: support lazy loading
Browse files Browse the repository at this point in the history
* feat: 🎸 LazyMapTo support

Co-authored-by: Niek Raaijmakers <niek@adobe.com>
Co-authored-by: Cezary Czernecki <czarek.czernecki@gmail.com>
  • Loading branch information
3 people authored Jan 11, 2021
1 parent 578335f commit 55452ff
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 31 deletions.
12 changes: 9 additions & 3 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@
"options": {
"main": "src/test.ts",
"tsConfig": "tsconfig.spec.json",
"codeCoverageExclude": ["src/test.ts", "src/lib/test/**/*"],
"codeCoverageExclude": [
"src/test.ts",
"src/lib/test/**/*"
],
"karmaConfig": "karma.conf.js"
}
},
Expand All @@ -67,5 +70,8 @@
}
}
},
"defaultProject": "spa-angular-editable-components"
}
"defaultProject": "spa-angular-editable-components",
"cli": {
"analytics": false
}
}
17 changes: 16 additions & 1 deletion src/lib/layout/aem-component.directive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { AEMComponentDirective } from './aem-component.directive';
import { Component, Input } from '@angular/core';
import { ComponentMapping, MapTo } from './component-mapping';
import { ComponentMapping, MapTo, LazyMapTo} from './component-mapping';
import { Utils } from './utils';

@Component({
Expand Down Expand Up @@ -43,6 +43,7 @@ class DirectiveComponent {
}
}
MapTo('directive/comp')(DirectiveComponent);
LazyMapTo('some/lazy/comp')(() => import('../test/lazy-component-wrapper/lazy.component').then((m) => m.LazyComponent));

describe('AEMComponentDirective', () => {

Expand Down Expand Up @@ -103,6 +104,20 @@ describe('AEMComponentDirective', () => {
expect(dynamicElement.getAttribute('attr1')).toEqual(componentData['attr1']);
expect(dynamicElement.getAttribute('attr2')).toEqual(componentData['attr2']);
});
it('should correctly pass the inputs for lazy component', async() => {
const componentData = {
some: 'Some value',
':type': 'some/lazy/comp'
};

component.data = componentData;

await import('../test/lazy-component-wrapper/lazy.component');
fixture.detectChanges();

});



it('should setup the placeholder', () => {
isInEditorSpy.and.returnValue(true);
Expand Down
64 changes: 52 additions & 12 deletions src/lib/layout/aem-component.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@
*/

import {
Directive,
Input,
Renderer2,
ViewContainerRef,
AfterViewInit,
ChangeDetectorRef,
Compiler,
ComponentFactory,
ComponentFactoryResolver,
ComponentRef,
AfterViewInit,
Directive,
Injector,
Input,
OnChanges,
OnDestroy,
OnInit,
OnDestroy, ChangeDetectorRef, OnChanges
Renderer2,
ViewContainerRef
} from '@angular/core';

import { ComponentMapping } from './component-mapping';
Expand All @@ -34,7 +39,6 @@ const PLACEHOLDER_CLASS_NAME = 'cq-placeholder';
@Directive({
selector: '[aemComponent]'
})

/**
* The current directive provides advanced capabilities among which are
*
Expand Down Expand Up @@ -82,17 +86,47 @@ export class AEMComponentDirective implements AfterViewInit, OnInit, OnDestroy,
*/
@Input() itemAttrs: any;

@Input() loaded: boolean;

@Input() aemComponent;


constructor(
private renderer: Renderer2,
private viewContainer: ViewContainerRef,
private compiler: Compiler,
private injector: Injector,
private factoryResolver: ComponentFactoryResolver,
private _changeDetectorRef: ChangeDetectorRef) {
}

ngOnInit(): void {
this.renderComponent(ComponentMapping.get(this.type));
async ngOnInit() {

if (this.type) {
const mappedFn = ComponentMapping.get(this.type);

if (mappedFn) {
this.renderComponent(mappedFn);
} else {
await this.initializeAsync();
}
} else {
console.warn('no type on ' + this.cqPath);
}

}

async initializeAsync() {
const lazyMappedPromise: Promise<unknown> = ComponentMapping.lazyGet(this.type);

try {
const LazyResolvedComponent = await lazyMappedPromise;
this.renderComponent(LazyResolvedComponent);
this.loaded = true;
this._changeDetectorRef.detectChanges();
} catch (err) {
console.warn(err);
}
}

ngOnChanges(): void {
Expand All @@ -115,12 +149,18 @@ export class AEMComponentDirective implements AfterViewInit, OnInit, OnDestroy,
if (componentDefinition) {
const factory = this.factoryResolver.resolveComponentFactory(componentDefinition);

this.viewContainer.clear();
this._component = this.viewContainer.createComponent(factory);
this.updateComponentData();
this.renderWithFactory(factory);
} else {
throw new Error('No component definition!!');
}
}

private renderWithFactory(factory: ComponentFactory<any>) {
this.viewContainer.clear();
this._component = this.viewContainer.createComponent(factory);
this.updateComponentData();
}

/**
* Updates the data of the component based the data of the directive
*/
Expand Down
20 changes: 18 additions & 2 deletions src/lib/layout/component-mapping.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,36 @@
* governing permissions and limitations under the License.
*/

import { ComponentMapping, MapTo } from './component-mapping';
import { ComponentMapping, LazyMapTo, MapTo } from './component-mapping';
import LazyComponent from '../test/lazy-component-wrapper/lazy.component';

describe('Component Mapping', () => {
it('stores configuration', () => {
it('stores configuration', async () => {
const Component1 = function() { /* void */ };
const Component2 = function() { /* void */ };
const editConfig1 = { some: 1 };
const editConfig2 = { some: 2 };

MapTo('component1')(Component1, editConfig1);
MapTo('component2')(Component2, editConfig2);
LazyMapTo('lazycomponent3')(() => new Promise<unknown>((resolve, reject) => {
import('../test/lazy-component-wrapper/lazy.component')
.then((Module) => { resolve(Module.LazyComponent); })
.catch(reject);
}));
LazyMapTo('lazycomponent4')(() => import('../test/lazy-component-wrapper/lazy.component').then((m) => m.LazyComponent));

expect(ComponentMapping.get('component1')).toBe(Component1);
expect(ComponentMapping.get('component2')).toBe(Component2);

const LoadedComp = await ComponentMapping.lazyGet('lazycomponent3');

expect(LoadedComp).toBe(LazyComponent);

const DefaultComp = await ComponentMapping.lazyGet('lazycomponent4');

expect(DefaultComp).toBe(LazyComponent);

expect(ComponentMapping.getEditConfig('component1')).toBe(editConfig1);
expect(ComponentMapping.getEditConfig('component2')).toBe(editConfig2);
});
Expand Down
64 changes: 54 additions & 10 deletions src/lib/layout/component-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class ComponentMappingWithConfig {
*/
private editConfigMap = {};

constructor(private spaMapping:SPAComponentMapping) {}
constructor(private spaMapping: SPAComponentMapping) {}

/**
* Stores a component class for the given resource types and also allows to provide an EditConfig object
Expand All @@ -34,36 +34,80 @@ export class ComponentMappingWithConfig {
map(resourceTypes, clazz, editConfig = null) {
const innerClass = clazz;

if (editConfig) {
this.editConfigMap[resourceTypes] = editConfig;
}
if (editConfig) {
this.editConfigMap[resourceTypes] = editConfig;
}
this.spaMapping.map(resourceTypes, innerClass);
}

this.spaMapping.map(resourceTypes, innerClass);
}
/**
* Stores a clazz the lazy way for dynamic imports / code splitting.function that returns a promise
* @param resourceTypes - List of resource types
* @param lazyClassFunction - A function that returns a promise that resolves a Component class
* @param [editConfig] - Edit configuration to be stored for the given resource types
*/
lazyMap(resourceTypes, lazyClassFunction: () => Promise<unknown>, editConfig = null) {
const innerFunction = lazyClassFunction;

if (editConfig) {
this.editConfigMap[resourceTypes] = editConfig;
}
this.spaMapping.lazyMap(resourceTypes, innerFunction);
}

/**
* Returns the component class for the given resourceType
* @param resourceType - Resource type for which the component class has been stored
*/
get(resourceType: string): any {
get(resourceType: string): unknown {
return this.spaMapping.get(resourceType);
}

/**
* Returns the component class for the given resourceType
* @param resourceType - Resource type for which the component class has been stored
*/
lazyGet(resourceType: string): Promise<unknown> {
return this.spaMapping.getLazy(resourceType);
}

/**
* Returns the EditConfig structure for the given type
* @param resourceType - Resource type for which the configuration has been stored
*/
getEditConfig(resourceType) {
return this.editConfigMap[resourceType];
getEditConfig(resourceType: string) {
return this.editConfigMap[resourceType];
}
}

const componentMapping = new ComponentMappingWithConfig(SPAComponentMapping);

/**
* Stores a component class for the given resource types and also allows to provide an EditConfig object
* @param resourceTypes - List of resource types
*/
function MapTo(resourceTypes) {
/**
* @param clazz - Component class to be stored
* @param [editConfig] - Edit configuration to be stored for the given resource types
*/
return (clazz, editConfig = null): any => {
return componentMapping.map(resourceTypes, clazz, editConfig);
};
}

export { componentMapping as ComponentMapping, MapTo };
/**
* Stores a clazz the lazy way for dynamic imports / code splitting.function that returns a promise
* @param resourceTypes - List of resource types
*/
function LazyMapTo(resourceTypes) {
/**
* @param lazyClassPromise - Function that returns a promise resolving a class
* @param [editConfig] - Edit configuration to be stored for the given resource types
*/
return (lazyClassFunction: () => Promise<unknown>, editConfig = null) => {
return componentMapping.lazyMap(resourceTypes, lazyClassFunction, editConfig);
};
}

export { componentMapping as ComponentMapping, MapTo, LazyMapTo };
29 changes: 29 additions & 0 deletions src/lib/test/lazy-component-wrapper/lazy.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you 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 REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import { Component, Input } from '@angular/core';

@Component({
selector: 'lazy-comp',
template: `<div>{{ some }}</div>`
})
/**
* The current class carries the base presentational logic of the AEM Layout Container (aka. Responsive grid)
*/
export class LazyComponent {
@Input() cqPath: string;
@Input() isInEditor: boolean;
@Input() some: string;

}

export default LazyComponent;
13 changes: 13 additions & 0 deletions src/lib/test/lazy-component-wrapper/lazy.entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you 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 REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
export * from './lazy.module';
export * from './lazy.component';
25 changes: 25 additions & 0 deletions src/lib/test/lazy-component-wrapper/lazy.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you 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 REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { LazyComponent } from './lazy.component';

@NgModule({
imports: [ CommonModule, BrowserModule, RouterModule ],
declarations: [
LazyComponent
]
})
export class LazyModule {
}
2 changes: 1 addition & 1 deletion src/lib/test/test-comp1.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Component, Input } from '@angular/core';
})

export class Test1Component {
@Input() title: any;
@Input() title: string;

constructor() {
// empty
Expand Down
2 changes: 1 addition & 1 deletion src/lib/test/test-comp2.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Component, Input } from '@angular/core';
})

export class Test2Component {
@Input() title:any;
@Input() title: string;

constructor() {
// empty
Expand Down
Loading

0 comments on commit 55452ff

Please sign in to comment.