Skip to content

Commit

Permalink
feat: add domService for DOM manipulations
Browse files Browse the repository at this point in the history
  • Loading branch information
SGrueber committed Apr 26, 2023
1 parent 35adb46 commit 3232241
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 0 deletions.
10 changes: 10 additions & 0 deletions docs/guides/angular-component-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,16 @@ UPDATE src/app/shared/components/common/accordion/accordion.component.ts (425 by

To reduce the number of ChangeDetection computation cycles, all components should have their `Component` decorator property `changeDetection` set to `ChangeDetectionStrategy.OnPush`.

## DOM Manipulations

When using Angular, avoid to access or change the DOM directly because this may lead to several issues:

- If you use direct DOM accessors like window, document etc., it might not refer to something relevant on the server-side code.
- It might open up your app as an easy target for the XSS injection attack or other security issues.
- The angular synchronization of components amd views are bypassed and this might lead to unwanted side effects.

However, if you need to manipulate the DOM use the multiple DOM manipulation methods of the Angular Renderer2 API or the pwa [DomService](../../src/app/core/utils/dom/dom.service.ts) or other angular techniques.

## Split Components When Necessary

Consider splitting one into multiple components when:
Expand Down
16 changes: 16 additions & 0 deletions src/app/core/utils/dom/dom.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { DomService } from './dom.service';

describe('Dom Service', () => {
let domService: DomService;

beforeEach(() => {
TestBed.configureTestingModule({});
domService = TestBed.inject(DomService);
});

it('should be created', () => {
expect(domService).toBeTruthy();
});
});
141 changes: 141 additions & 0 deletions src/app/core/utils/dom/dom.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable, Renderer2, RendererFactory2, RendererStyleFlags2 } from '@angular/core';

/**
* Utility Service for DOM Manipulations using Angular Renderer2. It allows you to manipulate DOM elements without accessing the DOM directly. While working with SSR DOM manipulations are not possible on server, this is also handled properly.
*
**/

@Injectable({ providedIn: 'root' })
export class DomService {
private renderer: Renderer2;
constructor(private rendererFactory: RendererFactory2, @Inject(DOCUMENT) private document: Document) {
// Get an instance of Renderer2
this.renderer = this.rendererFactory.createRenderer(undefined, undefined);
}

/**
* Appends a child to a given parent node.
*
* @param parent The parent element.
* @param el The child element.
*/
appendChild(parent: HTMLElement, el: HTMLElement): void {
return this.renderer.appendChild(parent, el);
}

/**
* Gets a (unique) element by its id
*
* @param id The element id.
* @param el The html element.
*/
getElementById(id: string): HTMLElement {
return this.document.getElementById(id);
}

/**
* Creates an HTML element specified by tagName and if a parent is given appends it to a parent element.
*
* @param tagName The type of element to be created, e.g. 'div'
* @param parent The parent element.
* @returns The created element.
*/
createElement<T extends HTMLElement>(tagName: string, parent?: HTMLElement): T {
const el: T = this.renderer.createElement(tagName);
if (parent) {
this.appendChild(parent, el);
}
return el;
}

/**
* Returns the first element within the document that matches the specified selector. If no matches are found a new element is created.
*
* @param selector A valid CSS selector string.
* @param tagName The type of element to be created if no element is found.
* @param parent The parent element for the element to be created.
* @returns Either the found or created element.
*/
getOrCreateElement<T extends HTMLElement>(selector: string, tagName: string, parent?: HTMLElement): T {
const el: T = this.document.querySelector(selector);
return el ? el : this.createElement<T>(tagName, parent);
}

/**
* Sets an attribute value for an element in the DOM.
*
* @param el The element.
* @param attributeName The attribute name.
* @param value The value to be set.
*/
setAttribute(el: HTMLElement, attributeName: string, value: string): void {
this.renderer.setAttribute(el, attributeName, value);
}

/**
* Sets an attribute value for the first element in the DOM that matches the specified selector. If no matches are found nothing is done.
*
* @param selector A valid CSS selector string.
* @param attributeName The attribute name.
* @param value The value to be set.
*/
setAttributeForSelector(selector: string, attributeName: string, value: string) {
const el: HTMLElement = this.document.querySelector(selector);
if (el) {
this.setAttribute(el, attributeName, value);
}
}

/**
* Sets the value of a property of an element in the DOM.
*
* @param el The element.
* @param name The property name.
* @param value The value to be set.
*/
setProperty(el: HTMLElement, name: string, value: string | boolean): void {
return this.renderer.setProperty(el, name, value);
}

// not-dead-code
/**
* Sets the value of a css variable.
* ToDo: remove the notDeadCode comment as soon as setCssVariable method is used
*
* @param variableName The name of the variable (without prefix '--').
* @param value The value to be set.
*/
setCssVariable(variableName: string, value: string): void {
this.renderer.setStyle(
this.document.documentElement,
`--${variableName.toLowerCase()}`,
value.toString(),
RendererStyleFlags2.DashCase
);
}

/**
* Adds a class to an element in the DOM.
*
* @param el The element.
* @param cssClass The class name.
*/
addClass(el: HTMLElement, cssClass: string): void {
if (el) {
this.renderer.addClass(el, cssClass);
}
}

/**
* Removes a class from an element in the DOM.
*
* @param el The element.
* @param cssClass The class name.
*/
removeClass(el: HTMLElement, cssClass: string) {
if (el) {
this.renderer.removeClass(el, cssClass);
}
}
}

0 comments on commit 3232241

Please sign in to comment.