2

I've a test component that receives an array of objects via @Input(). I want to modify the data according to some internal logic / extra buttons but also allow the caller to decide how the data should be displayed.

Example of the component, with a trivial .toUpperCase() to make user names uppercase:

@Component({
  selector: 'test-component',
  template: `
    <h1>My Test</h1>
    <ng-content></ng-content>
  `
})
export class TestComponent implements OnInit {

  @Input() data?: Array<{ name: string }>;

  ngOnInit(): void {
     this.data.map((item: any) => {
        item.name = item.name.toUpperCase();
        return item;
     });
  }

Now is there a way I can, in the projected content, access the data that was provided to the component? Something like this:

<test-component [data]="[{name: 'joe'}, {name: 'mike'}]">
    <p *ngFor="let person of data">{{ person.name }}</p>
</test-component>

This would be very helpful to, for example, build a component where I could project a table and use *ngFor to render rows. The component itself would add pagination buttons and filter the data used by the *ngFor accordingly.

TCB13
  • 3,067
  • 2
  • 39
  • 68
  • If you want to access the data inside of the body of the component, you can use a template with a template variable that exposes the property or - store `[{name: 'joe'}, {name: 'mike'}]` in a property and iterate over that. –  Oct 18 '22 at 12:03

2 Answers2

1

Here is a solution for the problem.

Component:

@Component({
  selector: 'test-component',
  template: `
    <h1>My Test</h1>
    <ng-container
        [ngTemplateOutlet]="template"
        [ngTemplateOutletContext]=" {
           data: this.data
        }"
      ></ng-container>
  `
})
export class TestComponent implements OnInit {

  @Input() data?: Array<{ name: string }>;
  @ContentChild(TemplateRef) template: TemplateRef<any> | null = null;

  ngOnInit(): void {
     this.data.map((item: any) => {
        item.name = item.name.toUpperCase();
        return item;
     });
  }

Usage:

<test-component [data]="[{name: 'joe'}, {name: 'mike'}]">
    <ng-template let-data="data">
        <p *ngFor="let person of data">{{ person.name }}</p>
    </ng-template>
</test-component>
TCB13
  • 3,067
  • 2
  • 39
  • 68
0

That's not the right approach.

First approach, your TestComponent should not mutate your objects like that.
A component in Angular is used to display something to the user.
Mutating values should be done in services, then sent to components.

Next, your TestComponent should be in charge of displaying the mutated data.
Not the parent.
So you should send your mutated data to the child component, and not mutate it in the child.
If you want to have a reusable component, then you should mutate the content in the parent, not in the child, then pass this data to the child.

Finally, depending on your needs, you can simply use a pipe to perform this in a much more simpler, efficient, and reusable way. Consider this approach.

MGX
  • 2,534
  • 2
  • 14
  • I get and agree with your points and yeah I can use a `pipe` for a specifically very simple thing such as making the name uppercase, but it doesn't scale that well in the table example. – TCB13 Oct 18 '22 at 13:06