0

I have an Angular (Angular 9) parent component that holds a form. I want to abstract and generalize display of validation errors, so I have created a child component to handle validation error display. Specifically, I'm trying to assign a string to an ngIf to display an error message if one exists dynamically. Here is my template:

  <div class="alert">
    <div *ngFor="let i of InnerDisplayData">
      <div *ngIf="'i[0]'">
        {{ i[1] }} <br />
        {{ i[0] }}
      </div>
    </div>
  </div>

Here is an excerpt from my component file:

this.InnerDisplayData = Array.from(this.valMap, ([x, y]) => [
      "(fg | async).controls['" + this.field + "'].errors." + x,
      y,
    ]);

    const sub: Subscription = this.fg.subscribe((x) => {
      console.log(JSON.stringify(x.controls["FirstName"].errors));
    });

Where fg is @Input() fg: Observable<FormGroup>

fg is triggering correctly, and the 'required' validator is popping true when it should for the control. My template shows that the string being generated (i[0]) for the innerDataDisplay is this:

(fg | async).controls['FirstName'].errors.required

However, it appears that the ngIf is triggering because it is looking at a truthy string, rather than evaluating the string. Therefore, it is not reacting based on the conditions in the FormGroup and always displays the content. What am I doing wrong? Thanks!

tmptplayer
  • 434
  • 7
  • 18
  • Does it work if you remove the inner quotes in `"'i[0]'"`? – John Montgomery Jun 03 '20 at 00:00
  • It does not - had tried without the quotes originally, added as part of my debug process. – tmptplayer Jun 03 '20 at 00:10
  • Well, it definitely won't work with the extra quotes, because that's always going to be just the literal string "i[0]". Looking a little closer at what you're trying to do, I'm not sure it can work, you'll probably have to either process it in the HTML or build your array asynchronously. – John Montgomery Jun 03 '20 at 00:15
  • I think there are easier ways to get where you're trying to go. Do you need a component that shows error messages for any form control? Are you trying to list all error objects for a given control? – Chris Danna Jun 03 '20 at 04:49
  • I have a component that wraps and handles form controls... so I was looking for a component that abstracts the task of displaying those (or any given) form controls. – tmptplayer Jun 03 '20 at 14:57
  • Why was this item downvoted? Is there a problem with how I'm asking this question? – tmptplayer Jun 03 '20 at 19:58

1 Answers1

1

Angular doesn't really intend to be used the way you are trying to use it. It can certainly get you to where you want to go, but you don't need to dynamically generate strings as code at runtime inside template code. Maybe you can use eval() to do it this way but many would consider that a bad idea, and also unnecessary since you are using Angular.

It looks like what you have is a component that can take a FormGroup and control name and display all the validation errors for that control? If the validation messages are generic enough you can create a component that takes the FormControl as an input and the template can show the standard messages.

@Component({
  selector: 'app-error-messages',
  templateUrl: './error-messages.component.html',
  styleUrls: ['./error-messages.component.css']
})
export class ErrorMessagesComponent {
  @Input() control: FormControl;
}
<div *ngIf="control.errors.required">
  Required.
</div>
<div *ngIf="control.errors.min">
  Must be greater than {{ control.errors.min.min }}.
</div>
<div *ngIf="control.errors.max">
  Must be less than {{ control.errors.max.max }}.
</div>
<!-- more error messages... -->

From the code you posted I wasn't sure if you instead were looking for a component that didn't require using *ngIf on each possible validation type. You could also create an array of errors on the component and the use *ngFor over the array to display the message.

this.errors = Object.keys(this.control.errors || {})
  .map(key => ({
     name: key, // the Validation, e.g. "required", "minLength", "max", etc.
     value: this.control.errors[key] // the error object in case you need it later
  }));

I set up this stackblitz with an example of either component with a small form.

Chris Danna
  • 1,164
  • 8
  • 17
  • You're right... I really don't want to go eval(). I think looping over an array of errors may be a viable solution, but I need to explore it before I call it the answer. I don't want to refactor the entire form to organize by control for several reasons, including my current component structure for embedding controls and the data feeding it. Thanks for your help! – tmptplayer Jun 03 '20 at 15:02
  • If you post more details I would be able to tell whether or not you would need to refactor your entire form. At this time I don't see why the error-message component can't be a piece used to build your form wrapper. – Chris Danna Jun 03 '20 at 15:11
  • Your suggestion to iterate over the errors was good for me- I ended up creating an observable in the component based off of the form group state. The template just displayed whatever was in the component. – tmptplayer Jun 03 '20 at 19:58