refactor: make content projection a separate service

pull/3544/head
Arman Ozak 6 years ago
parent 4df815ce9c
commit af51255d3d

@ -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)

@ -1,4 +1,4 @@
# How to Insert Scripts and Styles
# Dom Insertion (of Scripts and Styles)
You can use the `DomInsertionService` in @abp/ng.core package in order to insert scripts and styles in an easy and explicit way.
@ -71,45 +71,17 @@ In the example above, `<style>body {margin: 0;}</style>` element will place at t
Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy.
### How to Project Components & Templates
If you pass a `ProjectionStrategy` as the first parameter of `projectContent` method, the `DomInsertionService` will resolve the projected component or template and place it at the designated target, such as containers or document body. If provided, it will also pass the component or the template a context.
```js
const componentRef = this.domInsertionService.projectContent(
PROJECTION_STRATEGY.AppendComponentToBody(SomeOverlayComponent)
);
```
In the example above, `SomeOverlayComponent` component will placed at the **end** of `<body>` and a `ComponentRef` will be returned.
> 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.
```js
const componentRef = this.domInsertionService.projectContent(
PROJECTION_STRATEGY.ProjectComponentToContainer(
SomeOverlayComponent,
viewContainerRefOfTarget,
{ someProp: "SOME_VALUE" }
)
);
```
In this example, the `viewContainerRefOfTarget`, which is a `ViewContainerRef` instance, will be cleared and `SomeOverlayComponent` component will placed inside it. Moreover, the given context will be applied, so `someProp` of the component will be set to `SOME_VALUE`.
Please refer to [ProjectionStrategy](./Projection-Strategy.md) to see all available projection strategies and how you can build your own projection strategy.
## API
### insertContent
```js
injectContent(injector: Injector): ComponentRef<C> | EmbeddedViewRef<C>
insertContent(contentStrategy: ContentStrategy): void
```
`injector` parameter is the `Injector` instance you can pass to the projected content. It is not used in `TemplateProjectionStrategy`.
- `contentStrategy` parameter is the primary focus here and is explained above.
## What's Next?
- [TrackByService](./Track-By-Service.md)
- [ContentProjectionService](./Content-Projection-Service.md)

@ -353,6 +353,10 @@
"text": "DomInsertionService",
"path": "UI/Angular/Dom-Insertion-Service.md"
},
{
"text": "ContentProjectionService",
"path": "UI/Angular/Content-Projection-Service.md"
},
{
"text": "TrackByService",
"path": "UI/Angular/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);
}
}

@ -1,6 +1,5 @@
import { Injectable, Injector, TemplateRef, Type } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { ContentStrategy } from '../strategies/content.strategy';
import { ProjectionStrategy } from '../strategies/projection.strategy';
import { generateHash } from '../utils';
@Injectable({ providedIn: 'root' })
@ -17,11 +16,4 @@ export class DomInsertionService {
contentStrategy.insertElement();
this.inserted.add(hash);
}
projectContent<T extends Type<any> | TemplateRef<any>>(
projectionStrategy: ProjectionStrategy<T>,
injector = this.injector,
) {
return projectionStrategy.injectContent(injector);
}
}

@ -1,6 +1,7 @@
export * from './application-configuration.service';
export * from './auth.service';
export * from './config-state.service';
export * from './content-projection.service';
export * from './dom-insertion.service';
export * from './lazy-load.service';
export * from './localization.service';

@ -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');
});
});
});

@ -1,25 +1,11 @@
import { Component, ComponentRef, NgModule } from '@angular/core';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator';
import { DomInsertionService } from '../services';
import { CONTENT_STRATEGY, PROJECTION_STRATEGY } from '../strategies';
import { CONTENT_STRATEGY } from '../strategies';
describe('DomInsertionService', () => {
@Component({ template: '<div class="foo">bar</div>' })
class TestComponent {}
// createServiceFactory does not accept entryComponents directly
@NgModule({
declarations: [TestComponent],
entryComponents: [TestComponent],
})
class TestModule {}
let spectator: SpectatorService<DomInsertionService>;
const createService = createServiceFactory({
service: DomInsertionService,
imports: [TestModule],
});
let styleElements: NodeListOf<HTMLStyleElement>;
let spectator: SpectatorService<DomInsertionService>;
const createService = createServiceFactory(DomInsertionService);
beforeEach(() => (spectator = createService()));
@ -56,16 +42,4 @@ describe('DomInsertionService', () => {
expect(spectator.service.inserted.has(1437348290)).toBe(true);
});
});
describe('#projectContent', () => {
it('should call injectContent of given projectionStrategy and return what it returns', () => {
const strategy = PROJECTION_STRATEGY.AppendComponentToBody(TestComponent);
const componentRef = spectator.service.projectContent(strategy);
const foo = document.querySelector('body > ng-component > div.foo');
expect(componentRef).toBeInstanceOf(ComponentRef);
expect(foo.textContent).toBe('bar');
componentRef.destroy();
});
});
});

Loading…
Cancel
Save