diff --git a/lib/extensions/src/lib/components/dynamic-component/dynamic.component.spec.ts b/lib/extensions/src/lib/components/dynamic-component/dynamic.component.spec.ts
new file mode 100644
index 0000000000..5636f095b6
--- /dev/null
+++ b/lib/extensions/src/lib/components/dynamic-component/dynamic.component.spec.ts
@@ -0,0 +1,155 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, 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.
+ */
+
+/* tslint:disable:component-selector */
+
+import {
+ Component,
+ Input,
+ SimpleChanges,
+ OnChanges,
+ SimpleChange,
+ ComponentFactoryResolver
+} from '@angular/core';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
+import { DynamicExtensionComponent } from './dynamic.component';
+import { ComponentRegisterService } from '../../services/component-register.service';
+import { HttpClientModule } from '@angular/common/http';
+
+@Component({
+ selector: 'test-component',
+ template: '
Hey I am the mighty test component!
'
+})
+export class TestComponent implements OnChanges {
+ @Input() data: any;
+ public onChangesCalled = 0;
+ ngOnChanges(changes: SimpleChanges) { this.onChangesCalled ++; }
+}
+
+describe('DynamicExtensionComponent', () => {
+
+ let fixture: ComponentFixture;
+ let componentRegister: ComponentRegisterService;
+ let component: DynamicExtensionComponent;
+ let componentFactoryResolver: ComponentFactoryResolver;
+
+ beforeEach(async(() => {
+ componentRegister = new ComponentRegisterService();
+ componentRegister.setComponents({'test-component': TestComponent});
+
+ TestBed.configureTestingModule({
+ imports: [ HttpClientModule ],
+ declarations: [ DynamicExtensionComponent, TestComponent ],
+ providers: [ { provide: ComponentRegisterService, useValue: componentRegister } ]
+ });
+
+ TestBed.overrideModule(BrowserDynamicTestingModule, {
+ set: { entryComponents: [ TestComponent ] }
+ });
+
+ TestBed.compileComponents();
+ }));
+
+ describe('Sub-component creation', () => {
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DynamicExtensionComponent);
+ componentFactoryResolver = TestBed.get(ComponentFactoryResolver);
+ spyOn(componentFactoryResolver, 'resolveComponentFactory').and.callThrough();
+ component = fixture.componentInstance;
+ component.id = 'test-component';
+ component.data = { foo: 'bar' };
+
+ fixture.detectChanges();
+ component.ngOnChanges({});
+ });
+
+ afterEach(() => {
+ fixture.destroy();
+ TestBed.resetTestingModule();
+ });
+
+ it('should load the TestComponent', () => {
+ const innerElement = fixture.debugElement.query(By.css('[data-automation-id="found-me"]'));
+ expect(innerElement).not.toBeNull();
+ });
+
+ it('should load the TestComponent only ONCE', () => {
+ component.ngOnChanges({});
+ fixture.detectChanges();
+ component.ngOnChanges({});
+ fixture.detectChanges();
+
+ expect(( componentFactoryResolver.resolveComponentFactory).calls.count()).toBe(1);
+ });
+
+ it('should pass through the data', () => {
+ const testComponent = fixture.debugElement.query(By.css('test-component')).componentInstance;
+
+ expect(testComponent.data).toBe(component.data);
+ });
+
+ it('should update the subcomponent\'s input parameters', () => {
+ const data = { foo: 'baz' };
+
+ component.ngOnChanges({ data: new SimpleChange(component.data, data, false) });
+
+ const testComponent = fixture.debugElement.query(By.css('test-component')).componentInstance;
+ expect(testComponent.data).toBe(data);
+ });
+ });
+
+ describe('Angular life-cycle methods in sub-component', () => {
+
+ let testComponent;
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DynamicExtensionComponent);
+ component = fixture.componentInstance;
+ component.id = 'test-component';
+
+ fixture.detectChanges();
+ component.ngOnChanges({});
+ testComponent = fixture.debugElement.query(By.css('test-component')).componentInstance;
+ });
+
+ afterEach(() => {
+ fixture.destroy();
+ TestBed.resetTestingModule();
+ });
+
+ it('should call through the ngOnChanges', () => {
+ const params = {};
+
+ component.ngOnChanges(params);
+
+ expect(testComponent.onChangesCalled).toBe(2);
+ });
+
+ it('should NOT call through the ngOnChanges if the method does not exist (no error should be thrown)', () => {
+ testComponent.ngOnChanges = undefined;
+ const params = {};
+ const execution = () => {
+ component.ngOnChanges(params);
+ };
+
+ expect(execution).not.toThrowError();
+ });
+ });
+});
diff --git a/lib/extensions/src/lib/components/dynamic-component/dynamic.component.ts b/lib/extensions/src/lib/components/dynamic-component/dynamic.component.ts
index 4dd00a488b..ef7afd3c84 100644
--- a/lib/extensions/src/lib/components/dynamic-component/dynamic.component.ts
+++ b/lib/extensions/src/lib/components/dynamic-component/dynamic.component.ts
@@ -19,33 +19,49 @@ import {
Component,
Input,
ComponentRef,
- OnInit,
ComponentFactoryResolver,
ViewChild,
ViewContainerRef,
- OnDestroy
+ OnDestroy,
+ OnChanges,
+ SimpleChanges
} from '@angular/core';
import { ExtensionService } from '../../services/extension.service';
+import { ExtensionComponent } from '../../services/component-register.service';
+// cSpell:words lifecycle
@Component({
selector: 'adf-dynamic-component',
template: ``
})
-export class DynamicExtensionComponent implements OnInit, OnDestroy {
+export class DynamicExtensionComponent implements OnChanges, OnDestroy {
@ViewChild('content', { read: ViewContainerRef })
content: ViewContainerRef;
@Input() id: string;
+ @Input() data: any;
- private componentRef: ComponentRef;
+ private componentRef: ComponentRef;
+ private loaded: boolean = false;
- constructor(
- private extensions: ExtensionService,
- private componentFactoryResolver: ComponentFactoryResolver
- ) {}
+ constructor(private extensions: ExtensionService, private componentFactoryResolver: ComponentFactoryResolver) {}
- ngOnInit() {
- const componentType = this.extensions.getComponentById(this.id);
+ ngOnChanges(changes: SimpleChanges) {
+ if (!this.loaded) {
+ this.loadComponent();
+ this.loaded = true;
+ }
+
+ if (changes.data) {
+ this.data = changes.data.currentValue;
+ }
+
+ this.updateInstance();
+ this.proxy('ngOnChanges', changes);
+ }
+
+ private loadComponent() {
+ const componentType = this.extensions.getComponentById(this.id);
if (componentType) {
const factory = this.componentFactoryResolver.resolveComponentFactory(
componentType
@@ -53,15 +69,34 @@ export class DynamicExtensionComponent implements OnInit, OnDestroy {
if (factory) {
this.content.clear();
this.componentRef = this.content.createComponent(factory, 0);
- // this.setupWidget(this.componentRef);
}
}
}
ngOnDestroy() {
- if (this.componentRef) {
+ if (this.componentCreated()) {
this.componentRef.destroy();
this.componentRef = null;
}
}
+
+ private updateInstance() {
+ if (this.componentCreated()) {
+ this.componentRef.instance.data = this.data;
+ }
+ }
+
+ private proxy(lifecycleMethod, ...args) {
+ if (this.componentCreated() && this.lifecycleHookIsImplemented(lifecycleMethod)) {
+ this.componentRef.instance[lifecycleMethod].apply(this.componentRef.instance, args);
+ }
+ }
+
+ private componentCreated(): boolean {
+ return !!this.componentRef && !!this.componentRef.instance;
+ }
+
+ private lifecycleHookIsImplemented(lifecycleMethod: string): boolean {
+ return !!this.componentRef.instance[lifecycleMethod];
+ }
}
diff --git a/lib/extensions/src/lib/services/component-register.service.ts b/lib/extensions/src/lib/services/component-register.service.ts
new file mode 100644
index 0000000000..54a5079b34
--- /dev/null
+++ b/lib/extensions/src/lib/services/component-register.service.ts
@@ -0,0 +1,41 @@
+/*!
+ * @license
+ * Copyright 2016 Alfresco Software, 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, Type } from '@angular/core';
+
+export interface ExtensionComponent {
+ data: any;
+}
+
+@Injectable({ providedIn: 'root' })
+export class ComponentRegisterService {
+ components: { [key: string]: Type<{}> } = {};
+
+ setComponents(values: { [key: string]: Type<{}> }) {
+ if (values) {
+ this.components = Object.assign({}, this.components, values);
+ }
+ }
+
+ getComponentById(id: string): Type {
+ return > this.components[id];
+ }
+
+ hasComponentById(id: string): boolean {
+ return !!this.getComponentById(id);
+ }
+}
diff --git a/lib/extensions/src/lib/services/extension.service.spec.ts b/lib/extensions/src/lib/services/extension.service.spec.ts
index 79f495d9fe..ba39f94c96 100644
--- a/lib/extensions/src/lib/services/extension.service.spec.ts
+++ b/lib/extensions/src/lib/services/extension.service.spec.ts
@@ -21,6 +21,7 @@ import { ExtensionConfig } from '../config/extension.config';
import { RuleRef } from '../config/rule.extensions';
import { RouteRef } from '../config/routing.extensions';
import { ActionRef } from '../config/action.extensions';
+import { ComponentRegisterService } from './component-register.service';
describe('ExtensionService', () => {
const blankConfig: ExtensionConfig = {
@@ -33,11 +34,13 @@ describe('ExtensionService', () => {
};
let loader: ExtensionLoaderService;
+ let componentRegister: ComponentRegisterService;
let service: ExtensionService;
beforeEach(() => {
loader = new ExtensionLoaderService(null);
- service = new ExtensionService(loader);
+ componentRegister = new ComponentRegisterService();
+ service = new ExtensionService(loader, componentRegister);
});
it('should load and setup a config', async () => {
diff --git a/lib/extensions/src/lib/services/extension.service.ts b/lib/extensions/src/lib/services/extension.service.ts
index d968a9c9c0..58e86f2954 100644
--- a/lib/extensions/src/lib/services/extension.service.ts
+++ b/lib/extensions/src/lib/services/extension.service.ts
@@ -22,6 +22,7 @@ import { ExtensionLoaderService } from './extension-loader.service';
import { RouteRef } from '../config/routing.extensions';
import { ActionRef } from '../config/action.extensions';
import * as core from '../evaluators/core.evaluators';
+import { ComponentRegisterService } from './component-register.service';
@Injectable({
providedIn: 'root'
@@ -35,10 +36,12 @@ export class ExtensionService {
actions: Array = [];
authGuards: { [key: string]: Type<{}> } = {};
- components: { [key: string]: Type<{}> } = {};
evaluators: { [key: string]: RuleEvaluator } = {};
- constructor(private loader: ExtensionLoaderService) {}
+ constructor(
+ private loader: ExtensionLoaderService,
+ private componentRegister: ComponentRegisterService
+ ) {}
async load(): Promise {
const config = await this.loader.load(
@@ -79,9 +82,7 @@ export class ExtensionService {
}
setComponents(values: { [key: string]: Type<{}> }) {
- if (values) {
- this.components = Object.assign({}, this.components, values);
- }
+ this.componentRegister.setComponents(values);
}
getRouteById(id: string): RouteRef {
@@ -125,8 +126,8 @@ export class ExtensionService {
return false;
}
- getComponentById(id: string): Type<{}> {
- return this.components[id];
+ getComponentById(id: string) {
+ return this.componentRegister.getComponentById(id);
}
getRuleById(id: string): RuleRef {
diff --git a/lib/extensions/src/public_api.ts b/lib/extensions/src/public_api.ts
index 19998280ad..828094cd8e 100644
--- a/lib/extensions/src/public_api.ts
+++ b/lib/extensions/src/public_api.ts
@@ -28,6 +28,7 @@ export * from './lib/config/viewer.extensions';
export * from './lib/services/extension-loader.service';
export * from './lib/services/extension.service';
+export * from './lib/services/component-register.service';
export * from './lib/store/states/navigation.state';
export * from './lib/store/states/profile.state';