I can't access properties of objects in HTML template - Angular

356 views Asked by At

I have two objects that handle errors in the form:

formErrors =
  {
    'firstname': '',
    'lastname': '',
    'telnum': '',
    'email': ''
  }

ValidationMessages = {
  'firstname':{
    'required': 'First name is required.',
    'minlength': 'First name must be at least two characters long.',
    'maxlength': 'First name must be at least twenty-five characters long.'
  },
  'lastname':{
    'required': 'Last name is required.',
    'minlength': 'Last name must be at least two characters long.',
    'maxlength': 'Last name must be at least twenty-five characters long.'
  },
  'telnum':{
    'required': 'Tel. number  is required.',
    'pattern': 'Tel. number must contain only numbers.'
  },
  'email':{
    'required': 'Email is required.',
    'email': 'Email not in valid format.'
  }
}

Here is a validator using these objects:

onValueChanged(data?: any){
  if (!this.feedbackForm) {return;}
  const form = this.feedbackForm;
  for (const field in this.formErrors){
    if (this.formErrors.hasOwnProperty(field)) {
      //clear previous error message (if any)
      this.formErrors[field] = ''
      const control = form.get(field);
      if (control && control.dirty && !control.valid){
        const messages = this.ValidationMessages[field];
        for (const key in control.errors){
          if (control.errors?.hasOwnProperty(key)){
            this.formErrors[field] += messages[key] + ' ';
          }
        }
      }
    }
  }
}

I've tried to get properties from my array object but I get the error: TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ firstname: string; lastname: string; telnum: string; email: string; }'. I need to type any ok

I've fixed this by typing my array with any, but I've got new errors like I can't get any properties from here

Does someone know how to solve it?

1

There are 1 answers

0
Carrm On

When you assign an object to a variable, Typescript types it on-the-fly. Taking your formErrors object for example, what you wrote is equivalent for Typescript to:

interface FormErrors {
    firstname: string;
    lastname: string;
    telnum: string;
    email: string;
}

const formErrors: FormErrors  = {
    firstname: '',
    lastname: '',
    telnum: '',
    email: ''
}

As you can see, if you used that kind of typing for your object you wouldn't expect to access any other property than the 4 you declared in the FormErrors interface. And that's what Typescript is telling you.

When you try and access your object's property through a variable ( formErrors[field] ), this variable could contain any possible string and not just the 4 keys allowed by your object's type. Typescript does not care that you're looping over the keys, that's only known to you at this point.

You have two options when facing this error:

  1. Make your type/object indexable
  2. Use the keyof operator to tell Typescript that your variable is a valid key

Indexable types

You make a type indexable by saying "this object can contain any key" making it possible to use a string variable to access an object's property.

Applied to the FormErrors example:

interface FormErrors {
    [key: string]: string;
    firstname: string;
    lastname: string;
    telnum: string;
    email: string;
}

Notice the line I've added before your properties: it's the index signature saying that FormErrors can contain a bunch of keys associated with string values. You could now write formErrors['banana'] and Typescript wouldn't say anything.

Note that this signature can be used on its own, I've kept the properties because it lets you write things like formErrors.lastname (instead of formErrors['lastname']) and get autocompletion.

Here is a random article that will explain you more about indexable types.

keyof operator

A better way to solve this in such cases where you know the exhaustive list of properties is to use the keyof operator to tell Typescript that your string variable can only contain a key that belongs to your type.

A valid loop for the FormErrors interface (without index signature) would be:

Object.keys(formErrors).forEach((key: keyof FormErrors) = {
    formErrors[key] = 'data';
});

Here is Typescript's documentation about keyof, and here is another random article to know more about it.