Merge pull request #3586 from abpframework/feat/3585

Added a Method for Removing Content to DomInsertionService
pull/3602/head
Mehmet Erim 5 years ago committed by GitHub
commit 6e97836623
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -35,17 +35,19 @@ class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
this.domInsertionService.insertContent(
const scriptElement = this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendScriptToBody('alert()')
);
}
}
```
In the example above, `<script>alert()</script>` element will place at the **end** of `<body>`.
In the example above, `<script>alert()</script>` element will place at the **end** of `<body>` and `scriptElement` will be an `HTMLScriptElement`.
Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy.
> Important Note: `DomInsertionService` does not insert the same content twice. In order to add a content again, you first should remove the old content using `removeContent` method.
### How to Insert Styles
If you pass a `StyleContentStrategy` instance as the first parameter of `insertContent` method, the `DomInsertionService` will create a `<style>` element with given `content` and place it in the designated DOM position.
@ -60,27 +62,68 @@ class DemoComponent {
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
this.domInsertionService.insertContent(
const styleElement = this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}')
);
}
}
```
In the example above, `<style>body {margin: 0;}</style>` element will place at the **end** of `<head>`.
In the example above, `<style>body {margin: 0;}</style>` element will place at the **end** of `<head>` and `styleElement` will be an `HTMLStyleElement`.
Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy.
> Important Note: `DomInsertionService` does not insert the same content twice. In order to add a content again, you first should remove the old content using `removeContent` method.
### How to Remove Inserted Scripts & Styles
If you pass the inserted `HTMLScriptElement` or `HTMLStyleElement` element as the first parameter of `removeContent` method, the `DomInsertionService` will remove the given element.
```js
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core';
@Component({
/* class metadata here */
})
class DemoComponent {
private styleElement: HTMLStyleElement;
constructor(private domInsertionService: DomInsertionService) {}
ngOnInit() {
this.styleElement = this.domInsertionService.insertContent(
CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}')
);
}
ngOnDestroy() {
this.domInsertionService.removeContent(this.styleElement);
}
}
```
In the example above, `<style>body {margin: 0;}</style>` element **will be removed** from `<head>` when the component is destroyed.
## API
### insertContent
```js
insertContent(contentStrategy: ContentStrategy): void
insertContent<T extends HTMLScriptElement | HTMLStyleElement>(
contentStrategy: ContentStrategy<T>,
): T
```
- `contentStrategy` parameter is the primary focus here and is explained above.
- returns `HTMLScriptElement` or `HTMLStyleElement` based on given strategy.
### removeContent
```js
removeContent(element: HTMLScriptElement | HTMLStyleElement): void
```
- `element` parameter is the inserted `HTMLScriptElement` or `HTMLStyleElement` element, which was returned by `insertContent` method.
## What's Next?

@ -6,12 +6,23 @@ import { generateHash } from '../utils';
export class DomInsertionService {
readonly inserted = new Set<number>();
insertContent(contentStrategy: ContentStrategy) {
insertContent<T extends HTMLScriptElement | HTMLStyleElement>(
contentStrategy: ContentStrategy<T>,
): T {
const hash = generateHash(contentStrategy.content);
if (this.inserted.has(hash)) return;
contentStrategy.insertElement();
const element = contentStrategy.insertElement();
this.inserted.add(hash);
return element;
}
removeContent(element: HTMLScriptElement | HTMLStyleElement) {
const hash = generateHash(element.textContent);
this.inserted.delete(hash);
element.parentNode.removeChild(element);
}
}

@ -10,11 +10,13 @@ export abstract class ContentStrategy<T extends HTMLScriptElement | HTMLStyleEle
abstract createElement(): T;
insertElement() {
insertElement(): T {
const element = this.createElement();
this.contentSecurityStrategy.applyCSP(element);
this.domStrategy.insertElement(element);
return element;
}
}

@ -1,9 +1,9 @@
import {
CONTENT_SECURITY_STRATEGY,
CONTENT_STRATEGY,
StyleContentStrategy,
ScriptContentStrategy,
DOM_STRATEGY,
CONTENT_SECURITY_STRATEGY,
ScriptContentStrategy,
StyleContentStrategy,
} from '../strategies';
import { uuid } from '../utils';
@ -26,8 +26,8 @@ describe('StyleContentStrategy', () => {
domStrategy.insertElement = jest.fn((el: HTMLScriptElement) => {}) as any;
const strategy = new StyleContentStrategy('', domStrategy, contentSecurityStrategy);
const element = strategy.createElement();
strategy.insertElement();
strategy.createElement();
const element = strategy.insertElement();
expect(contentSecurityStrategy.applyCSP).toHaveBeenCalledWith(element);
expect(domStrategy.insertElement).toHaveBeenCalledWith(element);

@ -9,7 +9,7 @@ describe('DomInsertionService', () => {
beforeEach(() => (spectator = createService()));
afterEach(() => styleElements.forEach(element => element.remove()));
afterEach(() => (document.head.innerHTML = ''));
describe('#insertContent', () => {
it('should be able to insert given content', () => {
@ -19,6 +19,11 @@ describe('DomInsertionService', () => {
expect(styleElements[0].textContent).toBe('.test {}');
});
it('should set a hash for the inserted content', () => {
spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead('.test {}'));
expect(spectator.service.inserted.has(1437348290)).toBe(true);
});
it('should insert only once', () => {
expect(spectator.service.inserted.has(1437348290)).toBe(false);
@ -37,9 +42,25 @@ describe('DomInsertionService', () => {
expect(spectator.service.inserted.has(1437348290)).toBe(true);
});
it('should be able to insert given content', () => {
spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead('.test {}'));
it('should return inserted element', () => {
const element = spectator.service.insertContent(
CONTENT_STRATEGY.AppendStyleToHead('.test {}'),
);
expect(element.tagName).toBe('STYLE');
});
});
describe('#removeContent', () => {
it('should remove inserted element and the hash for the content', () => {
expect(document.head.querySelector('style')).toBeNull();
const element = spectator.service.insertContent(
CONTENT_STRATEGY.AppendStyleToHead('.test {}'),
);
expect(spectator.service.inserted.has(1437348290)).toBe(true);
spectator.service.removeContent(element);
expect(spectator.service.inserted.has(1437348290)).toBe(false);
expect(document.head.querySelector('style')).toBeNull();
});
});
});

Loading…
Cancel
Save