Skip to content

Instantly share code, notes, and snippets.

@HamedFathi
Last active November 14, 2023 12:10
Show Gist options
  • Save HamedFathi/f083fdd12a0c3e4b9df266576f6d4b44 to your computer and use it in GitHub Desktop.
Save HamedFathi/f083fdd12a0c3e4b9df266576f6d4b44 to your computer and use it in GitHub Desktop.
// Motivation: https://github.com/angular/angular/issues/18877

import { Directive, ElementRef, AfterViewInit } from '@angular/core';

@Directive({
  standalone: true,
  selector: '[remove-host]'
})
export class RemoveHostDirective implements AfterViewInit {

  constructor(private el: ElementRef) { }

  ngAfterViewInit() {
    const host = this.el.nativeElement;
    const parent = host.parentNode;

    if (parent) {
      const fragment = document.createDocumentFragment();

      while (host.firstChild) {
        fragment.appendChild(host.firstChild);
      }

      parent.insertBefore(fragment, host);

      parent.removeChild(host);
    }
  }
}

This Angular directive designed to remove the host element of a component from the DOM while keeping its children.

import { Component } from '@angular/core';

@Component({
  template: `
      <h1>Hello World!</h1>
      <p>This is content from the ExampleComponent.</p>
  `,
  // Include the directive
  hostDirectives: [RemoveHostDirective]
})
export class ExampleComponent {}

So, The host div element which will create by Angular will remove and you have just h1 and p without any wrapper around in the DOM.


// Motivation: https://vuejs.org/guide/components/attrs.html

import { Directive, ElementRef, Renderer2, OnInit } from '@angular/core';

@Directive({
  standalone: true,
  selector: '[attrs]',
})
export class AttrsDirective implements OnInit {
  constructor(private el: ElementRef, private renderer: Renderer2) { }

  ngOnInit() {
    const originalHostElement = this.el.nativeElement;
    const newHostElements = originalHostElement.querySelectorAll('[attrs]');

    if (!newHostElements.length) return;

    newHostElements.forEach((newHostElement: any) => {
      for (let i = 0; i < originalHostElement.attributes.length; i++) {
        const attribute = originalHostElement.attributes[i];
        if (!attribute.name.startsWith('_ng')) {
          const existingAttrValue = newHostElement.getAttribute(attribute.name);

          if (existingAttrValue) {
            const newValue = `${existingAttrValue} ${attribute.value}`;
            this.renderer.setAttribute(
              newHostElement,
              attribute.name,
              newValue.trim()
            );
          } else {
            this.renderer.setAttribute(
              newHostElement,
              attribute.name,
              attribute.value
            );
          }
        }
      }
    });

    for (let i = originalHostElement.attributes.length - 1; i >= 0; i--) {
      const attribute = originalHostElement.attributes[i];
      if (!attribute.name.startsWith('_ng')) {
        this.renderer.removeAttribute(originalHostElement, attribute.name);
      }
    }
  }
}

This Angular directive, AttrsDirective, is designed to transfer attributes from a host element to its child elements.

For example:

import { Component } from '@angular/core';
import { AttrsDirective } from './attrs.directive'; // assuming both are in the same directory

@Component({
  selector: 'app-bootstrap-badge',
  template: `
    <button type="button" class="btn btn-primary">
      Notifications <span class="badge" attrs>4</span>
    </button>
  `,
  styleUrls: ['node_modules/bootstrap/dist/css/bootstrap.min.css'],
  hostDirectives: [AttrsDirective]
})
export class BootstrapCardComponent { }

Now you can use like

import { Component } from '@angular/core';

@Component({
  selector: 'app-parent',
  template: `
    <app-bootstrap-badge class="text-bg-secondary"></app-bootstrap-badge>
  `
})
export class ParentComponent { }

So, text-bg-secondary instead of host element will transfer and apply to span inside the component.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment