mirror of https://github.com/abpframework/abp.git
8 changed files with 143 additions and 70 deletions
@ -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,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,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'); |
|||
}); |
|||
}); |
|||
}); |
|||
Loading…
Reference in new issue