diff --git a/.gitignore b/.gitignore
index 0ea9de3..6bdd727 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,5 @@ dist/
.scannerwork/
**/*.log
*.tgz
+coverage
+.idea
diff --git a/karma.conf.js b/karma.conf.js
index 0153bda..942d55c 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -29,6 +29,7 @@ const customLaunchers = {
}
};
+
module.exports = function(config) {
config.set({
basePath: '',
diff --git a/src/lib/layout/aem-component.directive.custom.spec.ts b/src/lib/layout/aem-component.directive.custom.spec.ts
new file mode 100644
index 0000000..39edc11
--- /dev/null
+++ b/src/lib/layout/aem-component.directive.custom.spec.ts
@@ -0,0 +1,120 @@
+/*
+ * 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 { 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, LazyMapTo, AbstractMappedComponent } from './component-mapping';
+import { Utils } from './utils';
+import { LazyComponentType } from "../test/lazy-component-wrapper/lazy.component";
+import {CustomDefaultRenderer} from "../test/aem-component-directive/custom-default-renderer";
+import {CustomDirective} from "../test/aem-component-directive/customdirective";
+
+
+
+
+
+@Component({
+ selector: 'test-component',
+ template: ``
+})
+class AEMDirectiveTestComponent {
+ @Input() data;
+}
+
+
+
+@Component({
+ selector: 'directive-component',
+ host: {
+ '[attr.attr1]': 'attr1',
+ '[attr.attr2]': 'attr2',
+ '[class]': 'hostClasses'
+ },
+ template: `
`
+})
+class DirectiveComponent extends AbstractMappedComponent {
+ @Input() attr1;
+ @Input() attr2;
+
+ get hostClasses() {
+ return 'component-class';
+ }
+}
+MapTo('directive/comp')(DirectiveComponent);
+LazyMapTo('some/lazy/comp')(() => import('../test/lazy-component-wrapper/lazy.component').then((m) => m.LazyComponent));
+
+describe('AEMComponentDirective - Custom', () => {
+
+ const EDIT_CONFIG_EMPTY_LABEL = 'Edit config empty label';
+
+ const TEST_EDIT_CONFIG_EMPTY = {
+ emptyLabel: EDIT_CONFIG_EMPTY_LABEL,
+ isEmpty: () => {
+ return true;
+ }
+ };
+
+ const TEST_EDIT_CONFIG_NOT_EMPTY = {
+ emptyLabel: EDIT_CONFIG_EMPTY_LABEL,
+ isEmpty: function() {
+ return false;
+ }
+ };
+
+ let component: AEMDirectiveTestComponent;
+ let fixture: ComponentFixture;
+
+ let isInEditorSpy;
+ let getEditConfigSpy;
+
+ beforeEach(async(() => {
+ isInEditorSpy = spyOn(Utils, 'isInEditor').and.returnValue(false);
+ getEditConfigSpy = spyOn(ComponentMapping, 'getEditConfig').and.returnValue(undefined);
+
+ TestBed.configureTestingModule({
+ declarations: [ AEMDirectiveTestComponent, DirectiveComponent,CustomDefaultRenderer,CustomDirective, AEMComponentDirective ]
+ }).overrideModule(BrowserDynamicTestingModule, {
+ set: {
+ entryComponents: [ DirectiveComponent, CustomDefaultRenderer,CustomDirective ]
+ }
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AEMDirectiveTestComponent);
+ component = fixture.componentInstance;
+ });
+
+ it('should render the fallback component', async () => {
+ const componentData = {
+ attr1: 'Some value',
+ attr2: 'Another value',
+ ':customType': 'missing/directive/comp'
+ };
+
+ component.data = componentData;
+ fixture.detectChanges();
+
+ await fixture.whenStable();
+
+ const element = fixture.nativeElement;
+ const dynamicElement = element.firstElementChild;
+
+ expect(dynamicElement.innerHTML).toEqual("test
")
+ });
+
+
+
+});
diff --git a/src/lib/layout/aem-component.directive.spec.ts b/src/lib/layout/aem-component.directive.spec.ts
index 44bfd8d..953da26 100644
--- a/src/lib/layout/aem-component.directive.spec.ts
+++ b/src/lib/layout/aem-component.directive.spec.ts
@@ -106,6 +106,36 @@ describe('AEMComponentDirective', () => {
expect(dynamicElement.getAttribute('attr1')).toEqual(componentData['attr1']);
expect(dynamicElement.getAttribute('attr2')).toEqual(componentData['attr2']);
});
+ it('should not render anything', () => {
+ const componentData = {
+ attr1: 'Some value',
+ attr2: 'Another value',
+ ':type': 'missing/directive/comp'
+ };
+
+ component.data = componentData;
+ fixture.detectChanges();
+
+ const element = fixture.nativeElement;
+ const dynamicElement = element.firstElementChild;
+
+ expect(dynamicElement).toBeNull();
+ });
+ it('should render the fallback component', () => {
+ const componentData = {
+ attr1: 'Some value',
+ attr2: 'Another value',
+ ':type': 'missing/directive/comp'
+ };
+
+ component.data = componentData;
+ fixture.detectChanges();
+
+ const element = fixture.nativeElement;
+ const dynamicElement = element.firstElementChild;
+
+ expect(dynamicElement).toBeNull();
+ });
it('should correctly pass the inputs for lazy component', async() => {
const componentData = {
some: 'Some value',
diff --git a/src/lib/layout/aem-component.directive.ts b/src/lib/layout/aem-component.directive.ts
index e1f81db..fbbbc79 100644
--- a/src/lib/layout/aem-component.directive.ts
+++ b/src/lib/layout/aem-component.directive.ts
@@ -28,7 +28,12 @@ import {
ViewContainerRef
} from '@angular/core';
-import { ComponentMapping, MappedComponentProperties } from './component-mapping';
+import {
+ ComponentMapping,
+ ComponentMappingProvider,
+ ComponentMappingWithConfig,
+ MappedComponentProperties
+} from './component-mapping';
import { Constants } from './constants';
import { Utils } from './utils';
@@ -93,18 +98,18 @@ export class AEMComponentDirective implements AfterViewInit, OnInit, OnDestroy,
constructor(
- private renderer: Renderer2,
- private viewContainer: ViewContainerRef,
- private compiler: Compiler,
- private injector: Injector,
- private factoryResolver: ComponentFactoryResolver,
- private _changeDetectorRef: ChangeDetectorRef) {
+ protected renderer: Renderer2,
+ protected viewContainer: ViewContainerRef,
+ protected compiler: Compiler,
+ protected injector: Injector,
+ protected factoryResolver: ComponentFactoryResolver,
+ protected _changeDetectorRef: ChangeDetectorRef) {
}
async ngOnInit() {
- if (this.type) {
- const mappedFn:Type = ComponentMapping.get(this.type);
+ if (this.getType()) {
+ const mappedFn:Type = this.getComponentMappingProvider().get(this.getType());
if (mappedFn) {
this.renderComponent(mappedFn);
@@ -118,15 +123,23 @@ export class AEMComponentDirective implements AfterViewInit, OnInit, OnDestroy,
}
async initializeAsync() {
- const lazyMappedPromise: Promise> = ComponentMapping.lazyGet(this.type);
-
try {
+ const lazyMappedPromise: Promise> = this.getComponentMappingProvider().lazyGet(this.getType());
const LazyResolvedComponent = await lazyMappedPromise;
this.renderComponent(LazyResolvedComponent);
this.loaded = true;
this._changeDetectorRef.detectChanges();
} catch (err) {
- console.warn(err);
+
+ if(!!this.getFallbackComponent()){
+ this.renderComponent(this.getFallbackComponent());
+ this.loaded = true;
+ this._changeDetectorRef.detectChanges();
+ console.info("loaded fallback component. cause:", err);
+ }else{
+ console.warn(err);
+ }
+
}
}
@@ -134,10 +147,17 @@ export class AEMComponentDirective implements AfterViewInit, OnInit, OnDestroy,
this.updateComponentData();
}
+ protected getFallbackComponent():Type | null{
+ return null;
+ }
+
+ protected getComponentMappingProvider():ComponentMappingProvider {
+ return ComponentMapping;
+ }
/**
* Returns the type of the cqItem if exists.
*/
- get type(): string | undefined {
+ protected getType(): string | undefined {
return this.cqItem && this.cqItem[Constants.TYPE_PROP];
}
@@ -146,7 +166,7 @@ export class AEMComponentDirective implements AfterViewInit, OnInit, OnDestroy,
*
* @param componentDefinition The component definition to render
*/
- private renderComponent(componentDefinition: Type) {
+ private renderComponent(componentDefinition: Type) {
if (componentDefinition) {
const factory = this.factoryResolver.resolveComponentFactory(componentDefinition);
@@ -189,7 +209,7 @@ export class AEMComponentDirective implements AfterViewInit, OnInit, OnDestroy,
this._component.instance.cqPath = this.cqPath;
this._component.instance.itemName = this.itemName;
- const editConfig = ComponentMapping.getEditConfig(this.type);
+ const editConfig = ComponentMapping.getEditConfig(this.getType());
if (editConfig && Utils.isInEditor) {
this.setupPlaceholder(editConfig);
diff --git a/src/lib/layout/component-mapping.ts b/src/lib/layout/component-mapping.ts
index ceae114..58cc20a 100644
--- a/src/lib/layout/component-mapping.ts
+++ b/src/lib/layout/component-mapping.ts
@@ -64,12 +64,16 @@ export abstract class AbstractMappedComponent implements MappedComponentProperti
@Input() itemName = '';
}
+export interface ComponentMappingProvider {
+ get(resourceType:string):Type
+ lazyGet(resourceType: string): Promise>
+}
/**
* The current class extends the @adobe/cq-spa-component-mapping#Mapto library and features with Angular specifics such as
*
* - Storing the editing configurations for each resource type
*/
-export class ComponentMappingWithConfig {
+export class ComponentMappingWithConfig implements ComponentMappingProvider{
/**
* Store of EditConfig structures
*/
@@ -172,4 +176,4 @@ function LazyMapTo(resourceTypes:
componentMapping.lazyMap(resourceTypes, lazyClassFunction, editConfig);
}
-export { componentMapping as ComponentMapping, MapTo, LazyMapTo };
\ No newline at end of file
+export { componentMapping as ComponentMapping, MapTo, LazyMapTo };
diff --git a/src/lib/test/aem-component-directive/custom-default-renderer.ts b/src/lib/test/aem-component-directive/custom-default-renderer.ts
new file mode 100644
index 0000000..8774e4e
--- /dev/null
+++ b/src/lib/test/aem-component-directive/custom-default-renderer.ts
@@ -0,0 +1,14 @@
+import {Component, OnInit} from "@angular/core";
+
+@Component({
+ selector: 'core-contentfragment-default-renderer-v1',
+ template: 'test
'
+})
+export class CustomDefaultRenderer implements OnInit{
+ ngOnInit(): void {
+ console.log('init?')
+ }
+
+
+
+}
diff --git a/src/lib/test/aem-component-directive/customdirective.ts b/src/lib/test/aem-component-directive/customdirective.ts
new file mode 100644
index 0000000..8018c3c
--- /dev/null
+++ b/src/lib/test/aem-component-directive/customdirective.ts
@@ -0,0 +1,20 @@
+import {AEMComponentDirective} from "../../layout/aem-component.directive";
+import {Directive, Type} from "@angular/core";
+import {ComponentMappingProvider} from "../../layout/component-mapping";
+import {CustomDefaultRenderer} from "./custom-default-renderer";
+import {ComponentMapping} from "@adobe/aem-spa-component-mapping";
+
+@Directive({
+ selector: '[CustomDirective]'
+})
+export class CustomDirective extends AEMComponentDirective {
+
+ getFallbackComponent():Type | null{
+ return CustomDefaultRenderer;
+ }
+
+ getType(): string | undefined {
+ return this.cqItem && this.cqItem[":customType"];
+ }
+
+}