mirror of https://github.com/abpframework/abp.git
committed by
GitHub
23 changed files with 1305 additions and 39 deletions
@ -0,0 +1,101 @@ |
|||||
|
# ContainerStrategy |
||||
|
|
||||
|
`ContainerStrategy` is an abstract class exposed by @abp/ng.core package. There are two container strategies extending it: `ClearContainerStrategy` and `InsertIntoContainerStrategy`. Implementing the same methods and properties, both of these strategies help you define how your containers will be prepared and where your content will be projected. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## API |
||||
|
|
||||
|
`ClearContainerStrategy` is a class that extends `ContainerStrategy`. It lets you **clear a container before projecting content in it**. |
||||
|
|
||||
|
|
||||
|
### constructor |
||||
|
|
||||
|
```js |
||||
|
constructor( |
||||
|
public containerRef: ViewContainerRef, |
||||
|
private index?: number, // works only in InsertIntoContainerStrategy |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
- `containerRef` is the `ViewContainerRef` that will be used when projecting the content. |
||||
|
|
||||
|
|
||||
|
### getIndex |
||||
|
|
||||
|
```js |
||||
|
getIndex(): number |
||||
|
``` |
||||
|
|
||||
|
This method return the given index clamped by `0` and `length` of the `containerRef`. For strategies without an index, it returns `0`. |
||||
|
|
||||
|
|
||||
|
### prepare |
||||
|
|
||||
|
```js |
||||
|
prepare(): void |
||||
|
``` |
||||
|
|
||||
|
This method is called before content projection. Based on used container strategy, it either clears the container or does nothing (noop). |
||||
|
|
||||
|
|
||||
|
|
||||
|
## ClearContainerStrategy |
||||
|
|
||||
|
`ClearContainerStrategy` is a class that extends `ContainerStrategy`. It lets you **clear a container before projecting content in it**. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## InsertIntoContainerStrategy |
||||
|
|
||||
|
`InsertIntoContainerStrategy` is a class that extends `ContainerStrategy`. It lets you **project your content at a specific node index in the container**. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Predefined Container Strategies |
||||
|
|
||||
|
Predefined container strategies are accessible via `CONTAINER_STRATEGY` constant. |
||||
|
|
||||
|
|
||||
|
### Clear |
||||
|
|
||||
|
```js |
||||
|
CONTAINER_STRATEGY.Clear(containerRef: ViewContainerRef) |
||||
|
``` |
||||
|
|
||||
|
Clears given container before content projection. |
||||
|
|
||||
|
|
||||
|
### Append |
||||
|
|
||||
|
```js |
||||
|
CONTAINER_STRATEGY.Append(containerRef: ViewContainerRef) |
||||
|
``` |
||||
|
|
||||
|
Projected content will be appended to the container. |
||||
|
|
||||
|
|
||||
|
### Prepend |
||||
|
|
||||
|
```js |
||||
|
CONTAINER_STRATEGY.Prepend(containerRef: ViewContainerRef) |
||||
|
``` |
||||
|
|
||||
|
Projected content will be prepended to the container. |
||||
|
|
||||
|
|
||||
|
### Insert |
||||
|
|
||||
|
```js |
||||
|
CONTAINER_STRATEGY.Insert( |
||||
|
containerRef: ViewContainerRef, |
||||
|
index: number, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
Projected content will be inserted into to the container at given index (clamped by `0` and `length` of the `containerRef`). |
||||
|
|
||||
|
|
||||
|
## See Also |
||||
|
|
||||
|
- [ProjectionStrategy](./Projection-Strategy.md) |
||||
@ -0,0 +1,78 @@ |
|||||
|
# Content Projection |
||||
|
|
||||
|
You can use the `ContentProjectionService` in @abp/ng.core package in order to project content in an easy and explicit way. |
||||
|
|
||||
|
## Getting Started |
||||
|
|
||||
|
You do not have to provide the `ContentProjectionService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services. |
||||
|
|
||||
|
```js |
||||
|
import { ContentProjectionService } from '@abp/ng.core'; |
||||
|
|
||||
|
@Component({ |
||||
|
/* class metadata here */ |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
constructor(private contentProjectionService: ContentProjectionService) {} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Usage |
||||
|
|
||||
|
You can use the `projectContent` method of `ContentProjectionService` to render components and templates dynamically in your project. |
||||
|
|
||||
|
### How to Project Components to Root Level |
||||
|
|
||||
|
If you pass a `RootComponentProjectionStrategy` as the first parameter of `projectContent` method, the `ContentProjectionService` will resolve the projected component and place it at the root level. If provided, it will also pass the component a context. |
||||
|
|
||||
|
```js |
||||
|
const strategy = PROJECTION_STRATEGY.AppendComponentToBody( |
||||
|
SomeOverlayComponent, |
||||
|
{ someOverlayProp: "SOME_VALUE" } |
||||
|
); |
||||
|
|
||||
|
const componentRef = this.ContentProjectionService.projectContent(strategy); |
||||
|
``` |
||||
|
|
||||
|
In the example above, `SomeOverlayComponent` component will placed at the **end** of `<body>` and a `ComponentRef` will be returned. Additionally, the given context will be applied, so `someOverlayProp` of the component will be set to `SOME_VALUE`. |
||||
|
|
||||
|
> You should keep the returned `ComponentRef` instance, as it is a reference to the projected component and you will need that reference to destroy the projected view and the component instance. |
||||
|
|
||||
|
### How to Project Components and Templates into a Container |
||||
|
|
||||
|
If you pass a `ComponentProjectionStrategy` or `TemplateProjectionStrategy` as the first parameter of `projectContent` method, and a `ViewContainerRef` as the second parameter of that strategy, the `ContentProjectionService` will project the component or template to the given container. If provided, it will also pass the component or the template a context. |
||||
|
|
||||
|
```js |
||||
|
const strategy = PROJECTION_STRATEGY.ProjectComponentToContainer( |
||||
|
SomeComponent, |
||||
|
viewContainerRefOfTarget, |
||||
|
{ someProp: "SOME_VALUE" } |
||||
|
); |
||||
|
|
||||
|
const componentRef = this.ContentProjectionService.projectContent(strategy); |
||||
|
``` |
||||
|
|
||||
|
In this example, the `viewContainerRefOfTarget`, which is a `ViewContainerRef` instance, will be cleared and `SomeComponent` component will placed inside it. Moreover, the given context will be applied, so `someProp` of the component will be set to `SOME_VALUE`. |
||||
|
|
||||
|
> You should keep the returned `ComponentRef` or `EmbeddedViewRef`, as they are a reference to the projected content and you will need them to destroy it when necessary. |
||||
|
|
||||
|
Please refer to [ProjectionStrategy](./Projection-Strategy.md) to see all available projection strategies and how you can build your own projection strategy. |
||||
|
|
||||
|
## API |
||||
|
|
||||
|
### projectContent |
||||
|
|
||||
|
```js |
||||
|
projectContent<T extends Type<any> | TemplateRef<any>>( |
||||
|
projectionStrategy: ProjectionStrategy<T>, |
||||
|
injector = this.injector, |
||||
|
): ComponentRef<C> | EmbeddedViewRef<C> |
||||
|
``` |
||||
|
|
||||
|
- `projectionStrategy` parameter is the primary focus here and is explained above. |
||||
|
- `injector` parameter is the `Injector` instance you can pass to the projected content. It is not used in `TemplateProjectionStrategy`. |
||||
|
|
||||
|
|
||||
|
## What's Next? |
||||
|
|
||||
|
- [TrackByService](./Track-By-Service.md) |
||||
@ -0,0 +1,117 @@ |
|||||
|
# ContextStrategy |
||||
|
|
||||
|
`ContextStrategy` is an abstract class exposed by @abp/ng.core package. There are three context strategies extending it: `ComponentContextStrategy`, `TemplateContextStrategy`, and `NoContextStrategy`. Implementing the same methods and properties, all of these strategies help you define how projected content will get their context. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## ComponentContextStrategy |
||||
|
|
||||
|
`ComponentContextStrategy` is a class that extends `ContextStrategy`. It lets you **pass context to a projected component**. |
||||
|
|
||||
|
|
||||
|
### constructor |
||||
|
|
||||
|
```js |
||||
|
constructor(public context: Partial<InferredInstanceOf<T>>) {} |
||||
|
``` |
||||
|
|
||||
|
- `T` refers to component type here, i.e. `Type<C>`. |
||||
|
- `InferredInstanceOf` is a utility type exposed by @abp/ng.core package. It infers component shape. |
||||
|
- `context` will be mapped to properties of the projected component. |
||||
|
|
||||
|
|
||||
|
### setContext |
||||
|
|
||||
|
```js |
||||
|
setContext(componentRef: ComponentRef<InferredInstanceOf<T>>): Partial<InferredInstanceOf<T>> |
||||
|
``` |
||||
|
|
||||
|
This method maps each prop of the context to the component property with the same name and calls change detection. It returns the context after mapping. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## TemplateContextStrategy |
||||
|
|
||||
|
`TemplateContextStrategy` is a class that extends `ContextStrategy`. It lets you **pass context to a projected template**. |
||||
|
|
||||
|
|
||||
|
### constructor |
||||
|
|
||||
|
```js |
||||
|
constructor(public context: Partial<InferredContextOf<T>>) {} |
||||
|
``` |
||||
|
|
||||
|
- `T` refers to template context type here, i.e. `TemplateRef<C>`. |
||||
|
- `InferredContextOf` is a utility type exposed by @abp/ng.core package. It infers context shape. |
||||
|
- `context` will be mapped to properties of the projected template. |
||||
|
|
||||
|
|
||||
|
### setContext |
||||
|
|
||||
|
```js |
||||
|
setContext(): Partial<InferredContextOf<T>> |
||||
|
``` |
||||
|
|
||||
|
This method does nothing and only returns the context, because template context is not mapped but passed in as parameter to `createEmbeddedView` method. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## NoContextStrategy |
||||
|
|
||||
|
`NoContextStrategy` is a class that extends `ContextStrategy`. It lets you **skip passing any context to projected content**. |
||||
|
|
||||
|
|
||||
|
### constructor |
||||
|
|
||||
|
```js |
||||
|
constructor() |
||||
|
``` |
||||
|
|
||||
|
Unlike other context strategies, `NoContextStrategy` contructor takes no parameters. |
||||
|
|
||||
|
|
||||
|
### setContext |
||||
|
|
||||
|
```js |
||||
|
setContext(): undefined |
||||
|
``` |
||||
|
|
||||
|
Since there is no context, this method gets no parameters and will return `undefined`. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Predefined Context Strategies |
||||
|
|
||||
|
Predefined context strategies are accessible via `CONTEXT_STRATEGY` constant. |
||||
|
|
||||
|
|
||||
|
### None |
||||
|
|
||||
|
```js |
||||
|
CONTEXT_STRATEGY.None() |
||||
|
``` |
||||
|
|
||||
|
This strategy will not pass any context to the projected content. |
||||
|
|
||||
|
|
||||
|
### Component |
||||
|
|
||||
|
```js |
||||
|
CONTEXT_STRATEGY.Component(context: Partial<InferredContextOf<T>>) |
||||
|
``` |
||||
|
|
||||
|
This strategy will help you pass the given context to the projected component. |
||||
|
|
||||
|
|
||||
|
### Template |
||||
|
|
||||
|
```js |
||||
|
CONTEXT_STRATEGY.Template(context: Partial<InferredContextOf<T>>) |
||||
|
``` |
||||
|
|
||||
|
This strategy will help you pass the given context to the projected template. |
||||
|
|
||||
|
|
||||
|
## See Also |
||||
|
|
||||
|
- [ProjectionStrategy](./Projection-Strategy.md) |
||||
@ -0,0 +1,200 @@ |
|||||
|
# ProjectionStrategy |
||||
|
|
||||
|
`ProjectionStrategy` is an abstract class exposed by @abp/ng.core package. There are three projection strategies extending it: `ComponentProjectionStrategy`, `RootComponentProjectionStrategy`, and `TemplateProjectionStrategy`. Implementing the same methods and properties, all of these strategies help you define how your content projection will work. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## ComponentProjectionStrategy |
||||
|
|
||||
|
`ComponentProjectionStrategy` is a class that extends `ProjectionStrategy`. It lets you **project a component into a container**. |
||||
|
|
||||
|
|
||||
|
### constructor |
||||
|
|
||||
|
```js |
||||
|
constructor( |
||||
|
component: T, |
||||
|
private containerStrategy: ContainerStrategy, |
||||
|
private contextStrategy?: ContextStrategy, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
- `component` is class of the component you would like to project. |
||||
|
- `containerStrategy` is the `ContainerStrategy` that will be used when projecting the component. |
||||
|
- `contextStrategy` is the `ContextStrategy` that will be used on the projected component. (_default: None_) |
||||
|
|
||||
|
Please refer to [ContainerStrategy](./Container-Strategy.md) and [ContextStrategy](./Context-Strategy.md) documentation for their usage. |
||||
|
|
||||
|
|
||||
|
### injectContent |
||||
|
|
||||
|
```js |
||||
|
injectContent(injector: Injector): ComponentRef<T> |
||||
|
``` |
||||
|
|
||||
|
This method prepares the container, resolves the component, sets its context, and projects it to the container. It returns a `ComponentRef` instance, which you should keep in order to clear projected components later on. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## RootComponentProjectionStrategy |
||||
|
|
||||
|
`RootComponentProjectionStrategy` is a class that extends `ProjectionStrategy`. It lets you **project a component into the document**, such as appending it to `<body>`. |
||||
|
|
||||
|
|
||||
|
### constructor |
||||
|
|
||||
|
```js |
||||
|
constructor( |
||||
|
component: T, |
||||
|
private contextStrategy?: ContextStrategy, |
||||
|
private domStrategy?: DomStrategy, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
- `component` is class of the component you would like to project. |
||||
|
- `contextStrategy` is the `ContextStrategy` that will be used on the projected component. (_default: None_) |
||||
|
- `domStrategy` is the `DomStrategy` that will be used when inserting component. (_default: AppendToBody_) |
||||
|
|
||||
|
Please refer to [ContextStrategy](./Context-Strategy.md) and [DomStrategy](./Dom-Strategy.md) documentation for their usage. |
||||
|
|
||||
|
|
||||
|
### injectContent |
||||
|
|
||||
|
```js |
||||
|
injectContent(injector: Injector): ComponentRef<T> |
||||
|
``` |
||||
|
|
||||
|
This method resolves the component, sets its context, and projects it to the document. It returns a `ComponentRef` instance, which you should keep in order to clear projected components later on. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## TemplateProjectionStrategy |
||||
|
|
||||
|
`TemplateProjectionStrategy` is a class that extends `ProjectionStrategy`. It lets you **project a template into a container**. |
||||
|
|
||||
|
|
||||
|
### constructor |
||||
|
|
||||
|
```js |
||||
|
constructor( |
||||
|
template: T, |
||||
|
private containerStrategy: ContainerStrategy, |
||||
|
private contextStrategy?: ContextStrategy, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
- `template` is `TemplateRef` you would like to project. |
||||
|
- `containerStrategy` is the `ContainerStrategy` that will be used when projecting the component. |
||||
|
- `contextStrategy` is the `ContextStrategy` that will be used on the projected component. (_default: None_) |
||||
|
|
||||
|
Please refer to [ContainerStrategy](./Container-Strategy.md) and [ContextStrategy](./Context-Strategy.md) documentation for their usage. |
||||
|
|
||||
|
|
||||
|
### injectContent |
||||
|
|
||||
|
```js |
||||
|
injectContent(): EmbeddedViewRef<T> |
||||
|
``` |
||||
|
|
||||
|
This method prepares the container, and projects the template together with the defined context to it. It returns an `EmbeddedViewRef`, which you should keep in order to clear projected templates later on. |
||||
|
|
||||
|
|
||||
|
|
||||
|
## Predefined Projection Strategies |
||||
|
|
||||
|
Predefined projection strategies are accessible via `PROJECTION_STRATEGY` constant. |
||||
|
|
||||
|
|
||||
|
### AppendComponentToBody |
||||
|
|
||||
|
```js |
||||
|
PROJECTION_STRATEGY.AppendComponentToBody( |
||||
|
component: T, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
Sets given context to the component and places it at the **end** of `<body>` tag in the document. |
||||
|
|
||||
|
|
||||
|
### AppendComponentToContainer |
||||
|
|
||||
|
```js |
||||
|
PROJECTION_STRATEGY.AppendComponentToContainer( |
||||
|
component: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
Sets given context to the component and places it at the **end** of the container. |
||||
|
|
||||
|
|
||||
|
### AppendTemplateToContainer |
||||
|
|
||||
|
```js |
||||
|
PROJECTION_STRATEGY.AppendTemplateToContainer( |
||||
|
templateRef: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
Sets given context to the template and places it at the **end** of the container. |
||||
|
|
||||
|
|
||||
|
### PrependComponentToContainer |
||||
|
|
||||
|
```js |
||||
|
PROJECTION_STRATEGY.PrependComponentToContainer( |
||||
|
component: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
Sets given context to the component and places it at the **beginning** of the container. |
||||
|
|
||||
|
|
||||
|
### PrependTemplateToContainer |
||||
|
|
||||
|
```js |
||||
|
PROJECTION_STRATEGY.PrependTemplateToContainer( |
||||
|
templateRef: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
Sets given context to the template and places it at the **beginning** of the container. |
||||
|
|
||||
|
|
||||
|
### ProjectComponentToContainer |
||||
|
|
||||
|
```js |
||||
|
PROJECTION_STRATEGY.ProjectComponentToContainer( |
||||
|
component: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
Clears the container, sets given context to the component, and places it **in the cleared** the container. |
||||
|
|
||||
|
|
||||
|
### ProjectTemplateToContainer |
||||
|
|
||||
|
```js |
||||
|
PROJECTION_STRATEGY.ProjectTemplateToContainer( |
||||
|
templateRef: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
Clears the container, sets given context to the template, and places it **in the cleared** the container. |
||||
|
|
||||
|
|
||||
|
## See Also |
||||
|
|
||||
|
- [DomInsertionService](./Dom-Insertion-Service.md) |
||||
@ -0,0 +1,4 @@ |
|||||
|
import { TemplateRef, Type } from '@angular/core'; |
||||
|
|
||||
|
export type InferredInstanceOf<T> = T extends Type<infer U> ? U : never; |
||||
|
export type InferredContextOf<T> = T extends TemplateRef<infer U> ? U : never; |
||||
@ -0,0 +1,14 @@ |
|||||
|
import { Injectable, Injector, TemplateRef, Type } from '@angular/core'; |
||||
|
import { ProjectionStrategy } from '../strategies/projection.strategy'; |
||||
|
|
||||
|
@Injectable({ providedIn: 'root' }) |
||||
|
export class ContentProjectionService { |
||||
|
constructor(private injector: Injector) {} |
||||
|
|
||||
|
projectContent<T extends Type<any> | TemplateRef<any>>( |
||||
|
projectionStrategy: ProjectionStrategy<T>, |
||||
|
injector = this.injector, |
||||
|
) { |
||||
|
return projectionStrategy.injectContent(injector); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
import { ViewContainerRef } from '@angular/core'; |
||||
|
|
||||
|
export abstract class ContainerStrategy { |
||||
|
constructor(public containerRef: ViewContainerRef) {} |
||||
|
|
||||
|
abstract getIndex(): number; |
||||
|
|
||||
|
prepare(): void {} |
||||
|
} |
||||
|
|
||||
|
export class ClearContainerStrategy extends ContainerStrategy { |
||||
|
getIndex(): number { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
prepare() { |
||||
|
this.containerRef.clear(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class InsertIntoContainerStrategy extends ContainerStrategy { |
||||
|
constructor(containerRef: ViewContainerRef, private index: number) { |
||||
|
super(containerRef); |
||||
|
} |
||||
|
|
||||
|
getIndex() { |
||||
|
return Math.min(Math.max(0, this.index), this.containerRef.length); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const CONTAINER_STRATEGY = { |
||||
|
Clear(containerRef: ViewContainerRef) { |
||||
|
return new ClearContainerStrategy(containerRef); |
||||
|
}, |
||||
|
Append(containerRef: ViewContainerRef) { |
||||
|
return new InsertIntoContainerStrategy(containerRef, containerRef.length); |
||||
|
}, |
||||
|
Prepend(containerRef: ViewContainerRef) { |
||||
|
return new InsertIntoContainerStrategy(containerRef, 0); |
||||
|
}, |
||||
|
Insert(containerRef: ViewContainerRef, index: number) { |
||||
|
return new InsertIntoContainerStrategy(containerRef, index); |
||||
|
}, |
||||
|
}; |
||||
@ -0,0 +1,47 @@ |
|||||
|
import { ComponentRef, TemplateRef, Type } from '@angular/core'; |
||||
|
import { InferredContextOf, InferredInstanceOf } from '../models'; |
||||
|
|
||||
|
export abstract class ContextStrategy<T = any> { |
||||
|
constructor(public context: Partial<ContextType<T>>) {} |
||||
|
|
||||
|
/* tslint:disable-next-line:no-unused-variable */ |
||||
|
setContext(componentRef?: ComponentRef<InferredInstanceOf<T>>): Partial<ContextType<T>> { |
||||
|
return this.context; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class NoContextStrategy< |
||||
|
T extends Type<any> | TemplateRef<any> = any |
||||
|
> extends ContextStrategy<T> { |
||||
|
constructor() { |
||||
|
super(undefined); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class ComponentContextStrategy<T extends Type<any> = any> extends ContextStrategy<T> { |
||||
|
setContext(componentRef: ComponentRef<InferredInstanceOf<T>>): Partial<InferredInstanceOf<T>> { |
||||
|
Object.keys(this.context).forEach(key => (componentRef.instance[key] = this.context[key])); |
||||
|
componentRef.changeDetectorRef.detectChanges(); |
||||
|
return this.context; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class TemplateContextStrategy<T extends TemplateRef<any> = any> extends ContextStrategy<T> { |
||||
|
setContext(): Partial<InferredContextOf<T>> { |
||||
|
return this.context; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const CONTEXT_STRATEGY = { |
||||
|
None<T extends Type<any> | TemplateRef<any> = any>() { |
||||
|
return new NoContextStrategy<T>(); |
||||
|
}, |
||||
|
Component<T extends Type<any> = any>(context: Partial<InferredInstanceOf<T>>) { |
||||
|
return new ComponentContextStrategy<T>(context); |
||||
|
}, |
||||
|
Template<T extends TemplateRef<any> = any>(context: Partial<InferredContextOf<T>>) { |
||||
|
return new TemplateContextStrategy<T>(context); |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
type ContextType<T> = T extends Type<infer U> | TemplateRef<infer U> ? U : never; |
||||
@ -1,5 +1,8 @@ |
|||||
|
export * from './container.strategy'; |
||||
export * from './content-security.strategy'; |
export * from './content-security.strategy'; |
||||
export * from './content.strategy'; |
export * from './content.strategy'; |
||||
|
export * from './context.strategy'; |
||||
export * from './cross-origin.strategy'; |
export * from './cross-origin.strategy'; |
||||
export * from './dom.strategy'; |
export * from './dom.strategy'; |
||||
export * from './loading.strategy'; |
export * from './loading.strategy'; |
||||
|
export * from './projection.strategy'; |
||||
|
|||||
@ -0,0 +1,176 @@ |
|||||
|
import { |
||||
|
ApplicationRef, |
||||
|
ComponentFactoryResolver, |
||||
|
ComponentRef, |
||||
|
EmbeddedViewRef, |
||||
|
Injector, |
||||
|
TemplateRef, |
||||
|
Type, |
||||
|
ViewContainerRef, |
||||
|
} from '@angular/core'; |
||||
|
import { InferredContextOf, InferredInstanceOf } from '../models/utility'; |
||||
|
import { ContainerStrategy, CONTAINER_STRATEGY } from './container.strategy'; |
||||
|
import { ContextStrategy, CONTEXT_STRATEGY } 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<InferredInstanceOf<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<InferredInstanceOf<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( |
||||
|
templateRef: T, |
||||
|
private containerStrategy: ContainerStrategy, |
||||
|
private contextStrategy = CONTEXT_STRATEGY.None(), |
||||
|
) { |
||||
|
super(templateRef); |
||||
|
} |
||||
|
|
||||
|
injectContent() { |
||||
|
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, context?: InferredInstanceOf<T>) { |
||||
|
return new RootComponentProjectionStrategy<T>( |
||||
|
component, |
||||
|
context && CONTEXT_STRATEGY.Component(context), |
||||
|
); |
||||
|
}, |
||||
|
AppendComponentToContainer<T extends Type<unknown>>( |
||||
|
component: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
context?: InferredInstanceOf<T>, |
||||
|
) { |
||||
|
return new ComponentProjectionStrategy<T>( |
||||
|
component, |
||||
|
CONTAINER_STRATEGY.Append(containerRef), |
||||
|
context && CONTEXT_STRATEGY.Component(context), |
||||
|
); |
||||
|
}, |
||||
|
AppendTemplateToContainer<T extends TemplateRef<unknown>>( |
||||
|
templateRef: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
context?: InferredContextOf<T>, |
||||
|
) { |
||||
|
return new TemplateProjectionStrategy<T>( |
||||
|
templateRef, |
||||
|
CONTAINER_STRATEGY.Append(containerRef), |
||||
|
context && CONTEXT_STRATEGY.Template(context), |
||||
|
); |
||||
|
}, |
||||
|
PrependComponentToContainer<T extends Type<unknown>>( |
||||
|
component: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
context?: InferredInstanceOf<T>, |
||||
|
) { |
||||
|
return new ComponentProjectionStrategy<T>( |
||||
|
component, |
||||
|
CONTAINER_STRATEGY.Prepend(containerRef), |
||||
|
context && CONTEXT_STRATEGY.Component(context), |
||||
|
); |
||||
|
}, |
||||
|
PrependTemplateToContainer<T extends TemplateRef<unknown>>( |
||||
|
templateRef: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
context?: InferredContextOf<T>, |
||||
|
) { |
||||
|
return new TemplateProjectionStrategy<T>( |
||||
|
templateRef, |
||||
|
CONTAINER_STRATEGY.Prepend(containerRef), |
||||
|
context && CONTEXT_STRATEGY.Template(context), |
||||
|
); |
||||
|
}, |
||||
|
ProjectComponentToContainer<T extends Type<unknown>>( |
||||
|
component: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
context?: InferredInstanceOf<T>, |
||||
|
) { |
||||
|
return new ComponentProjectionStrategy<T>( |
||||
|
component, |
||||
|
CONTAINER_STRATEGY.Clear(containerRef), |
||||
|
context && CONTEXT_STRATEGY.Component(context), |
||||
|
); |
||||
|
}, |
||||
|
ProjectTemplateToContainer<T extends TemplateRef<unknown>>( |
||||
|
templateRef: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
context?: InferredContextOf<T>, |
||||
|
) { |
||||
|
return new TemplateProjectionStrategy<T>( |
||||
|
templateRef, |
||||
|
CONTAINER_STRATEGY.Clear(containerRef), |
||||
|
context && CONTEXT_STRATEGY.Template(context), |
||||
|
); |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
type ComponentRefOrEmbeddedViewRef<T> = T extends Type<infer U> |
||||
|
? ComponentRef<U> |
||||
|
: T extends TemplateRef<infer C> |
||||
|
? EmbeddedViewRef<C> |
||||
|
: never; |
||||
@ -0,0 +1,80 @@ |
|||||
|
import { ViewContainerRef } from '@angular/core'; |
||||
|
import { |
||||
|
ClearContainerStrategy, |
||||
|
CONTAINER_STRATEGY, |
||||
|
InsertIntoContainerStrategy, |
||||
|
} from '../strategies'; |
||||
|
|
||||
|
describe('ClearContainerStrategy', () => { |
||||
|
const containerRef = ({ |
||||
|
clear: jest.fn(), |
||||
|
length: 7, |
||||
|
} as any) as ViewContainerRef; |
||||
|
|
||||
|
describe('#getIndex', () => { |
||||
|
it('should return 0', () => { |
||||
|
const strategy = new ClearContainerStrategy(containerRef); |
||||
|
expect(strategy.getIndex()).toBe(0); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('#prepare', () => { |
||||
|
it('should call clear method of containerRef once', () => { |
||||
|
const strategy = new ClearContainerStrategy(containerRef); |
||||
|
strategy.prepare(); |
||||
|
expect(strategy.getIndex()).toBe(0); |
||||
|
expect(containerRef.clear).toHaveBeenCalledTimes(1); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('InsertIntoContainerStrategy', () => { |
||||
|
const containerRef = ({ |
||||
|
clear: jest.fn(), |
||||
|
length: 7, |
||||
|
} as any) as ViewContainerRef; |
||||
|
|
||||
|
describe('#getIndex', () => { |
||||
|
test.each` |
||||
|
index | expected |
||||
|
${0} | ${0} |
||||
|
${4} | ${4} |
||||
|
${9} | ${7} |
||||
|
${-1} | ${0} |
||||
|
${Infinity} | ${7} |
||||
|
`(
|
||||
|
'should return $expected when index is given $index', |
||||
|
({ index, expected }: { index: number; expected: number }) => { |
||||
|
const strategy = new InsertIntoContainerStrategy(containerRef, index); |
||||
|
expect(strategy.getIndex()).toBe(expected); |
||||
|
}, |
||||
|
); |
||||
|
}); |
||||
|
|
||||
|
describe('#prepare', () => { |
||||
|
it('should not call clear method of containerRef', () => { |
||||
|
const strategy = new InsertIntoContainerStrategy(containerRef, 0); |
||||
|
strategy.prepare(); |
||||
|
expect(containerRef.clear).not.toHaveBeenCalled(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('CONTAINER_STRATEGY', () => { |
||||
|
const containerRef = ({ |
||||
|
clear: jest.fn(), |
||||
|
length: 7, |
||||
|
} as any) as ViewContainerRef; |
||||
|
|
||||
|
test.each` |
||||
|
name | Strategy | index |
||||
|
${'Clear'} | ${ClearContainerStrategy} | ${undefined} |
||||
|
${'Append'} | ${InsertIntoContainerStrategy} | ${containerRef.length} |
||||
|
${'Prepend'} | ${InsertIntoContainerStrategy} | ${0} |
||||
|
${'Insert'} | ${InsertIntoContainerStrategy} | ${4} |
||||
|
`('should successfully map $name to $Strategy.name', ({ name, Strategy, index }) => {
|
||||
|
expect(CONTAINER_STRATEGY[name](containerRef, index)).toEqual( |
||||
|
new Strategy(containerRef, index), |
||||
|
); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,38 @@ |
|||||
|
import { Component, ComponentRef, NgModule } from '@angular/core'; |
||||
|
import { createServiceFactory, SpectatorService } from '@ngneat/spectator'; |
||||
|
import { ContentProjectionService } from '../services'; |
||||
|
import { PROJECTION_STRATEGY } from '../strategies'; |
||||
|
|
||||
|
describe('ContentProjectionService', () => { |
||||
|
@Component({ template: '<div class="foo">bar</div>' }) |
||||
|
class TestComponent {} |
||||
|
|
||||
|
// createServiceFactory does not accept entryComponents directly
|
||||
|
@NgModule({ |
||||
|
declarations: [TestComponent], |
||||
|
entryComponents: [TestComponent], |
||||
|
}) |
||||
|
class TestModule {} |
||||
|
|
||||
|
let componentRef: ComponentRef<TestComponent>; |
||||
|
let spectator: SpectatorService<ContentProjectionService>; |
||||
|
const createService = createServiceFactory({ |
||||
|
service: ContentProjectionService, |
||||
|
imports: [TestModule], |
||||
|
}); |
||||
|
|
||||
|
beforeEach(() => (spectator = createService())); |
||||
|
|
||||
|
afterEach(() => componentRef.destroy()); |
||||
|
|
||||
|
describe('#projectContent', () => { |
||||
|
it('should call injectContent of given projectionStrategy and return what it returns', () => { |
||||
|
const strategy = PROJECTION_STRATEGY.AppendComponentToBody(TestComponent); |
||||
|
componentRef = spectator.service.projectContent(strategy); |
||||
|
const foo = document.querySelector('body > ng-component > div.foo'); |
||||
|
|
||||
|
expect(componentRef).toBeInstanceOf(ComponentRef); |
||||
|
expect(foo.textContent).toBe('bar'); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,79 @@ |
|||||
|
import { ComponentRef } from '@angular/core'; |
||||
|
import { |
||||
|
ComponentContextStrategy, |
||||
|
CONTEXT_STRATEGY, |
||||
|
NoContextStrategy, |
||||
|
TemplateContextStrategy, |
||||
|
} from '../strategies'; |
||||
|
import { uuid } from '../utils'; |
||||
|
|
||||
|
describe('ComponentContextStrategy', () => { |
||||
|
describe('#setContext', () => { |
||||
|
let componentRef: ComponentRef<any>; |
||||
|
|
||||
|
beforeEach( |
||||
|
() => |
||||
|
(componentRef = { |
||||
|
instance: { |
||||
|
x: '', |
||||
|
y: '', |
||||
|
z: '', |
||||
|
}, |
||||
|
changeDetectorRef: { |
||||
|
detectChanges: jest.fn(), |
||||
|
}, |
||||
|
} as any), |
||||
|
); |
||||
|
|
||||
|
test.each` |
||||
|
props | values |
||||
|
${['x']} | ${[uuid()]} |
||||
|
${['x', 'y']} | ${[uuid(), uuid()]} |
||||
|
${['x', 'y', 'z']} | ${[uuid(), uuid(), uuid()]} |
||||
|
`(
|
||||
|
'should set $props as $values and call detectChanges once', |
||||
|
({ props, values }: { props: string[]; values: string[] }) => { |
||||
|
const context = {}; |
||||
|
props.forEach((prop, i) => { |
||||
|
context[prop] = values[i]; |
||||
|
}); |
||||
|
|
||||
|
const strategy = new ComponentContextStrategy(context); |
||||
|
strategy.setContext(componentRef); |
||||
|
|
||||
|
expect(props.every(prop => componentRef.instance[prop] === context[prop])).toBe(true); |
||||
|
expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalledTimes(1); |
||||
|
}, |
||||
|
); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('NoContextStrategy', () => { |
||||
|
describe('#setContext', () => { |
||||
|
it('should return undefined', () => { |
||||
|
const strategy = new NoContextStrategy(); |
||||
|
expect(strategy.setContext(null)).toBeUndefined(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('TemplateContextStrategy', () => { |
||||
|
describe('#setContext', () => { |
||||
|
it('should return context', () => { |
||||
|
const context = { x: uuid() }; |
||||
|
const strategy = new TemplateContextStrategy(context); |
||||
|
expect(strategy.setContext()).toEqual(context); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('CONTEXT_STRATEGY', () => { |
||||
|
test.each` |
||||
|
name | Strategy |
||||
|
${'Component'} | ${ComponentContextStrategy} |
||||
|
${'None'} | ${NoContextStrategy} |
||||
|
${'Template'} | ${TemplateContextStrategy} |
||||
|
`('should successfully map $name to $Strategy.name', ({ name, Strategy }) => {
|
||||
|
expect(CONTEXT_STRATEGY[name](undefined)).toEqual(new Strategy(undefined)); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,276 @@ |
|||||
|
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.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.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; |
||||
|
let context: any; |
||||
|
|
||||
|
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 and $contextStrategy.name context strategy', |
||||
|
({ name, Strategy, containerStrategy }) => { |
||||
|
expect(PROJECTION_STRATEGY[name](content, containerRef, context)).toEqual( |
||||
|
new Strategy(content, containerStrategy(containerRef), CONTEXT_STRATEGY.None()), |
||||
|
); |
||||
|
}, |
||||
|
); |
||||
|
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, context)).toEqual( |
||||
|
new Strategy(content, CONTEXT_STRATEGY.None(), domStrategy()), |
||||
|
); |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
test.each` |
||||
|
name | Strategy | containerStrategy | contextStrategy |
||||
|
${'AppendComponentToContainer'} | ${ComponentProjectionStrategy} | ${CONTAINER_STRATEGY.Append} | ${CONTEXT_STRATEGY.Component} |
||||
|
${'AppendTemplateToContainer'} | ${TemplateProjectionStrategy} | ${CONTAINER_STRATEGY.Append} | ${CONTEXT_STRATEGY.Template} |
||||
|
${'PrependComponentToContainer'} | ${ComponentProjectionStrategy} | ${CONTAINER_STRATEGY.Prepend} | ${CONTEXT_STRATEGY.Component} |
||||
|
${'PrependTemplateToContainer'} | ${TemplateProjectionStrategy} | ${CONTAINER_STRATEGY.Prepend} | ${CONTEXT_STRATEGY.Template} |
||||
|
${'ProjectComponentToContainer'} | ${ComponentProjectionStrategy} | ${CONTAINER_STRATEGY.Clear} | ${CONTEXT_STRATEGY.Component} |
||||
|
${'ProjectTemplateToContainer'} | ${TemplateProjectionStrategy} | ${CONTAINER_STRATEGY.Clear} | ${CONTEXT_STRATEGY.Template} |
||||
|
`(
|
||||
|
'should successfully map $name to $Strategy.name with $containerStrategy.name container strategy and $contextStrategy.name context strategy', |
||||
|
({ name, Strategy, containerStrategy, contextStrategy }) => { |
||||
|
context = { x: true }; |
||||
|
expect(PROJECTION_STRATEGY[name](content, containerRef, context)).toEqual( |
||||
|
new Strategy(content, containerStrategy(containerRef), contextStrategy(context)), |
||||
|
); |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
test.each` |
||||
|
name | Strategy | contextStrategy | domStrategy |
||||
|
${'AppendComponentToBody'} | ${RootComponentProjectionStrategy} | ${CONTEXT_STRATEGY.Component} | ${DOM_STRATEGY.AppendToBody} |
||||
|
`(
|
||||
|
'should successfully map $name to $Strategy.name with $contextStrategy.name context strategy and $domStrategy.name dom strategy', |
||||
|
({ name, Strategy, domStrategy, contextStrategy }) => { |
||||
|
context = { x: true }; |
||||
|
expect(PROJECTION_STRATEGY[name](content, context)).toEqual( |
||||
|
new Strategy(content, contextStrategy(context), domStrategy()), |
||||
|
); |
||||
|
}, |
||||
|
); |
||||
|
}); |
||||
Loading…
Reference in new issue