Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds eg-brs extensions #41

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,14 @@ export class TalkModule {
Just like with the Angular Router, define the map of component selector and lazy module.

```
const lazyConfig = [
{
selector: 'talk',
loadChildren: () => import('./talk/talk.module').then(m => m.TalkModule)
}
const lazyConfig = {
definitions: [
{
selector: 'talk',
loadChildren: () => import('./talk/talk.module').then(m => m.TalkModule)
}
],
useCustomElementNames: false
];

@NgModule({
Expand Down
14 changes: 9 additions & 5 deletions projects/ngx-element/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,17 @@ export class TalkModule {
Just like with the Angular Router, define the map of component selector and lazy module.

```
const lazyConfig = [
{
selector: 'talk',
loadChildren: () => import('./talk/talk.module').then(m => m.TalkModule)
}
const lazyConfig = {
definitions: [
{
selector: 'talk',
loadChildren: () => import('./talk/talk.module').then(m => m.TalkModule)
}
],
useCustomElementNames: false
];


@NgModule({
...,
imports: [
Expand Down
25 changes: 11 additions & 14 deletions projects/ngx-element/src/lib/ngx-element.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,31 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { NgxElementComponent } from './ngx-element.component';
import { LAZY_CMPS_PATH_TOKEN } from './tokens';
import { createDef, LazyComponentRegistry, LAZY_CMPS_REGISTRY } from './tokens';

describe('NgxElementComponent', () => {
let component: NgxElementComponent;
let fixture: ComponentFixture<NgxElementComponent>;

const lazyConfig = [
{
selector: 'talk',
loadChildren: () => import('../../../ngx-element-app/src/app/talk/talk.module').then(m => m.TalkModule)
},
{
selector: 'sponsor',
loadChildren: () => import('../../../ngx-element-app/src/app/sponsor/sponsor.module').then(m => m.SponsorModule)
}
];
const lazyConfig: LazyComponentRegistry = {
definitions: [
createDef('talk', () => import('../../../ngx-element-app/src/app/talk/talk.module').then(m => m.TalkModule)),
createDef('sponsor', () => import('../../../ngx-element-app/src/app/sponsor/sponsor.module').then(m => m.SponsorModule))
],
useCustomElementNames: false
};

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ NgxElementComponent ],
declarations: [NgxElementComponent],
providers: [
{
provide: LAZY_CMPS_PATH_TOKEN,
provide: LAZY_CMPS_REGISTRY,
useValue: lazyConfig
}
]
})
.compileComponents();
.compileComponents();
}));

beforeEach(() => {
Expand Down
58 changes: 45 additions & 13 deletions projects/ngx-element/src/lib/ngx-element.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
ComponentFactory,
OnInit,
Input,
Output,
Type,
ViewChild,
ViewContainerRef,
Expand All @@ -12,16 +11,17 @@ import {
EventEmitter,
ElementRef,
Injector,
ReflectiveInjector
Inject
} from '@angular/core';
import {NgxElementService} from './ngx-element.service';
import {merge, Subscription} from 'rxjs';
import {map} from 'rxjs/operators';
import { LazyComponentRegistry, LAZY_CMPS_REGISTRY } from './tokens';

@Component({
selector: 'lib-ngx-element',
template: `
<ng-template #container></ng-template>
<ng-content></ng-content>
`,
styles: []
})
Expand All @@ -35,12 +35,13 @@ export class NgxElementComponent implements OnInit, OnDestroy {
componentToLoad: Type<any>;
componentFactoryResolver: ComponentFactoryResolver;
injector: Injector;
refInjector: ReflectiveInjector;
refInjector: Injector;

constructor(
private ngxElementService: NgxElementService,
private elementRef: ElementRef
) {}
private elementRef: ElementRef,
@Inject(LAZY_CMPS_REGISTRY) private registry: LazyComponentRegistry
) { }

/**
* Subscribe to event emitters of a lazy loaded and dynamically instantiated Angular component
Expand All @@ -60,7 +61,9 @@ export class NgxElementComponent implements OnInit, OnDestroy {
}

ngOnInit(): void {
this.ngxElementService.getComponentToLoad(this.selector).subscribe(event => {
const selector = this.resolveSelector();

this.ngxElementService.getComponentToLoad(selector).subscribe(event => {
this.componentToLoad = event.componentClass;
this.componentFactoryResolver = this.ngxElementService.getComponentFactoryResolver(this.componentToLoad);
this.injector = this.ngxElementService.getInjector(this.componentToLoad);
Expand All @@ -71,13 +74,19 @@ export class NgxElementComponent implements OnInit, OnDestroy {
}

createComponent(attributes) {
this.container.clear();
const factory = this.componentFactoryResolver.resolveComponentFactory(this.componentToLoad);

this.refInjector = ReflectiveInjector.resolveAndCreate(
[{provide: this.componentToLoad, useValue: this.componentToLoad}], this.injector
);
this.componentRef = this.container.createComponent(factory, 0, this.refInjector);
if (this.registry.useCustomElementNames && this.ngxElementService.isSelectorRegistered(factory.selector)) {
console.warn(`Cannot lazy load component that defines ${factory.selector} as a selector, because the selector is
already reserved in the LazyComponentRegistry.`);
return;
}

this.refInjector = Injector.create({ providers: [{ provide: this.componentToLoad, useValue: this.componentToLoad }] });

const projectNodes = this.extractProjectedNodes(factory);
this.container.clear();
this.componentRef = this.container.createComponent(factory, 0, this.refInjector, projectNodes);

this.setAttributes(attributes);
this.listenToAttributeChanges();
Expand All @@ -97,7 +106,7 @@ export class NgxElementComponent implements OnInit, OnDestroy {
for (let attr, i = 0; i < attrs.length; i++) {
attr = attrs[i];

if (attr.nodeName.match('^data-')) {
if ((!this.registry.useCustomElementNames && attr.nodeName.match('^data-')) || this.registry.useCustomElementNames) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The property useCustomElementNames in the registry also affects the attributes that can be passed to the element?

attributes.push({
name: this.camelCaseAttribute(attr.nodeName),
value: attr.nodeValue
Expand Down Expand Up @@ -138,4 +147,27 @@ export class NgxElementComponent implements OnInit, OnDestroy {
this.componentRef.destroy();
this.ngElementEventsSubscription.unsubscribe();
}

private extractProjectedNodes(factory: ComponentFactory<any>) {
const projectNodes = [];
factory.ngContentSelectors.forEach(selector => {
const el = this.elementRef.nativeElement as HTMLElement;
const content = el.querySelectorAll(selector);
if (content) {
const nodes = [];
content.forEach(c => {
const p = c.parentElement;
nodes.push(p.removeChild(c));
});
projectNodes.push(nodes);
}
});
return projectNodes;
}

private resolveSelector() {
return this.registry.useCustomElementNames ?
this.elementRef.nativeElement.localName.substring(this.registry.customElementNamePrefix.length + 1) :
this.selector;
}
}
23 changes: 15 additions & 8 deletions projects/ngx-element/src/lib/ngx-element.module.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
import { NgModule, Injector, ModuleWithProviders } from '@angular/core';
import { NgModule, Injector, ModuleWithProviders, Inject } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { NgxElementComponent } from './ngx-element.component';
import { LAZY_CMPS_PATH_TOKEN } from './tokens';
import { LazyComponentRegistry, LAZY_CMPS_REGISTRY } from './tokens';

@NgModule({
declarations: [NgxElementComponent],
entryComponents: [NgxElementComponent]
})
export class NgxElementModule {

constructor(private injector: Injector) {
const ngxElement = createCustomElement(NgxElementComponent, { injector });
customElements.define('ngx-element', ngxElement);
constructor(private injector: Injector, @Inject(LAZY_CMPS_REGISTRY) private registry: LazyComponentRegistry) {
if(!registry.useCustomElementNames) {
const ngxElement = createCustomElement(NgxElementComponent, { injector });
customElements.define('ngx-element', ngxElement);
} else {
registry.definitions.forEach(def => {
const ngxElement = createCustomElement(NgxElementComponent, { injector });
customElements.define(`${registry.customElementNamePrefix}-${def.selector}`, ngxElement);
});
}
}

static forRoot(modulePaths: any[]): ModuleWithProviders<NgxElementModule> {
static forRoot(registry: any): ModuleWithProviders<NgxElementModule> {
return {
ngModule: NgxElementModule,
providers: [
{
provide: LAZY_CMPS_PATH_TOKEN,
useValue: modulePaths
provide: LAZY_CMPS_REGISTRY,
useValue: registry
}
]
};
Expand Down
21 changes: 9 additions & 12 deletions projects/ngx-element/src/lib/ngx-element.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
import { TestBed } from '@angular/core/testing';
import { NgxElementService } from './ngx-element.service';
import { LAZY_CMPS_PATH_TOKEN } from './tokens';
import { createDef, LazyComponentRegistry, LAZY_CMPS_REGISTRY } from './tokens';

describe('NgxElementService', () => {
let service: NgxElementService;

const lazyConfig = [
{
selector: 'talk',
loadChildren: () => import('../../../ngx-element-app/src/app/talk/talk.module').then(m => m.TalkModule)
},
{
selector: 'sponsor',
loadChildren: () => import('../../../ngx-element-app/src/app/sponsor/sponsor.module').then(m => m.SponsorModule)
}
];
const lazyConfig: LazyComponentRegistry = {
definitions: [
createDef('talk', () => import('../../../ngx-element-app/src/app/talk/talk.module').then(m => m.TalkModule)),
createDef('sponsor', () => import('../../../ngx-element-app/src/app/sponsor/sponsor.module').then(m => m.SponsorModule))
],
useCustomElementNames: false
};

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{
provide: LAZY_CMPS_PATH_TOKEN,
provide: LAZY_CMPS_REGISTRY,
useValue: lazyConfig
}
]
Expand Down
65 changes: 38 additions & 27 deletions projects/ngx-element/src/lib/ngx-element.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable, Inject, NgModuleFactory, Type, Compiler, Injector, ComponentFactoryResolver } from '@angular/core';
import { LAZY_CMPS_PATH_TOKEN, LazyComponentDef } from './tokens';
import { LAZY_CMPS_REGISTRY, LazyComponentDef, LazyComponentRegistry } from './tokens';
import { LazyCmpLoadedEvent } from './lazy-component-loaded-event';
import { Observable, from } from 'rxjs';

Expand All @@ -15,15 +15,12 @@ export class NgxElementService {
componentFactoryResolvers = new Map<Type<any>, ComponentFactoryResolver>();

constructor(
@Inject(LAZY_CMPS_PATH_TOKEN)
modulePaths: {
selector: string
}[],
@Inject(LAZY_CMPS_REGISTRY) private registry: LazyComponentRegistry,
private compiler: Compiler,
private injector: Injector
) {
const ELEMENT_MODULE_PATHS = new Map<string, any>();
modulePaths.forEach(route => {
registry.definitions.forEach(route => {
ELEMENT_MODULE_PATHS.set(route.selector, route);
});

Expand Down Expand Up @@ -53,6 +50,20 @@ export class NgxElementService {
return from(registered);
}

/**
* Checks whether the selector is registered in the registry.
* @param selector
*/
isSelectorRegistered(selector: string) {
let result = false;
this.registry.definitions.forEach(def => {
if (selector === def.selector) {
result = true;
}
});
return result;
}

/**
* Allows to lazy load a component given its selector.
* If the component selector has been registered, it's according module
Expand Down Expand Up @@ -93,35 +104,35 @@ export class NgxElementService {
}
})
.then(moduleFactory => {
const elementModuleRef = moduleFactory.create(this.injector);
let componentClass;
const elementModuleRef = moduleFactory.create(this.injector);
let componentClass;

if (typeof elementModuleRef.instance.customElementComponent === 'object') {
componentClass = elementModuleRef.instance.customElementComponent[componentSelector];
if (typeof elementModuleRef.instance.customElementComponent === 'object') {
componentClass = elementModuleRef.instance.customElementComponent[componentSelector];

if (!componentClass) {
// tslint:disable-next-line: no-string-throw
throw `You specified multiple component elements in module ${elementModuleRef} but there was no match for tag
if (!componentClass) {
// tslint:disable-next-line: no-string-throw
throw `You specified multiple component elements in module ${elementModuleRef} but there was no match for tag
${componentSelector} in ${JSON.stringify(elementModuleRef.instance.customElementComponent)}.
Make sure the selector in the module is aligned with the one specified in the lazy module definition.`;
}
} else {
componentClass = elementModuleRef.instance.customElementComponent;
}
} else {
componentClass = elementModuleRef.instance.customElementComponent;
}

// Register injector of the lazy module.
// This is needed to share the entryComponents between the lazy module and the application
const moduleInjector = elementModuleRef.injector;
this.receiveContext(componentClass, moduleInjector);
// Register injector of the lazy module.
// This is needed to share the entryComponents between the lazy module and the application
const moduleInjector = elementModuleRef.injector;
this.receiveContext(componentClass, moduleInjector);

this.loadedComponents.set(componentSelector, componentClass);
this.elementsLoading.delete(componentSelector);
this.componentsToLoad.delete(componentSelector);
this.loadedComponents.set(componentSelector, componentClass);
this.elementsLoading.delete(componentSelector);
this.componentsToLoad.delete(componentSelector);

resolve({
selector: componentSelector,
componentClass
});
resolve({
selector: componentSelector,
componentClass
});
})
.catch(err => {
this.elementsLoading.delete(componentSelector);
Expand Down
Loading