Используем Renderer2 в Angular

May 19, 2019

Если вы начинающий разработчик в использовании Angular2, то у вас может возникнуть вопрос: Что я могу использовать, кроме нативных методов JS, если я хочу модифицировать DOM?

На этом этапе вы можете начать использовать Renderer2, чтобы удовлетворить свои потребности.

Renderer2 в отличие от нативных методов JS поддерживает абстрактные методы Angular, выполнение в не DOM ориентированных средах, например, рендеринг на стороне сервера, Web-Worker, мобильные и десктопные приложения.

Renderer2 в основном используется при создании кастомных директив. Например:

import { Directive, ElementRef, OnInit, Renderer2, HostListener } from '@angular/core';
@Directive({
    selector: '[animate]'
})
export class Animate  {
    constructor(private renderer: Renderer2, private el: ElementRef) {}

@HostListener('click')
    performTask() {
        let randomColor = "#"+((1<<24)*Math.random()|0).toString(16);
        this.renderer.setStyle(this.el.nativeElement, 'color', randomColor);
        this.renderer.setStyle(this.el.nativeElement, 'background-color', 'black');
    }
}
<h2 animate>Click here to give me a random color</h2>

Пример на stackblitz.com

Пример выше показывает как использовать Renderer2 в директиве, но мы также можем использовать ее в компоненте!

В прведенном ниже примере при помощи декоратора ViewChild, мы можем создать button:

import { Component, ElementRef, Renderer2, ViewChild } from '@angular/core';

@Component({
   selector: 'app-component',
   template: `<ul class="col-md-2">
                <li (click)="addBtn()" #addButton>Click here to add new button</li>
              </ul>`
})

export class AppComponent {
    @ViewChild('addButton')
    private animateThis: ElementRef;
    constructor(private renderer: Renderer2) { }

    addBtn() {
        const button = this.renderer.createElement('button');
        const buttonText = this.renderer.createText('This is a button');
        this.renderer.appendChild(button, buttonText);
        this.renderer.appendChild(this.animateThis.nativeElement, button);
    }
}
<ul class="col-md-2">
  <li (click)="addBtn()" #addButton>Click here to add new button</li>
</ul>

Пример на stackblitz.com

Renderer2 имеет 19 методов.

createElement()

createElement(name: string, namespace?: string):any

appendChild()

Добавляет дочерний элемент в переданный родительский узел:

appendChild(parent: any, newChild: any):

createText()

createText(value: string)

С 3 приведенными выше методами мы можем создать элемент и вставить его на страницу:

import { Component, ElementRef, Renderer2, ViewChild } from '@angular/core';

@Component({
    selector: 'app-component',
    template: `<ul class="col-md-2">
                    <li (click)="addBtn()" #addButton>Click here to add new button</li>
               </ul>`
})

export class AppComponent {
    @ViewChild('addButton')
    private animateThis: ElementRef;
    constructor(private renderer: Renderer2) {}

    addBtn() {
        const button = this.renderer.createElement('button');
        const buttonText = this.renderer.createText('This is a button');
        this.renderer.appendChild(button, buttonText);
        this.renderer.appendChild(this.animateThis.nativeElement, button);
    }
}

insertBefore()

insertBefore() добавляет дочерний узел перед целевым элементом, но также внутри родительского элемента.

insertBefore(parent: any, newChild: any, refChild: any)
import { Component, ElementRef, Renderer2, ViewChild } from '@angular/core';

@Component({
   selector: 'app-component',
    template: `<ul class="col-md-2">
                    <li (click)="addBtn()" #addButton>Click here to add new button</li>
               </ul>`
})

export class AppComponent {
    @ViewChild('addButton')
    private animateThis: ElementRef;
    constructor(private elRef: ElementRef, private renderer: Renderer2) {}

    addBtn() {
        const button = this.renderer.createElement('button');
        const buttonText = this.renderer.createText('This is a button');
        const comment = this.renderer.createComment('createComment? Comment Created!');
        const parent = this.elRef.nativeElement.parentNode;
        const reference = this.elRef.nativeElement;
        this.renderer.appendChild(button, buttonText);
        this.renderer.insertBefore(parent, comment, reference)
        this.renderer.appendChild(this.animateThis.nativeElement, button);
    }
}

Пример на stackblitz.com (внутри компонета)

Пример на stackblitz.com (внутри директивы)

setStyle()

Создает внутренние стили для элемента.

setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2)

Свойство flags предназначено для стилевой вариации. По умолчанию undefined.

import { Component, ViewChild, ElementRef, Renderer2  } from '@angular/core';

@Component({
    selector: 'my-app',
    template: `
        Username:
        <input type="text" placeholder="type your name..."  #changeStyle>
    `,
})
export class AppComponent  {
    @ViewChild('changeStyle')
    private elRef: ElementRef;
    constructor(private renderer: Renderer2) {}

    ngOnInit() {
        this.renderer.setStyle(this.elRef.nativeElement, 'border', '1px solid red');
    }
}

addClass()

Добавляет класс к элементу.

addClass(el: any, name: string)
import { Component, ViewChild, ElementRef, Renderer2  } from '@angular/core';

@Component({
    selector: 'my-app',
    template: `
        Username:
        <input type="text" placeholder="type your name..."  #changeStyle>
    `,
})
export class AppComponent  {
    @ViewChild('changeStyle')
    private elRef: ElementRef;
    constructor(private renderer: Renderer2) { }

    ngOnInit() {
        this.renderer.addClass(this.elRef.nativeElement, 'someClass');
    }

    //HTML output
    //<input placeholder="type your name..." type="text" class="someClass">
}

Если я захочу добавить значение input, то это можно сделать при помощи setAttribute.

setAttribute()

setAttribute(el: any, name: string, value: string, namespace?: string)
import { Component, ViewChild, ElementRef, Renderer2  } from '@angular/core';

@Component({
    selector: 'my-app',
    template: `
        Username:
        <input type="text" placeholder="type your name..."  #changeStyle>
    `,
})
export class AppComponent  {
    @ViewChild('changeStyle')
    private elRef: ElementRef;
    constructor(private renderer: Renderer2) {}

    ngOnInit() {
        this.renderer.setAttribute(this.elRef.nativeElement, 'value', 'Gokhan');
    }
}

setProperty()

Установим свойство со значением на элементе.

setProperty(el: any, name: string, value: any)
    import { Component, ViewChild, ElementRef, Renderer2  } from '@angular/core';

@Component({
    selector: 'my-app',
    template: `
        Username:
        <input type="text" placeholder="type your name..."  #changeStyle>
    `,
})
export class AppComponent  {
    @ViewChild('changeStyle')
    private elRef: ElementRef;
    constructor(private renderer: Renderer2) {}

    ngOnInit() {
        this.renderer.setProperty(this.elRef.nativeElement, 'disabled', 'disabled');
    }
}

Двойники для remove у приведенных выше методов идентичны:

removeClass(el: any, name: string)

removeAttribute(el: any, name: string, namespace?: string)

removeStyle(el: any, style: string, flags?: RendererStyleFlags2)

Пример на stackblitz.com

nextSibling()

nextSibling() предоставляет следующий сестринский элемент.

nextSibling(node: any)
import { Component, Renderer2, ElementRef, ViewChild, OnInit } from '@angular/core';

@Component({
    selector: 'my-app',
    template: `
        <p #next>First</p>
        <p>Second</p>
        <p>Third</p>
    `,
    styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
    @ViewChild('next')
    private elRef: ElementRef;
    constructor(private renderer: Renderer2) {}

    ngOnInit(){
        const currentElement = this.elRef.nativeElement;
        const nextEl = this.renderer.nextSibling(currentElement);
        this.renderer.addClass(nextEl, 'red');
    }
}

parentNode()

parentNode работает подобно nextSibling - отдает пользователю родительский node элемента.

parentNode(node: any)
import { Component, Renderer2, ElementRef, ViewChild, OnInit } from '@angular/core';

@Component({
    selector: 'my-app',
    template: `
        <div>
            <p #parent>First</p>
            <p>Second</p>
            <p>Third</p>
        </div>
    `,
    styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
    @ViewChild('parent')
    private elRef: ElementRef;
    constructor(private renderer: Renderer2) {}

    ngOnInit(){
        const currentElement = this.elRef.nativeElement;
        const parent = this.renderer.parentNode(currentElement);
        this.renderer.addClass(parent, 'red');
    }
}

selectRootElement()

Когда вы используется selectRootElement и выбираете корневой элемент, то тем самым удаляете все содержимое корневого элемента. Чтобы сохранить элементы внутри корневого элемента установить опциональный параметр preserveContent в true.

selectRootElement(selectorOrNode: any, preserveContent?: boolean)

Вы также можете выбрать корневой элемент по классу или по тегу.

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

@Component({
    selector: 'my-app',
    template: `
        <div #root>
            X or Y?
        </div>
        <button (click)="changeIt()">Click</button>
    `,
    styleUrls: [ './app.component.css' ]
})
export class AppComponent {
    switchText: boolean = false;
    @ViewChild('root')
    private elRef: ElementRef;
    constructor(private renderer: Renderer2) {}
    changeIt() {
        this.switchText = !this.switchText;
        const rootEl = this.elRef.nativeElement;
        const text = this.switchText ?
            this.renderer.createText('Hey X!') :
            this.renderer.createText('Hey Y!');
        this.renderer.selectRootElement(rootEl);
        this.renderer.appendChild(rootEl, text)
    }
}

nextSibling, selectRootElement и parentNode методы на директивах - stackblitz.com

listen()

listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void
import { Component, ViewChild, ElementRef, Renderer2 } from '@angular/core';

@Component({
    selector: 'my-app',
    template: `
        <p #listen>
            Hover to see some magic!
        </p>
        {{count}}
    `,
})
export class AppComponent  {
    @ViewChild('listen')
    private elRef: ElementRef;
    constructor(private renderer: Renderer2) {
    }
    toggle = false;
    count = 0;
    ngAfterViewInit() {
        this.renderer.listen(this.elRef.nativeElement, 'mouseover', () => {
            this.toggle = !this.toggle;
            this.count++;
            const currentElement = this.elRef.nativeElement;
            const firstText = this.renderer.createText('Hover to see new text! (Hover me)');
            const secondText = this.renderer.createText('Text changed! (Hover me)');
            const thirdText = this.renderer.createText('Reached maximum count!');

            this.renderer.selectRootElement(currentElement);
            if(this.count < 10){
                this.toggle ? this.renderer.appendChild(currentElement, secondText) :
                this.renderer.appendChild(currentElement, firstText);
            } else {
                this.renderer.appendChild(currentElement, thirdText);
                this.count = 10;
            }

        });
    }
}

listen - stackblitz.com

Источник

Добавить комментарий
Комментарии:
Vit
Oct 17, 2019
is there a way to set styles like in angular 1.x angular.element($groupTitle).css({width: `${parentWidth}px`, top: '0px'})? I mean set object of style is more convenient.