Gary McGill
Gary McGill

Reputation: 27556

Can I do two things at once in a structural directive?

I'd like to create a structural directive which behaves as follows:

<p *myDirective="condition">This is some text</p>

So, either there's nothing rendered, or:

<p class="my-added-class">This is some text</p>

In other words, it's a bit like *ngIf, but with additional behaviour.

I can find examples of how to do the include/exclude behaviour (in fact there is such an example in the Angular docs). I can also find examples of how to add a class to an element using the Renderer2 API.

However, I don't see how I can combine these techniques, because the first method manipulates the viewContainer to create an embedded view, whereas the second method uses the renderer to manipulate an element.

Is there a way to do this? Can I somehow create the embedded view and then manipulate the elements it creates? Or can I manipulate the template to change how the view is rendered?

[NOTE: @HostBinding does not work with structural directives, so that's not an option]

Upvotes: 5

Views: 2626

Answers (2)

Whisher
Whisher

Reputation: 32806

An other way

just to play around :)

Using Renderer2 is universal safe

import {
  Directive,
  Renderer2,
  TemplateRef,
  ViewContainerRef,
  ElementRef,
  Input, OnInit } from '@angular/core';

@Directive({
  selector: '[appMy]'
})
export class MyDirective implements OnInit{
  constructor(
  private templateRef: TemplateRef<any>,
  private viewContainer: ViewContainerRef,
  private renderer: Renderer2) { }
  @Input() set appMy(condition: boolean) {
   if (condition) {
     this.viewContainer.createEmbeddedView(this.templateRef);
    } else  {
     this.viewContainer.clear();
   }
  }
  ngOnInit() {
    const elementRef = this.viewContainer.get(0).rootNodes[0] as ElementRef;
    this.renderer.addClass(elementRef, 'myclass');
  }
}

Following the @Pankaj way but with renderer

@Input() set appMy(condition: boolean) {
   if (condition) {
     const view = this.viewContainer.createEmbeddedView(this.templateRef);
     this.renderer.addClass(view.rootNodes[0], 'myclass');
   } else  {
     this.viewContainer.clear();
   }
  }

Upvotes: 3

Pankaj Parkar
Pankaj Parkar

Reputation: 136184

I'd think of adding class on the DOM when it satisfied the expression passed to it (inside setter). You can grab the ElementRef dependency inside directive and append a class to it which its truthy.

@Input() set myDirective(condition: boolean) {
  if (condition) {
    this.viewContainer.createEmbeddedView(this.templateRef);
    this.elementRef.nativeElement.nextElementSibling.classList.add('my-added-class'); // renderer API can be used here
    // as Alex and Yurzui suggested
    // const view = this.viewContainer.createEmbeddedView(this.templateRef); 
    // view.rootNodes[0].classList.add('some-class')
  } else if (condition) {
    this.viewContainer.clear();
  }
}

Upvotes: 3

Related Questions