1

Most commonly you would set the mat-option text in a way like this.

<mat-option [value]="type.key">{{ type.value }}</mat-option>

The thing is I have a directive that processes the string, before setting it on the element's inner text. Which overrides the HTML of the mat option.

<mat-option [value]="type.key" [appTransform]="type.value"></mat-option>

So is there a way to set the interpolated value via the directive?

1 Answer 1

1

You can use Renderer2 and ElementRef to transform the text and insert it using appendChild method.

@Directive({
  selector: '[appTransform]',
  standalone: true,
})
export class AppTransformDirective {
  str: InputSignal<string> = input.required<string>({
    alias: 'appTransform',
  });
  renderer = inject(Renderer2);
  element = inject(ElementRef);
  ngOnInit() {
    const transformed = `${this.str()} car`;
    const text = this.renderer.createText(transformed);
    const elementToInsertIn = this.element.nativeElement.querySelector(
      '.mdc-list-item__primary-text'
    );
    this.renderer.appendChild(elementToInsertIn, text);
  }
}

Full Code:

import {
  Component,
  Directive,
  input,
  InputSignal,
  ElementRef,
  Renderer2,
  inject,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatFormFieldModule } from '@angular/material/form-field';

interface Food {
  value: string;
  viewValue: string;
}

@Directive({
  selector: '[appTransform]',
  standalone: true,
})
export class AppTransformDirective {
  str: InputSignal<string> = input.required<string>({
    alias: 'appTransform',
  });
  renderer = inject(Renderer2);
  element = inject(ElementRef);
  ngOnInit() {
    const transformed = `${this.str()} car`;
    const text = this.renderer.createText(transformed);
    const elementToInsertIn = this.element.nativeElement.querySelector(
      '.mdc-list-item__primary-text'
    );
    this.renderer.appendChild(elementToInsertIn, text);
  }
}

/**
 * @title Basic select
 */
@Component({
  selector: 'select-overview-example',
  templateUrl: 'select-overview-example.html',
  imports: [
    MatFormFieldModule,
    MatSelectModule,
    MatInputModule,
    FormsModule,
    AppTransformDirective,
  ],
})
export class SelectOverviewExample {
  cars: string[] = ['volvo', 'saab', 'mercedes', 'audi'];
}

Stackblitz Demo

Sign up to request clarification or add additional context in comments.

6 Comments

Great answer! But I wasn't clear enough about this being applicable to any element or component. So for mat select this definitely works, but if I want to use this directive for another mat component, then I would have to select the specific element I want to append to like you did with ".mdc-list-item__primary-text". So that's the main issue. I'd like the input value to be inserted wherever it would be inserted as if it was interpolated like in the first example. Hope this is clear. Thanks!
@Zachu You have to write specific code to insert for each scenario (because each scenario has a different DOM structure, post initialization), it cannot insert like that, else just change your code to {{ transform(type.value) }}
If the transformation is pure, changing the directive into a pipe before using it in interpolation would prevent unnecessary rerendering (class AppTransformPipe implements PipeTransform {} then {{ type.value | appTransform }} ).
@Zachu This is the correct answer, use a pipe as DM said, directive is not a best for this scenario
Issue is that I would have to manually re-trigger the pipe based on a global signal, and I don't know how to do that since the signal is not part of the pipe's arguments.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.