0
votes

The problem (Angular 7)

I would like to be able to "modify" the template of an overridden component without rewriting the whole template meaning it would share the same html template as the component it overrides. The goal is to be able to customize a component and that any change on the overriden component template would be directly reflected in the overriding component.

Maybe we could say that I want to share part of the logic and template between two components.

Example of current implementation

@Component({
  selector: 'component-a',
  templateUrl: './component-a.html'
})
export class ComponentAComponent {

  constructor() { }
}
@Component({
  selector: 'component-a',
  templateUrl: './component-a2.html'
})
export class ComponentA2Component extends ComponentAComponent {

 specificStuff = 'I am additional stuff';

  constructor() { }
}

component-a2.component.html

...
[ same as beginning of component-a.component.html]
...
<!-- Some specific stuff like an additional label, button, mat form field -->
<div>
  {{ specificStuff }}
</div>
...
[ same as end of component-a.component.html]
...

As we can see, if I change component-a.html it will not affect componentA2.

The goal

The goal would be that both component use the same template but final (compiled) DOM would be different. I do not want to do this with ngIf.

So something like

@Component({
  selector: 'component-a',
  templateUrl: './component-a.html'
})
export class ComponentAComponent {

  public specificStuff;

  constructor() { }
}
@Component({
  selector: 'component-a',
  templateUrl: './component-a.html'
})
export class ComponentA2Component extends ComponentAComponent {

 public name = 'World';
 public specificStuff = '<div> Hello {{name}} </div>';

  constructor() { }
}

component-a.component.html

<div>Yo</div>
<ng-template #specificStuff></ng-template>
<div>Bye</div>

Resulting template for componentA:

<div>Yo</div>
<ng-template #specificStuff></ng-template>
<div>Bye</div>

Resulting template for componentA2:

<div>Yo</div>
<ng-template #specificStuff><div> Hello {{name}} </div></ng-template>
<div>Bye</div>

What I've looked

innerHTML: would not work with angular interpolation, directives, etc.

ngComponentOutlet: would need the creation of another component and dealing with the context + I would like to avoid to include a layer with the component selector to avoid breaking the styling

ngTemplateOutlet: Template would still need to be passed somehow ? ( maybe this is what I need but not sure how to use it)

3
Maybe you want to use content projection. angular.io/guide/lifecycle-hooks#content-projectionAndrei Tătar
If I am not wrong, I would still need to change the template of the component in which my component is injected in order to set the content, so I would need to "override" this component too. It is not really what I want. I would like to be able to "customize" the component from the component itself.Tino
you can't do that without rewriting the template completely. Think of what angular knows at runtime about your template (if you do AOT).Andrei Tătar
Being impossible is an acceptable answer. I guess what I really need after research is somehow something like pug, nunjucks, EJS with template inheritance. Thanks a lot for your suggestions.Tino

3 Answers

0
votes

If I understand you want to share logic and state between components. To do that you can create a class that holds this properties.

For example:

export class FeatureCore {
    public stuff = 'stuff';
    public specificStuff = '<div> Hello {{name}} </div>';
}

...

export class ComponentA2Component extends FeatureCore implements OnInit {
    constructor() {
        super()
    }
    ngOnInit(): void {
    console.log(this.stuff); // 'stuff'
    }
}

This extends method should implemented in your other component. Consider use RxJS operators to get streams of values.

0
votes

You probably want to use angular content projection:

https://angular.io/guide/lifecycle-hooks#content-projection

https://stackblitz.com/edit/angular-projection-content-ab?file=src/app/app.component.ts

Otherwise you can pass a template as a parameter to component a. Keep in mind you can have multiple content projection slots.

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

@Component({
  selector: 'component-a',
  template: `

    <p> component A begining <p>
    <ng-content></ng-content>
    <p> component A ending <p>

    `,
})
export class ComponentA  {

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

@Component({
  selector: 'component-b',
  template: `
  <component-a>
  <h1>This is from component B</h1>
  </component-a>
  `,
})
export class ComponentB  {
   // you can inherit componentA if it makes things easier. I would keep them separate
}
0
votes

From my understanding what you need is to add some conditionals to solve your problem. You can have both cases in the same component.

Or is there a reason for needing two separate components?

You could do something similar to this and keep using templates to show the specific content.

<div>Yo</div>
<div *ngIf="yourVariable; else specificStuff">
    Hello, there is no specific stuff here.
</div>
<div>Bye</div>

<ng-template #specificStuff><div> Hello {{name}} </div></ng-template>