2

I'm trying to access a DOM element outside the current directive's element.. for a resize-like behavior, using an outside element as a handle and to not change the target markup that much, especially not using transclusion like <ng-content>.
What I'm doing right now is something I think a bit unorthodox, like using the BrowserDomAdapter:

import {Directive, Input, HostListener} from 'angular2/core';
import {BrowserDomAdapter} from 'angular2/platform/browser';

@Directive
({
  selector: '[resizable-handle]',
  providers: [BrowserDomAdapter]
})
export class ResizableHandle
{
  // get selector from actual directive selector attribute
  @Input('resizable-handle') resizableSelector: string;

  constructor(private _domAdapter: BrowserDomAdapter){}

  @HostListener('mousedown', ['$event'])
  startResize(e: MouseEvent)
  {
    this._domAdapter.query(this.resizableSelector); //... manipulate this native element
  }
  //... and so on...
}

And the template looks like this:

<a [resizable-handle]="'.target-container'"> ... </a>
<!-- ...somewhere further, on a different level, the target I don't want to touch in order to get this working... -->
<div class="target-container"> ... </div>
1
  • What is the problem with <ng-content> in your case? Commented Apr 21, 2016 at 8:23

3 Answers 3

3

Thanks kemsky!

Based on your suggestion regarding template variables I have devised this solution:

@Directive
({
  selector: '[resizable-handle]', 
  host: { '(click)': 'emitHeight()' }
})
export class ResizableHandle 
{
  @Input('resizable-handle') resizableSelector: ElementRef;

  @Input() targetHeight: any;
  @Output() targetHeightChange = new EventEmitter<number>();

  emitHeight(){ this.targetHeightChange.emit(100) }
}

@Component
({
  selector: 'my-app',
  directives: [ResizableHandle],
  template: `
    <h1>Hello</h1>
    <a [resizable-handle]="targetContainer" [(targetHeight)]="containerHeight" href="javascript:void(0)"> [click me!] </a>
    <div #targetContainer [style.height.px]="containerHeight"> ... </div>
  `
})
export class AppComponent 
{
  public containerHeight = 25;
} 

Plunker preview

Also thanks Günter Zöchbauer. I built another solution using a parent directive; this because the handle and the target were actually not in the same tree, so I needed a "root" to communicate.

@Directive({ selector: '[resizable-handle]' })
export class ResizableHandle 
{
  constructor(@Host() @Inject(forwardRef(()=>Resizable)) private _resizable: Resizable){}
  // simulate dropping a resize handle here
  @HostListener('click', ['$event'])
  emitHeight(){ this._resizable.change = Math.max( Math.random()*100, 25 ); }
}

@Directive({ selector: '[resizable-target]' })
export class ResizableTarget
{
  constructor(@Host() @Inject(forwardRef(()=>Resizable)) private _resizable: Resizable)
  {
    this._resizable.change.subscribe( c => this.height = c );
  }

  @HostBinding('style.height.px') public height;
}

@Directive({ selector: '[resizable]' })
export class Resizable
{
  private _change: number;
  private _observableChange: Observable<number>;

  constructor()
  {
    this._observableChange = Observable.create( observer => this._change = observer ).share();
  }
  set change(value){ this._change.next(value) }
  get change(){ return this._observableChange; }
}

@Component
({
    selector: 'my-app',
    directives: [ResizableHandle, ResizableTarget, Resizable],
    template: `
      <div resizable>
        <a resizable-handle href="javascript:void(0)"> [click me!] </a>
        <p>...</p>
        <div resizable-target> ...a... </div>
        <div resizable-target> ...b... </div>
      </div>
    `,
})
export class AppComponent{}

Plunker preview

Sign up to request clarification or add additional context in comments.

Comments

2

I would use a directive that is applied to the target element that does the actual manipulation directly on the target.

Then you can inject it using the constructor. This always injects from the closest parent where it finds this directive.

@Directive({
  selector: '[resizable-target]',
  host: {'[style.border]': 'border'},
})
export class ResizableTarget {
  // change style just to demonstrate this directive can be manipulated
  border:string = "solid 3px blue";

  @HostBinding('class.reached') reached:boolean = false;
}
@Directive({
  selector: '[resizable-handle]',
})
export class ResizableHandle {
  // inject the target
  constructor(private _target:ResizableTarget) {}

  @HostListener('mousedown', ['$event'])
  startResize(e: MouseEvent) {
    // call methods or set properties in order to manipulate the target
    this._target.reached = true; 
    this._target.border = "solid 3px red";
  }
}
@Component({
    selector: 'my-app',
    directives: [ResizableTarget, ResizableHandle],
    template: `
    <h1>Hello</h1>

    <div resizable-target>
      <div>
        <div> some content
          <div resizable-handle>handle</div>
        </div>
      </div>
    </div>
    `,
})
export class AppComponent {
}

Plunker example

Comments

1

You could use template variables:

@Input('resizable-handle') resizableSelector: ElementRef;

<a [resizable-handle]="target-container"> ... </a>
<!-- ...somewhere further, on a different level, the target I don't want to touch in order to get this working... -->
<div #target-container> ... </div>

1 Comment

No way, I don't want to directly use the document. I'm using the BrowserDomAdapter because maybe I will, for example, use another type of browser or something totally different at some point... but I think even this is unreliable for portability. I was thinking of using some other paradigm inside the framework... I don't know..

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.