カテゴリー
【Angular基礎講座】レンダリング後のhtmlの無駄にラップされている要素を無理やり剥がしてみる
※ 当ページには【広告/PR】を含む場合があります。
2019/09/19
2022/08/10
今回はAngularで、レンダリング出力後のDOMを直接操作するのに便利な
Renderer2
ちなみに、ムダ要素のラッピング剥がし程度ならば、
Renderer2
今回は
Renderer2
検証 - 通常のレンダリング後のhtml出力
先ずレンダリングの後で、無駄にラップされているhtmlの一例を挙げます。
以下のように、孫コンポネート、子コンポネート、基準コンポネート(自分)、親コンポネート、親の親コンポネートを定義して、階層構造を作ります。
import { Component } from '@angular/core';
@Component({
selector: 'app-my-grandchild-comp',
template: `<div>my-grandchild</div>`
})
export class MyGrandChildComponent {}
@Component({
selector: 'app-my-child-comp',
template: `
<div>
my-child
<app-my-grandchild-comp></app-my-grandchild-comp>
</div>
`
})
export class MyChildComponent {}
@Component({
selector: 'app-myself-comp',
template: `
<div>
me
<app-my-child-comp></app-my-child-comp>
</div>
`
})
export class MyselfComponent {}
@Component({
selector: 'app-my-parent-comp',
template: `
<div>
my-parent
<app-myself-comp></app-myself-comp>
</div>
`
})
export class MyParentComponent {}
@Component({
selector: 'app-my-grandparent-comp',
template: `
<div>
my-grandparent
<app-my-parent-comp></app-my-parent-comp>
</div>
`
})
export class MyGrandParentComponent {}
app.component.htmlの方からの呼び出しは、
<app-my-grandparent-comp></app-my-grandparent-comp>
とすることで、レンダリングすると、以下のようなDOM構造になります。

これは5回の階層構造で事足りるものが、コンポネート自体の要素のタグ名も合わさって、計10段の構造になってしまいました。
とはいえ、レンダリング後のHTMLが気になる方は、
<div>
my-grandparent
<div>
my-parent
<div>
me
<div>
my-child
<div>
my-grandchild
</div>
</div>
</div>
</div>
</div>
のようにスッキリと出力したい場合もあると思います。
例えば、階層構造を持つ
<table>
<thead>, <tbody>, <tfoot>
<tr>
Renderer2の基本
本題をご説明する前に、
Renderer2
説明をしやすくするために、上のソースコードで定義した
MyselfComponent
<app-myself-comp>
先ずは以下のような属性ディレクテブを作成してDOMの情報をサンプリングしてみましょう。
import {
Directive, AfterViewInit, ElementRef, Renderer2
} from '@angular/core';
@Directive({
selector: '[appDomManipulator]'
})
export class DomManipulatorDirective implements AfterViewInit {
constructor(
private el: ElementRef,
private renderer: Renderer2
) { }
ngAfterViewInit() {
const itsMe = this.el.nativeElement; // 自分本体(この場合<app-myself-comp>)
const parentsDiv = this.renderer.parentNode(itsMe); // <app-parent-comp>内の<div>
const parent = this.renderer.parentNode(parentsDiv); // <app-parent-comp>本体
const grandparentsDiv = this.renderer.parentNode(parent); // <app-grandparent-comp>内の<div>
const grandparent = this.renderer.parentNode(grandparentsDiv); // <app-grandparent-comp>本体
const myDiv = itsMe.childNodes[0]; // 自分の中の<div>
const child = myDiv.childNodes[1]; // <app-child-comp>本体
const childsDiv = child.childNodes[0]; // <app-child-comp>内の<div>
const grandchild = childsDiv.childNodes[1]; // <app-grandchild-comp>本体
console.log(grandparent);
console.log(parent);
console.log(itsMe);
console.log(child);
console.log(grandchild);
}
}
この属性ディレクテブを
MyParentComponent
<app-myself-comp>
...
@Component({
selector: 'app-my-parent-comp',
template: `
<div>
my-parent
<app-myself-comp appDomManipulator></app-myself-comp>
</div>
`
})
export class MyParentComponent {}
...
さて、これをビルドしてブラウザのコンソールを確認すると、

となって、基点のDOM(
<app-myself-comp>
ここでのポイントとして、
Renderer2
parentNode
再度
parentNode
子DOM要素も理屈は同じですが、
childNodes
insertBeforeで親子の縁を切る
Renderer2
この関数を利用して、親子関係にあるDOMを移し変えることを考えます。

この関数の用法をかいつまむと、
Method:
insertBefore(parent: any, newChild: any, refChild: any): void
Parameters:
parent:
新しく親にしたいDOMノード
newChild:
新たに子ノードとして移動したいDOMノード
refChild:
もともと親だったDOMノード
今回は、子DOM要素をもともとの親DOM要素の階層に移動したいので、
insertBefore(親の親ノード, 子ノード, 親ノード)
ではこれを実行する属性ディレクテブを以下に示します。
import {
Directive, AfterViewInit, ElementRef, Renderer2
} from '@angular/core';
@Directive({
selector: '[appUnwrapDom]'
})
export class UnwrapDomDirective implements AfterViewInit {
constructor(
private el: ElementRef,
private renderer: Renderer2
) { }
ngAfterViewInit() {
const myDiv = this.el.nativeElement; // <app-***>の中身(=この場合指定した<div>)
const parent = this.renderer.parentNode(myDiv); // 親要素の<app-*****>
const superparent = this.renderer.parentNode(parent); // 親要素の一つ上
this.renderer.insertBefore(superparent, myDiv, parent);
}
}
そして、この属性ディレクテブを子要素(
<div>...</div>
<app-***>
具体的には以下のように変更します。
import { Component } from '@angular/core';
@Component({
selector: 'app-my-grandchild-comp',
template: `<div appUnwrapDom>my-grandchild</div>`
})
export class MyGrandChildComponent {}
@Component({
selector: 'app-my-child-comp',
template: `
<div appUnwrapDom>
my-child
<app-my-grandchild-comp></app-my-grandchild-comp>
</div>
`
})
export class MyChildComponent {}
@Component({
selector: 'app-myself-comp',
template: `
<div appUnwrapDom>
me
<app-my-child-comp></app-my-child-comp>
</div>
`
})
export class MyselfComponent {}
@Component({
selector: 'app-my-parent-comp',
template: `
<div appUnwrapDom>
my-parent
<app-myself-comp></app-myself-comp>
</div>
`
})
export class MyParentComponent {}
@Component({
selector: 'app-my-grandparent-comp',
template: `
<div appUnwrapDom>
my-grandparent
<app-my-parent-comp></app-my-parent-comp>
</div>
`
})
export class MyGrandParentComponent {}
こうすることで、DOMの親子関係を変更することができました。

不要なDOMを削除するremoveChild()メソッド
さて、親子関係の順序は入れ替えることができましたが、残った
<app-***>
ということで、
Renderer2
用法は
insertBefore
removeChild(消したいノードの親ノード, 消したいノード)
上記の
unwrap-dom.directive.ts
import {
Directive, AfterViewInit, ElementRef, Renderer2
} from '@angular/core';
@Directive({
selector: '[appUnwrapDom]'
})
export class UnwrapDomDirective implements AfterViewInit {
constructor(
private el: ElementRef,
private renderer: Renderer2
) { }
ngAfterViewInit() {
const myDiv = this.el.nativeElement; // <app-***>の中身(=この場合指定した<div>)
const parent = this.renderer.parentNode(myDiv); // 親要素の<app-*****>
const superparent = this.renderer.parentNode(parent); // 親要素の一つ上
this.renderer.insertBefore(superparent, myDiv, parent);
// 残留した<app-***>を消去
this.renderer.removeChild(superparent, parent);
}
}
こうすることで、

と綺麗さっぱりなDOM構造を得ました。
まとめ
今回考察した、angular独特のレンダリング後DOMのアンラップ程度なら、
<ng-content></ng-content>
ですが、angular組み込みの構造ディレクテブ(
ngIf, ngIf, ngSwitch,...
こういった複雑なDOMになってしまう場合では、あらかじめDOMの出力位置が予想できていればこその
<ng-content>
レンダリング後のHTMLにかなり複雑な処理をする際には、今回のように
Renderer2
参考
今回
Renderer2
実質上の
Renderer3
Ivy
Renderer2
Ivy
今回の内容も今後Ivy版でお届けできたらいいな、と思います。
また、
Renderer2
この記事の内容はだいたい Angular Cli 4+をターゲットに書いており、さらっと
AfterViewInit
もうちらほらと11の話も出てきている時期で何かと参考書籍の鮮度も気になって参ります。
Ivy
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー