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
- 118