Building a wrapper directive (wrap some content / component) in angular2
Asked Answered
I

2

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 {


}
Immigration answered 15/12, 2016 at 13:32 Comment(0)
B
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>
Bio answered 18/8, 2017 at 2:37 Comment(9)
Where does control come from in the @Input set method? Also, where does SubmissionGroup come from? I can't find any documentation on it.Tody
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...Bio
This is the relevant answer, can't understand the +7 on the other one...Impress
Thanks for the cleanup on this @Impress - 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 ATMBio
What is FormInputContainerComponent? Should that be WrapperContainerComponent in this example?Bunny
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?)Bunny
Getting closer... Helps when you use ` *myWrapperDirective` instead of *myWraperDirective :) Surprised it didn't give an error that it didn't recognize that name.Bunny
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: <input type="text" name="age" #ageRef="ngModel" [(ngModel)]="age" required *myWrapperDirective /> <pre>{{ageRef?.errors | json}}</pre> It works if you wrap both the <input> and the code that uses the element ref in the same <div *myWrapperDirective> ... 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?Bunny
There is any way to make it work with projection (ng-content) rather than *ngTemplateOutlet please?Brinton
B
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 {

}
Blowzed answered 15/12, 2016 at 13:59 Comment(4)
This obviously applies only to elements that accept content. It won't work when used on an input element, for that matter.Chap
This doesn't actually seem to do what @Immigration 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.Darlinedarling
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.Fetishism
This is not what is asked for - this wraps content of elemen with directive and NOT ACTUAL component with directive. In your example <div class="data" myWrapper> should be wrapped.Cupronickel

© 2022 - 2024 — McMap. All rights reserved.