23

Trying to play with Angular 2 here... understood that it's still in alpha.

How do I access DOM elements from Component? Specifically, I would like to use other libraries like d3 to generate custom DOM from code. I suppose I need to create a component and somehow plug into component life-circle to alter DOM with d3... any idea where to start digging?

I'm using this quickstart repo.

Thanks!

Anatoly
  • 233
  • 1
  • 2
  • 4

5 Answers5

19

If you don't mind using Directive instead of Component it is straightforward. For Angular 2.0.0-alpha.33 in typescript you can use D3 to manipulate the DOM by Injecting an ElementRef:

@Directive({
    selector:   'scatter-plot',
    lifecycle: [LifecycleEvent.onChange],
    properties: [ 'data' ]
})
export class ScatterPlot {

    root: Selection<any>;
    data: Array<ScatterPlotDataPoint>;

    x: (number) => number;
    y: (number) => number;

    defaultSize: string;
    circles: any;

    constructor(
        @Inject(ElementRef) elementRef: ElementRef,
        @Attribute('height') height: string,
        @Attribute('default-size') defaultSize: string
    ) {
        var el:HTMLElement = elementRef.nativeElement;
        this.root = d3.select(el);

        this.defaultSize = defaultSize ? defaultSize : "5";
        this.circles = this.root
            .append('svg')
            .attr('class', 'chart')
            .style({
                'width':  '100%',
                'height': height ? height + 'px': '',
            }).
            selectAll('circle');

    }

    render(newValue) {
        if (!newValue) return;

        this.x = d3.scale.linear().domain([-10, 110]).range([0, 250]);
        this.y = d3.scale.linear().domain([-10, 110]).range([100, 0]);

        this.circles = this.circles
            .data(newValue);

        this.circles.exit().remove();

        this.circles.enter()
            .append('circle');

        this.circles
            .transition()
            .attr("r", d => d.size ? d.size: this.defaultSize)
            .style("fill", d => d.color)
            .attr('cx', d => this.x(d.x))
            .attr('cy', d => this.y(d.y));

    }

    onChange() {
        this.render(this.data);
    }
}
Hardbyte
  • 1,467
  • 13
  • 25
7

Use ElementRef

ElementRef will inject the current DOM node into your component. The ElementRef service will always be local to your current DOM node.

Having injected it, you can get hold of the actual DOM node using nativeElement:

var el = elementRef.nativeElement

Once you have this, you can manipulate it it in any way you like, either using DOM scripting, or using a DOM wrapper like jQuery:

$(el)
  .append('<p>Some interesting Stuff')
  .addClass('my_class');

But don't do this if you can help it

Be aware that, as with Angular 1, the use of direct DOM manipulation is discouraged. You can get pretty much all of your work done using templates, and you should favour this way of working most of the time.

Direct DOM manipulation leads to convoluted, hard to understand, hard to test code.

There are times though, when it can seem like the best solution. For example, if you have to integrate with a third party, high-complexity framework like a graphing library.

Here's a worked example (in ES6)

var AppComponent = ng.core
  .Component({
    selector: "app",
    template:
    `
      <div>Captured element</div>
    `
  })
  .Class({
    constructor: [ng.core.ElementRef, function(elementRef) {
      var el = elementRef.nativeElement
      el;
    }]
  })
superluminary
  • 47,086
  • 25
  • 151
  • 148
  • How would you approach something like [angular-ui's layout directive](https://github.com/angular-ui/ui-layout) in Angular 2, without using direct DOM manipulation? It is a component that has to manage the sizes of the components inside it. It seems like you'd have to traverse the DOM to make that happen. – Chris Mar 18 '16 at 00:15
  • @Chris - If you just want to embed multiple transclusions inside a layout component, you would use the content tag. There's a good article on it here: https://www.airpair.com/angularjs/posts/creating-components-p3-angular2-directives – superluminary Mar 18 '16 at 11:58
5

Note: [Deprecated]

You can use "BrowserDomAdapter"
https://angular.io/docs/ts/latest/api/platform/browser/BrowserDomAdapter-class.html

import {BrowserDomAdapter} from 'angular2/platform/browser'

@Component({
  selector: 'dom',
  templateUrl: 'path/to/template.html',
  providers: [BrowserDomAdapter]
})
export class DomComponent {
  constructor(private _dom: BrowserDomAdapter) {
    var domIWant = this._dom.query('whatever you want to get');
  }
}

Process

  1. import BrowserDomAdapter
  2. apply providers: [BrowserDomAdapter]
  3. initiate in constructor()
  4. get dom using instanced BrowserDomAdapter
taz
  • 145
  • 2
  • 3
0

This doc (http://victorsavkin.com/post/118372404541/the-core-concepts-of-angular-2) mentions 'decorator-style directives' which I believe are described with a little more detail and basic example here (https://angular.io/api/core/Directive).

Evan Wieland
  • 1,445
  • 1
  • 20
  • 36
Martin MacPherson
  • 337
  • 1
  • 2
  • 10
0

As other posters have mentioned: you can manipulate the DOM by injecting ElementRef into the directive. However, consider carefully whether you really need to do this.

D3 is a DOM manipulation tool. It allows you to bind a data object to a DOM node, then add or remove child elements from that element in response to changing data values. This is pretty much exactly what Angular 2 does.

Of course, D3 does other things as well. You can generate angles for a pie chart, or a normal distribution for example, and you may wish to use D3 for this.

I know it's not the answer you're looking for. It's not the answer I was looking for, but... Consider carefully whether you need to use D3 for DOM manipulation and data binding. Angular 2 already has DOM manipulation and data binding.

superluminary
  • 47,086
  • 25
  • 151
  • 148