10

Is there anyway to change this:

<div>Some text here</div>

To:

<tr>Some text here</tr>

Change the tag DIV tag to TR tag dynamically? Basically Replace the Tags?

edumas000
  • 272
  • 1
  • 3
  • 10

5 Answers5

13

you could create custom structure directive to do this.

Online demo here

replace-tag.directive.ts

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

@Directive({
  selector: '[replaceTag]'
})
export class ReplaceTagDirective implements AfterViewChecked {
  constructor(
    private templateRef: TemplateRef<any>,
    private vcf: ViewContainerRef
  ) { }
  private _tag: string;
  private _needUpdate: boolean = false;

  @Input('replaceTag')
  set tag(t: string): void {
    this._tag = t;
    this._needUpdate = true;
    this.vcf.clear();
    let template = this.templateRef.elementRef.nativeElement.nextElementSibling;
    if (template) {
      this.templateRef.elementRef.nativeElement.parentNode.removeChild(template);
    }
    this.vcf.createEmbeddedView(this.templateRef);
  }

  ngAfterViewChecked() {
    if (this._needUpdate) {
      this._updateTemplate();
      this._needUpdate = false;
    }
  }

  private _updateTemplate() {
    let template = this.templateRef.elementRef.nativeElement.nextElementSibling;
    if (template) {
      let r = document.createElement(this._tag);
      r.innerHTML = template.innerHTML;
      this.templateRef.elementRef.nativeElement.parentNode.replaceChild(r, template);
    }
  }
}

app.component.ts

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

@Component({
  selector: 'app-root',
  template: `
    App
    <span *replaceTag="tag">
      <strong>content {{ tag }} </strong>
      <em>{{ message }}</em>
    </span>
  `
})
export class AppComponent implements OnInit {
  tag: string = 'div';
  timeOut: number = 2;

  get message(): string {
    return this.timeOut? `<- this tag will change after ${this.timeOut} seconds.` : 'done';
  }

  ngOnInit() {
    setTimeout(() => {
      this.tag = 'h2';
      this.timeOut = 4;
    }, 2000);

    setTimeout(() => {
      this.tag = 'sub';
      this.timeOut = 0;
    }, 4000);
  }
}
Tiep Phan
  • 12,386
  • 3
  • 38
  • 41
  • Hey Thanks!! I Actually was thinking more on the Lines of some type of wrapper component .. `
    Change Tags
    ' `@Input() tagName: string = 'div';` `constructor(private elem: ElementRef, private renderer: Renderer) {}` `ngAfterContentInit() { const NGCONTENT = this.elem.nativeElement.childNodes[1]; const HTMLTAG = this.renderer.createElement(this.elem.nativeElement, this.tagName); HTMLTAG.appendChild(NGCONTENT); }` But I really like the Directive.... Direction I will def use this solution!! Thanks.
    – edumas000 Feb 05 '17 at 09:13
  • 2
    That works great. But what if I want to keep the other attrs when I change the tag ( [ngStyle], Id, etc )? – atcastroviejo Apr 22 '17 at 22:23
  • Awesome! Great way to ensure the integrity of headings in the DOM when reusing components. Made 2 adjustments. The original version was deleting 2 components instead of 1. I removed the "if (template)" block from "set tag" function, since the "replaceChild" call in the "_updateTemplate" replaces the originally targeted element. The second adjustment was to copy the attributes from the original tag over into the new tag. This helps with CSS styling https://plnkr.co/edit/wCmTuctSdYK43qtzLwti?p=preview – reidreid46 Sep 21 '19 at 20:50
  • @reidreid46 You have a bug in your plnkr, you are duplicating elements. – Edgar Quintero Apr 24 '20 at 19:11
  • @EdgarQuintero interesting, could you point me towards the line in the code that's duplicating? Or is there a code snippet where the element isn't duplicated? – reidreid46 May 02 '20 at 20:09
5

We can use Grochni's approach to provide us a dynamic html element container using ng-template.

<ng-container [ngTemplateOutlet]="showTr ? tr : div" [ngTemplateOutletContext]="{ $implicit: content }">
</ng-container>
<ng-template #content>
  Some text here
</ng-template>

<ng-template #tr let-content>
  <tr><ng-container [ngTemplateOutlet]="content"></ng-container></tr>
</ng-template>

<ng-template #div let-content>
  <div><ng-container [ngTemplateOutlet]="content"></ng-container></div>
</ng-template>
4

Here is another way to avoid duplication. I needed to use a different tag depending on some condition:

Create template which both tags will use:

<ng-template #tagTemplate>
      <h1>Hello and Hi</h1>
      <p>Bye</p>
</ng-template>

Define both tags and use ngIf. The content of the tags will be the template created above.

<div class="post-widget" *ngIf="data">
    <ng-container *ngTemplateOutlet="tagTemplate;"></ng-container>
</div>

<ion-content class="post-widget" *ngIf="!data">
    <ng-container *ngTemplateOutlet="tagTemplate;"></ng-container>
</ion-content>
Sumama Waheed
  • 3,579
  • 3
  • 18
  • 32
  • What I'm about to describe won't affect 95% of people attempting this method, but if you're doing complex dependency injection you may run into this: Lets say `ion-content` has a provider defined by its component, maybe `TagService`. That service **won't** be available to anything in `#tagTemplate` template because components inherit the dependency tree where they are defined. If you find yourself in a situation like this it is possible to create a directive and apply it directly to the `ng-template` to provide the service (or provide it in whatever component all of this is in). – Simon_Weaver Nov 22 '21 at 06:33
0

you can use *ngIf on each one of them

<div *ngIf="someFunction()">Some text here</div>
<tr *ngIf="someFunction()">Some text here</tr>
Yoav Schniederman
  • 5,253
  • 3
  • 28
  • 32
  • 2
    Hey thanks! .. I was doing this Solution a lot, But I'm really trying hard to keep my code DRY (Don't Repeat Yourself).. – edumas000 Feb 05 '17 at 09:05
-3

Try this, where you manage (bool) show_div in your controller.

//Js
$scope.show_div = true;

<!--Html-->
<div ng-if="show_div">Some text here</div>
<tr ng-if="!show_div">Some text here</tr>
Muhit
  • 789
  • 1
  • 7
  • 17