mirror of https://github.com/abpframework/abp.git
3 changed files with 427 additions and 0 deletions
@ -0,0 +1,181 @@ |
|||||
|
import { |
||||
|
ApplicationRef, |
||||
|
ComponentFactoryResolver, |
||||
|
ComponentRef, |
||||
|
EmbeddedViewRef, |
||||
|
Injector, |
||||
|
TemplateRef, |
||||
|
Type, |
||||
|
ViewContainerRef, |
||||
|
} from '@angular/core'; |
||||
|
import { InferedInstanceOf } from '../models/utility'; |
||||
|
import { ContainerStrategy, CONTAINER_STRATEGY } from './container.strategy'; |
||||
|
import { |
||||
|
ComponentContextStrategy, |
||||
|
ContextStrategy, |
||||
|
CONTEXT_STRATEGY, |
||||
|
TemplateContextStrategy, |
||||
|
} from './context.strategy'; |
||||
|
import { DomStrategy, DOM_STRATEGY } from './dom.strategy'; |
||||
|
|
||||
|
export abstract class ProjectionStrategy<T = any> { |
||||
|
constructor(public content: T) {} |
||||
|
|
||||
|
abstract injectContent(injector: Injector): ComponentRefOrEmbeddedViewRef<T>; |
||||
|
} |
||||
|
|
||||
|
export class ComponentProjectionStrategy<T extends Type<any>> extends ProjectionStrategy<T> { |
||||
|
constructor( |
||||
|
component: T, |
||||
|
private containerStrategy: ContainerStrategy, |
||||
|
private contextStrategy: ContextStrategy = CONTEXT_STRATEGY.None(), |
||||
|
) { |
||||
|
super(component); |
||||
|
} |
||||
|
|
||||
|
injectContent(injector: Injector) { |
||||
|
this.containerStrategy.prepare(); |
||||
|
|
||||
|
const resolver = injector.get(ComponentFactoryResolver) as ComponentFactoryResolver; |
||||
|
const factory = resolver.resolveComponentFactory<InferedInstanceOf<T>>(this.content); |
||||
|
|
||||
|
const componentRef = this.containerStrategy.containerRef.createComponent( |
||||
|
factory, |
||||
|
this.containerStrategy.getIndex(), |
||||
|
injector, |
||||
|
); |
||||
|
this.contextStrategy.setContext(componentRef); |
||||
|
|
||||
|
return componentRef as ComponentRefOrEmbeddedViewRef<T>; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class RootComponentProjectionStrategy<T extends Type<any>> extends ProjectionStrategy<T> { |
||||
|
constructor( |
||||
|
component: T, |
||||
|
private contextStrategy: ContextStrategy = CONTEXT_STRATEGY.None(), |
||||
|
private domStrategy: DomStrategy = DOM_STRATEGY.AppendToBody(), |
||||
|
) { |
||||
|
super(component); |
||||
|
} |
||||
|
|
||||
|
injectContent(injector: Injector) { |
||||
|
const appRef = injector.get(ApplicationRef); |
||||
|
const resolver = injector.get(ComponentFactoryResolver) as ComponentFactoryResolver; |
||||
|
const componentRef = resolver |
||||
|
.resolveComponentFactory<InferedInstanceOf<T>>(this.content) |
||||
|
.create(injector); |
||||
|
|
||||
|
this.contextStrategy.setContext(componentRef); |
||||
|
|
||||
|
appRef.attachView(componentRef.hostView); |
||||
|
const element: HTMLElement = (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0]; |
||||
|
this.domStrategy.insertElement(element); |
||||
|
|
||||
|
return componentRef as ComponentRefOrEmbeddedViewRef<T>; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class TemplateProjectionStrategy<T extends TemplateRef<any>> extends ProjectionStrategy<T> { |
||||
|
constructor( |
||||
|
template: T, |
||||
|
private containerStrategy: ContainerStrategy, |
||||
|
private contextStrategy = CONTEXT_STRATEGY.None(), |
||||
|
) { |
||||
|
super(template); |
||||
|
} |
||||
|
|
||||
|
injectContent(injector: Injector) { |
||||
|
this.containerStrategy.prepare(); |
||||
|
|
||||
|
const embeddedViewRef = this.containerStrategy.containerRef.createEmbeddedView( |
||||
|
this.content, |
||||
|
this.contextStrategy.context, |
||||
|
this.containerStrategy.getIndex(), |
||||
|
); |
||||
|
embeddedViewRef.detectChanges(); |
||||
|
|
||||
|
return embeddedViewRef as ComponentRefOrEmbeddedViewRef<T>; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const PROJECTION_STRATEGY = { |
||||
|
AppendComponentToBody<T extends Type<unknown>>( |
||||
|
component: T, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) { |
||||
|
return new RootComponentProjectionStrategy<T>(component, contextStrategy); |
||||
|
}, |
||||
|
AppendComponentToContainer<T extends Type<unknown>>( |
||||
|
component: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) { |
||||
|
return new ComponentProjectionStrategy<T>( |
||||
|
component, |
||||
|
CONTAINER_STRATEGY.Append(containerRef), |
||||
|
contextStrategy, |
||||
|
); |
||||
|
}, |
||||
|
AppendTemplateToContainer<T extends TemplateRef<unknown>>( |
||||
|
template: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: TemplateContextStrategy<T>, |
||||
|
) { |
||||
|
return new TemplateProjectionStrategy<T>( |
||||
|
template, |
||||
|
CONTAINER_STRATEGY.Append(containerRef), |
||||
|
contextStrategy, |
||||
|
); |
||||
|
}, |
||||
|
PrependComponentToContainer<T extends Type<unknown>>( |
||||
|
component: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) { |
||||
|
return new ComponentProjectionStrategy<T>( |
||||
|
component, |
||||
|
CONTAINER_STRATEGY.Prepend(containerRef), |
||||
|
contextStrategy, |
||||
|
); |
||||
|
}, |
||||
|
PrependTemplateToContainer<T extends TemplateRef<unknown>>( |
||||
|
template: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: TemplateContextStrategy<T>, |
||||
|
) { |
||||
|
return new TemplateProjectionStrategy<T>( |
||||
|
template, |
||||
|
CONTAINER_STRATEGY.Prepend(containerRef), |
||||
|
contextStrategy, |
||||
|
); |
||||
|
}, |
||||
|
ProjectComponentToContainer<T extends Type<unknown>>( |
||||
|
component: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) { |
||||
|
return new ComponentProjectionStrategy<T>( |
||||
|
component, |
||||
|
CONTAINER_STRATEGY.Clear(containerRef), |
||||
|
contextStrategy, |
||||
|
); |
||||
|
}, |
||||
|
ProjectTemplateToContainer<T extends TemplateRef<unknown>>( |
||||
|
template: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: TemplateContextStrategy<T>, |
||||
|
) { |
||||
|
return new TemplateProjectionStrategy<T>( |
||||
|
template, |
||||
|
CONTAINER_STRATEGY.Clear(containerRef), |
||||
|
contextStrategy, |
||||
|
); |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
type ComponentRefOrEmbeddedViewRef<T> = T extends Type<infer U> |
||||
|
? ComponentRef<U> |
||||
|
: T extends TemplateRef<infer C> |
||||
|
? EmbeddedViewRef<C> |
||||
|
: never; |
||||
@ -0,0 +1,245 @@ |
|||||
|
import { |
||||
|
Component, |
||||
|
ComponentRef, |
||||
|
EmbeddedViewRef, |
||||
|
TemplateRef, |
||||
|
ViewChild, |
||||
|
ViewContainerRef, |
||||
|
} from '@angular/core'; |
||||
|
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; |
||||
|
import { |
||||
|
ComponentProjectionStrategy, |
||||
|
ContainerStrategy, |
||||
|
CONTAINER_STRATEGY, |
||||
|
CONTEXT_STRATEGY, |
||||
|
DOM_STRATEGY, |
||||
|
PROJECTION_STRATEGY, |
||||
|
RootComponentProjectionStrategy, |
||||
|
TemplateProjectionStrategy, |
||||
|
} from '../strategies'; |
||||
|
|
||||
|
describe('ComponentProjectionStrategy', () => { |
||||
|
@Component({ |
||||
|
template: '<div class="foo">{{ bar || baz }}</div>', |
||||
|
}) |
||||
|
class TestComponent { |
||||
|
bar: string; |
||||
|
baz = 'baz'; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
template: '<ng-container #container></ng-container>', |
||||
|
}) |
||||
|
class HostComponent { |
||||
|
@ViewChild('container', { static: true, read: ViewContainerRef }) |
||||
|
containerRef: ViewContainerRef; |
||||
|
} |
||||
|
|
||||
|
let containerStrategy: ContainerStrategy; |
||||
|
let spectator: Spectator<HostComponent>; |
||||
|
let componentRef: ComponentRef<TestComponent>; |
||||
|
|
||||
|
const createComponent = createComponentFactory({ |
||||
|
component: HostComponent, |
||||
|
entryComponents: [TestComponent], |
||||
|
}); |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
spectator = createComponent({}); |
||||
|
containerStrategy = CONTAINER_STRATEGY.Clear(spectator.component.containerRef); |
||||
|
}); |
||||
|
|
||||
|
afterEach(() => { |
||||
|
componentRef.destroy(); |
||||
|
spectator.detectChanges(); |
||||
|
}); |
||||
|
|
||||
|
describe('#injectContent', () => { |
||||
|
it('should should insert content into container and return a ComponentRef', () => { |
||||
|
const strategy = new ComponentProjectionStrategy(TestComponent, containerStrategy); |
||||
|
componentRef = strategy.injectContent(spectator); |
||||
|
spectator.detectChanges(); |
||||
|
|
||||
|
const div = spectator.query('div.foo'); |
||||
|
expect(div.textContent).toBe('baz'); |
||||
|
expect(componentRef).toBeInstanceOf(ComponentRef); |
||||
|
}); |
||||
|
|
||||
|
it('should be able to map context to projected component', () => { |
||||
|
const contextStrategy = CONTEXT_STRATEGY.Component({ bar: 'bar' }); |
||||
|
const strategy = new ComponentProjectionStrategy( |
||||
|
TestComponent, |
||||
|
containerStrategy, |
||||
|
contextStrategy, |
||||
|
); |
||||
|
componentRef = strategy.injectContent(spectator); |
||||
|
spectator.detectChanges(); |
||||
|
|
||||
|
const div = spectator.query('div.foo'); |
||||
|
expect(div.textContent).toBe('bar'); |
||||
|
expect(componentRef.instance.bar).toBe('bar'); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('RootComponentProjectionStrategy', () => { |
||||
|
@Component({ |
||||
|
template: '<div class="foo">{{ bar || baz }}</div>', |
||||
|
}) |
||||
|
class TestComponent { |
||||
|
bar: string; |
||||
|
baz = 'baz'; |
||||
|
} |
||||
|
|
||||
|
@Component({ template: '' }) |
||||
|
class HostComponent {} |
||||
|
|
||||
|
let spectator: Spectator<HostComponent>; |
||||
|
let componentRef: ComponentRef<TestComponent>; |
||||
|
|
||||
|
const createComponent = createComponentFactory({ |
||||
|
component: HostComponent, |
||||
|
entryComponents: [TestComponent], |
||||
|
}); |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
spectator = createComponent({}); |
||||
|
}); |
||||
|
|
||||
|
afterEach(() => { |
||||
|
componentRef.destroy(); |
||||
|
spectator.detectChanges(); |
||||
|
}); |
||||
|
|
||||
|
describe('#injectContent', () => { |
||||
|
it('should should insert content into body and return a ComponentRef', () => { |
||||
|
const strategy = new RootComponentProjectionStrategy(TestComponent); |
||||
|
componentRef = strategy.injectContent(spectator); |
||||
|
spectator.detectChanges(); |
||||
|
|
||||
|
const div = document.querySelector('body > ng-component > div.foo'); |
||||
|
expect(div.textContent).toBe('baz'); |
||||
|
expect(componentRef).toBeInstanceOf(ComponentRef); |
||||
|
componentRef.destroy(); |
||||
|
spectator.detectChanges(); |
||||
|
}); |
||||
|
|
||||
|
it('should be able to map context to projected component', () => { |
||||
|
const contextStrategy = CONTEXT_STRATEGY.Component({ bar: 'bar' }); |
||||
|
const strategy = new RootComponentProjectionStrategy(TestComponent, contextStrategy); |
||||
|
componentRef = strategy.injectContent(spectator); |
||||
|
spectator.detectChanges(); |
||||
|
|
||||
|
const div = document.querySelector('body > ng-component > div.foo'); |
||||
|
expect(div.textContent).toBe('bar'); |
||||
|
expect(componentRef.instance.bar).toBe('bar'); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('TemplateProjectionStrategy', () => { |
||||
|
@Component({ |
||||
|
template: ` |
||||
|
<ng-template #template let-bar> |
||||
|
<div class="foo">{{ bar || baz }}</div> |
||||
|
</ng-template> |
||||
|
<ng-container #container></ng-container> |
||||
|
`,
|
||||
|
}) |
||||
|
class HostComponent { |
||||
|
@ViewChild('container', { static: true, read: ViewContainerRef }) |
||||
|
containerRef: ViewContainerRef; |
||||
|
|
||||
|
@ViewChild('template', { static: true }) |
||||
|
templateRef: TemplateRef<{ $implicit?: string }>; |
||||
|
|
||||
|
baz = 'baz'; |
||||
|
} |
||||
|
|
||||
|
let containerStrategy: ContainerStrategy; |
||||
|
let spectator: Spectator<HostComponent>; |
||||
|
let embeddedViewRef: EmbeddedViewRef<{ $implicit?: string }>; |
||||
|
|
||||
|
const createComponent = createComponentFactory({ |
||||
|
component: HostComponent, |
||||
|
}); |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
spectator = createComponent({}); |
||||
|
containerStrategy = CONTAINER_STRATEGY.Clear(spectator.component.containerRef); |
||||
|
}); |
||||
|
|
||||
|
afterEach(() => { |
||||
|
embeddedViewRef.destroy(); |
||||
|
spectator.detectChanges(); |
||||
|
}); |
||||
|
|
||||
|
describe('#injectContent', () => { |
||||
|
it('should should insert content into container and return an EmbeddedViewRef', () => { |
||||
|
const templateRef = spectator.component.templateRef; |
||||
|
const strategy = new TemplateProjectionStrategy(templateRef, containerStrategy); |
||||
|
embeddedViewRef = strategy.injectContent(spectator); |
||||
|
spectator.detectChanges(); |
||||
|
|
||||
|
const div = spectator.query('div.foo'); |
||||
|
expect(div.textContent).toBe('baz'); |
||||
|
expect(embeddedViewRef).toHaveProperty('detectChanges'); |
||||
|
expect(embeddedViewRef).toHaveProperty('markForCheck'); |
||||
|
expect(embeddedViewRef).toHaveProperty('detach'); |
||||
|
expect(embeddedViewRef).toHaveProperty('reattach'); |
||||
|
expect(embeddedViewRef).toHaveProperty('destroy'); |
||||
|
expect(embeddedViewRef).toHaveProperty('rootNodes'); |
||||
|
expect(embeddedViewRef).toHaveProperty('context'); |
||||
|
}); |
||||
|
|
||||
|
it('should be able to map context to projected template', () => { |
||||
|
const templateRef = spectator.component.templateRef; |
||||
|
const contextStrategy = CONTEXT_STRATEGY.Template<typeof templateRef>({ $implicit: 'bar' }); |
||||
|
const strategy = new TemplateProjectionStrategy( |
||||
|
templateRef, |
||||
|
containerStrategy, |
||||
|
contextStrategy, |
||||
|
); |
||||
|
embeddedViewRef = strategy.injectContent(spectator); |
||||
|
spectator.detectChanges(); |
||||
|
|
||||
|
const div = spectator.query('div.foo'); |
||||
|
expect(div.textContent).toBe('bar'); |
||||
|
expect(embeddedViewRef.context).toEqual(contextStrategy.context); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('PROJECTION_STRATEGY', () => { |
||||
|
const content = undefined; |
||||
|
const containerRef = ({ length: 0 } as any) as ViewContainerRef; |
||||
|
test.each` |
||||
|
name | Strategy | containerStrategy |
||||
|
${'AppendComponentToContainer'} | ${ComponentProjectionStrategy} | ${CONTAINER_STRATEGY.Append} |
||||
|
${'AppendTemplateToContainer'} | ${TemplateProjectionStrategy} | ${CONTAINER_STRATEGY.Append} |
||||
|
${'PrependComponentToContainer'} | ${ComponentProjectionStrategy} | ${CONTAINER_STRATEGY.Prepend} |
||||
|
${'PrependTemplateToContainer'} | ${TemplateProjectionStrategy} | ${CONTAINER_STRATEGY.Prepend} |
||||
|
${'ProjectComponentToContainer'} | ${ComponentProjectionStrategy} | ${CONTAINER_STRATEGY.Clear} |
||||
|
${'ProjectTemplateToContainer'} | ${TemplateProjectionStrategy} | ${CONTAINER_STRATEGY.Clear} |
||||
|
`(
|
||||
|
'should successfully map $name to $Strategy.name with $containerStrategy.name container strategy', |
||||
|
({ name, Strategy, containerStrategy }) => { |
||||
|
expect(PROJECTION_STRATEGY[name](content, containerRef)).toEqual( |
||||
|
new Strategy(content, containerStrategy(containerRef)), |
||||
|
); |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
const contextStrategy = undefined; |
||||
|
test.each` |
||||
|
name | Strategy | domStrategy |
||||
|
${'AppendComponentToBody'} | ${RootComponentProjectionStrategy} | ${DOM_STRATEGY.AppendToBody} |
||||
|
`(
|
||||
|
'should successfully map $name to $Strategy.name with $domStrategy.name dom strategy', |
||||
|
({ name, Strategy, domStrategy }) => { |
||||
|
expect(PROJECTION_STRATEGY[name](content, contextStrategy)).toEqual( |
||||
|
new Strategy(content, contextStrategy, domStrategy()), |
||||
|
); |
||||
|
}, |
||||
|
); |
||||
|
}); |
||||
Loading…
Reference in new issue