17

I'm pretty new building directives with Angular2. What I want is to create a popup directive that will wrap the content with some css classes.

Content

Content can be pure text and headers like:

<div class="data">
    <h2>Header</h2>
    Content to be placed here.
</div>

Then I want to give this a directive attribute like: popup

<div class="data" popup>
    <h2>Header</h2>
    Content to be placed here.
</div>

What the directive should do, is to wrap the div inside, lets say:

<div class="some class">
    <div class="some other class">
        <div class="data">
            <h2>Header</h2>
            Content to be placed here.
        </div>
    </div>
</div>

The case i described so far, is this a attribute or structural directives.

import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({
  selector: `[popup]`
})

export class PopupDirective {


}
Kamil Naja
  • 6,267
  • 6
  • 33
  • 47
Mikkel
  • 1,771
  • 11
  • 35
  • 59

2 Answers2

24

The other answer is related but different.

For something closer, see this: How to conditionally wrap a div around ng-content - my solution is for Angular 4, but the linked question has some hints about how this might be doable for Angular 2.

I solved this problem with a component and a directive combined. My component looks something like this:

import { Component, Input, TemplateRef } from '@angular/core';

@Component({
  selector: 'my-wrapper-container',
  template: `
<div class="whatever">
  <ng-container *ngTemplateOutlet="template"></ng-container>
</div>
`
})
export class WrapperContainerComponent {
  @Input() template: TemplateRef<any>;
}

and my directive like this:

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

@Directive({
  selector: '[myWrapperDirective]'
})
export class WrapperDirective implements OnInit {

  private wrapperContainer: ComponentRef<WrapperContainerComponent>;

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainerRef: ViewContainerRef,
    private componentFactoryResolver: ComponentFactoryResolver
  ) { }

  ngOnInit() {
    const containerFactory = this.componentFactoryResolver.resolveComponentFactory(WrapperContainerComponent);
    this.wrapperContainer = this.viewContainerRef.createComponent(containerFactory);
    this.wrapperContainer.instance.template = this.templateRef;
  }
}

To be able to load your component dynamically, you need to list your component as an entryComponent inside your module :

@NgModule({
  imports: [CommonModule],
  declarations: [WrapperContainerComponent, WrapperDirective],
  exports: [WrapperContainerComponent, WrapperDirective],
  entryComponents: [WrapperContainerComponent]
})
export class MyModule{}

so the HTML in the end is:

<some_tag *myWrapperDirective />

Which renders as:

<my-wrapper-container>
  <div class="whatever">
    <some_tag />
  </div>
</my-wrapper-container>
Dan Field
  • 20,885
  • 5
  • 55
  • 71
  • 1
    Where does control come from in the @Input set method? Also, where does SubmissionGroup come from? I can't find any documentation on it. – Finnur Eiríksson Dec 06 '17 at 10:20
  • 1
    Wow. That's due to some bad copy pasting from the actual project I did this for. I'll clean it up when I have a few... – Dan Field Dec 07 '17 at 15:12
  • This is the relevant answer, can't understand the +7 on the other one... – n00dl3 Jan 22 '18 at 12:05
  • Thanks for the cleanup on this @n00dl3 - there was some hasty copy and paste work and I just keep losing track of this, I'm not working in Angular 4 as much ATM – Dan Field Jan 22 '18 at 15:25
  • What is `FormInputContainerComponent`? Should that be `WrapperContainerComponent` in this example? – Tyler Rick Feb 16 '18 at 01:39
  • I couldn't get it to work (copied exactly, except changing `FormInputContainerComponent` as mentioned above). No errors but It rendered as ``. Any idea what I did wrong? (Or have a working Plunkr?) – Tyler Rick Feb 16 '18 at 01:57
  • 1
    Getting closer... Helps when you use ` *myWrapperDirective` instead of `*myWraperDirective` :) Surprised it didn't give an error that it didn't recognize that name. – Tyler Rick Feb 16 '18 at 02:02
  • 1
    It looks like when you wrap an input in this way, `#` element references don't work correctly. For example, this always shows null for the errors: `
    {{ageRef?.errors | json}}
    ` It works if you wrap both the `` and the code that uses the element ref in the same `
    ` ... but that doesn't work if you need the element ref throughout the parent component. Any idea how to "export" the element ref for use outside of the directive?
    – Tyler Rick Feb 16 '18 at 17:22
  • There is any way to make it work with projection (ng-content) rather than *ngTemplateOutlet please? – Youness Houdass May 05 '20 at 18:55
9

You can achieve this with a component attribute selector and Angular 2 Content Projection <ng-content>

@Component({
  selector: 'my-app',
  template: `
    <div class="app"> 
        <div class="data" myWrapper>
            <h2>Header</h2>
            Content to be placed here.
        </div> 
    </div>
  `
})
export class AppComponent {}


@Component({
  selector: '[myWrapper]',
  template: `
    <div class="my-class">
      <div class="my-sub-class">
          <ng-content></ng-content>
      </div>
    </div>
  `
})
export class MyComponent {

}
Kld
  • 6,970
  • 3
  • 37
  • 50
  • 3
    This obviously applies only to elements that accept content. It won't work when used on an input element, for that matter. – user776686 Jul 08 '17 at 09:34
  • 7
    This doesn't actually seem to do what @Mikkel is asking. This will wrap the inside of the elemnt the [myWrapper] is placed on, not wrap that template around the info it is placed on. – Jessy Jul 24 '17 at 01:46
  • This works just fine. Used this with angular 7. But one more thing, instead of directive, use it as a component as [compname] is not recommended by angular style guide. – Swapnil Mhaske Feb 26 '19 at 11:35
  • 1
    This is not what is asked for - this wraps content of elemen with directive and NOT ACTUAL component with directive. In your example `
    ` should be wrapped.
    – Antoniossss Jun 10 '20 at 12:41