const button = document.querySelector('button');
const header = document.querySelector('header');
const className = 'green-font';

button.addEventListener('click', () => {
    header.classList.add(className);
});

Working with DOM directly

Can't Touch This!

What not to do to the DOM

Wrong approach!

Native DOM API access through ElementRef

Template

Data binding + directives

Mindset

@Component({ ... })
export class AppComponent {
    property = '';
}

Component

<div ngClass="property">
<div ngStyle="property">

Data-binding and built-in ngClass directive

@Component({
    selector: 'my-app',
    template: `
        <div [ngClass]="className">I will be red</div>
        <button (click)="addClass()">Add class</button>
    `
})
export class AppComponent {
    className = '';

    addClass() {
        this.className = 'red';
    }
}

Using custom addAttribute directive

@Component({
    selector: 'my-app',
    template: `
        <div [attr]="attr"></div>
        <button (click)="addAttribute()">Add attribute</button>
    `
})
export class AppComponent {
    attr;

    addAttribute() {
        this.attr = {name: 'data-custom', value: 'custom-value'};
    }
}

Implementation of custom addAttribute directive

@Directive({
    selector: '[attr]'
})
export class AttributeDirective {
    @Input() attr: { name: string, value: string };

    constructor(private el: ElementRef) {}

    ngOnChanges() {
        if (this.attr.name !== '') {
            this.el.nativeElement.setAttribute(this.attr.name, this.attr.value);
        }
    }
}

API's we use in a browser

Modify DOM hierarchy (appendChild, remove, removeChild, innerHTML)

Change/Read DOM element properties like

(class, style, attributes)

Warning from Angular docs

Use this API as the last resort when direct access to DOM is needed.

Tools & DOM operations

Operations Tools
Change/Read DOM element properties like Sandboxed DOM access through Renderer
Modify DOM hierarchy  Templating through ViewContainerRef

Built-in directives

Directives Tools
NgClass, NgStyle Renderer
ngIf, ngFor View Container

 Relying on direct DOM access creates tight coupling between your application and rendering layers which will make it impossible to separate the two and deploy your application into a web worker.

The built-in browser DOM APIs don't automatically protect you from security vulnerabilities

"

"

"

"

Security vulnerabilities

Pitfalls of using using native API

Platform dependence

Cross-site scripting (XSS)

constructor(el: ElementRef, sanitizer: DomSanitizer) {
    const myHtml = '<span>Harmless span<span/>';

    // returns '<script>alert("I am malicious code")</script>';
    const userHtml = getUserGeneratedHtml();
    
    const html = myHtml + userHtml;
   
    const dom = convertStringToDom(html);

    // malicious code will be executed on the page
    el.nativeElement.appendChild(dom);

Sanitizing input from the user

constructor(el: ElementRef, sanitizer: DomSanitizer) {
    const myHtml = '<span>Harmless span<span/>';

    // returns '<script>alert("I am malicious code")</script>';
    const userHtml = getUserGeneratedHtml();
    
    const html = myHtml + userHtml;

    // malicious code will be removed from the string
    const sanitized = sanitizer.sanitize(SecurityContext.HTML, html);
   
    const dom = convertStringToDom(sanitized);

    el.nativeElement.appendChild(dom);

Bypassing default sanitizataion mechanism

  • bypassSecurityTrustHtml
  • bypassSecurityTrustScript
  • bypassSecurityTrustStyle
  • bypassSecurityTrustUrl
  • bypassSecurityTrustResourceUrl

Platform dependence

what platforms do we have?

 

browser

 

universal

 

webworker

 

native

Using native DOM API

Platform Element API
browser OK
webworker ERROR
universal OK

Renderer

Rendering layer architecture

Create DOM nodes from the template

Rendering layer abstractions on different platforms

Platform Package DOM adapter Renderer
Browser platform-browser BrowserDomAdapter DefaultDomRenderer2
Universal platform-server DominoAdapter DefaultServerRenderer2
Worker platform-webworker WorkerDomAdapter WebWorkerRenderer2

Web Worker Specific Renderer

Implementing custom renderer

class CustomRendererFactory2 implements RendererFactory2 {
    createRenderer(...): Renderer2 {
        return new CustomRenderer2();
    }
}
export class CustomRenderer2 implements Renderer2 {
    createElement(name: string, namespace?: string): any {}
    createText(value: string): any {}
    appendChild(parent: any, newChild: any): void {}
    removeChild(parent: any, oldChild: any): void {}
    addClass(el: any, name: string): void {}
    ...
}
@NgModule({
    providers: [
        CustomRendererFactory2,
        {
            provide: RendererFactory2,
            useExisting: CustomRendererFactory2
        }
    ]
})
export class AppModule {}

Extending default renderer with custom behavior

export class CustomRenderer2 implements Renderer2 {
    constructor(private delegate: Renderer2) {}

    createElement(name: string, namespace?: string): any {
        const el = this.delegate.createElement(name, namespace);
        this.delegate.addClass(el, 'created-by-angular');
        return el;
    }
}

Using Renderer to add a custom attribute

@Directive({
    selector: '[attr]'
})
export class AttributeDirective {
    @Input() attr: { name: string, value: string };

    constructor(private el: ElementRef, private renderer: Renderer2) {
    }

    ngOnChanges() {
        if (this.attr.name !== '') {
            this.renderer.setAttribute(this.el.nativeElement, this.attr.name, this.attr.value);
        }
    }
}

Using renderer

Platform Element API
browser OK
webworker OK
universal OK

 

security

    use Sanitizer

Considerations when using direct access API

platforms

   use Renderer

Tools & DOM operations

Operations Tools
Change/Read DOM element properties like
Sandboxed DOM access through Renderer
Modify DOM hierarchy  Templating through ViewContainerRef

Unexpected consequences of direct manipulation with DOM

@Component({
    selector: 'my-app',
    template: `
        <a-comp></a-comp>
        <a-comp></a-comp>
        <a-comp></a-comp>
        <button (click)="remove()">Remove last component</button>
    `
})
export class AppComponent {
    @ViewChildren(DComponent) childComps;

    constructor(private el: ElementRef) {}

    ngAfterViewChecked() {
        console.log(this.childComps.length); // always shows 3
    }

    remove() {
        const child = this.el.nativeElement.querySelector('a-comp');
        if (child !== null) {
            child.remove();
        }
    }
}
@Component({
    selector: 'a-comp',
    template: `<b-comp></b-comp>`
})
export class AComponent {}

@Component({
    selector: 'a-comp',
    template: `<c-comp></c-comp>`
})
export class BComponent {}

Fundamental building block of UI in Angular

View (ViewRef)

The role of the compiler

ViewContainerRef

class ViewContainerRef {
    createEmbeddedView(templateRef...): EmbeddedViewRef<C>
    createComponent(componentFactory...): ComponentRef<C>
    ...
}
class ViewContainerRef {
    ...
    clear() : void
    insert(viewRef: ViewRef, index?: number) : ViewRef
    get(index: number) : ViewRef
    indexOf(viewRef: ViewRef) : number
    detach(index?: number) : ViewRef
    move(viewRef: ViewRef, currentIndex: number) : ViewRef
}

Creating views

Manipulating views

Types of views

Host Views

Embedded Views

binds together context object and DOM elements

binds together component class and DOM elements

ComponentFactory

TemplateRef

Refactored example

@Component({
    selector: 'my-app',
    template: `
        <ng-template><d-comp></d-comp></ng-template>
        <ng-container #vc></ng-container>
        <button (click)="remove()">Remove last component</button>
    `
})
export class AppComponent {
    @ViewChildren(DComponent) childComps;
    @ViewChild('vc', {read: ViewContainerRef}) vc;
    @ViewChild(TemplateRef) t;

    ngOnInit() {
        this.vc.createEmbeddedView(this.t);
        this.vc.createEmbeddedView(this.t);
        this.vc.createEmbeddedView(this.t);
    }

    ngAfterViewChecked() {
        console.log(this.childComps.length); // shows correct number 2, 1, 0
    }

    remove() {
        if (this.vc.length > 0) {
            this.vc.remove();
        }
    }
}

Manipulating views

class ViewContainerRef {
    ...
    clear() : void
    insert(viewRef: ViewRef, index?: number) : ViewRef
    get(index: number) : ViewRef
    indexOf(viewRef: ViewRef) : number
    remove(index?: number): void;
    detach(index?: number) : ViewRef
    move(viewRef: ViewRef, currentIndex: number) : ViewRef
}

Manipulating views

ViewContainreRef API: a view can be reused

detach

attach

Advanced Examples

of using Renderer and ViewContainerRef

Future of the renderer - Renderer3

Possible additions:

- types of renderer (encapsulated, native)

- events plugin

- ComponentRef

-FactoryResolver

ngTemplateOutlet and ngComponentOutlet

Maybe add later:

DOCUMENT
- Events (keyup)
- Querying (querySelector)
- Creating elements

DOM

By maximk

DOM

  • 111