Tips for adding and verifying arrays within forms using Angular2

Within my JavaScript model, this.profile, there exists a property named emails. This property is an array composed of objects with the properties {email, isDefault, status}.

Following this, I proceed to define it as shown below:

  this.profileForm = this.formBuilder.group({
    .... other properties here
    emails: [this.profile.emails]
  });

  console.log(this.profile.emails); //is an array
  console.log(this.profileForm.emails); // undefined

In the HTML file, I utilize it in the following manner:

    <div *ngFor="let emailInfo of profileForm.emails">
        {{emailInfo.email}}
        <button (click)="removeEmail(emailInfo)">
           Remove 
        </button>
    </div>

If I opt not to include it within the formGroup and use it purely as an array - as depicted below - everything works perfectly. However, I have a business requirement that dictates this array must not be empty, which complicates setting form validation based on the length.

  emails : [];
  this.profileForm = this.formBuilder.group({
    .... other properties here
  });
  
  this.emails = this.profile.emails;
  console.log(this.profile.emails); //is an array
  console.log(this.emails); // is an array

I also attempted utilizing formBuilder.array, but soon realized it's intended for arrays of controls rather than data arrays.

   emails: this.formBuilder.array([this.profile.emails])

Thus, my primary inquiry involves how best to bind an array from the model to the UI and how to effectively validate the array's length?

Answer №1

What is the best way to link an array from a model to the user interface?

In my view, it's most effective to transfer all the email data from profile.emails to the formArray to ensure both values and validation are retained.

How can I validate the length of an array?

To verify the length of an array, you can utilize the Validators.minLength(Number) function as you would for any other control.

Demonstration code:

Component:

export class AnyComponent implements OnInit {

  profileForm: FormGroup;
  emailsCtrl: FormArray;

  constructor(private formBuilder: FormBuilder) { }

  ngOnInit(): void {

    this.emailsCtrl = this.formBuilder.array([], Validators.minLength(ANY_NUMBER));
    this.profile.emails.forEach((email: any) => this.emailsCtrl.push(this.initEmail(email)));

    this.profileForm = this.formBuilder.group({
      // ... other controls
      emails: this.emailsCtrl
    });
  }

  private initEmail = (obj: any): FormGroup => {
    return this.formBuilder.group({
      'email': [obj.email], //, any validation],
      'isDefault': [obj.isDefault] //, any validation]
    });
  }
}

Template:

<div *ngFor="let emailInfo of emailsCtrl.value">
  {{emailInfo.email}}
  <button (click)="removeEmail(emailInfo)">
    Remove
  </button>
</div>
<div *ngIf="emailsCtrl.hasError('minlength')">
  It should have at least {{emailsCtrl.getError('minlength').requiredLength}} emails
</div>

Note: Ensure the parameter passed to the Validators.minLength(param) method is greater than 1 for proper validation.

You will observe in the source code that when the control is empty, it returns null automatically.

To address this, include the required Validator like so:

this.emailsCtrl = this.formBuilder.array([], Validators.compose([Validators.required, Validators.minLength(ANY_NUMBER > 1)]);

And in the template:

<div *ngIf="emailsCtrl.invalid">
  <span *ngIf="emailsCtrl.hasError('required')">
    It's required
  </span>
  <span *ngIf="emailsCtrl.hasError('minlength')">
    It should have at least {{emailsCtrl.getError('minlength').requiredLength}} emails
  </span>
</div>

Note:

To simplify removal of specific email items, consider passing the index in the removeEmail function instead of using indexOf each time. Example:

<div *ngFor="let emailInfo of emailsCtrl.value; let i = index">
  {{emailInfo.email}}
  <button (click)="removeEmail(i)">
    Remove
  </button>
</div>

Component:

removeEmail(i: number): void {
  this.emailsCtrl.removeAt(i);
}

Check out this DEMO for a simple demonstration.

Answer №2

I found success with this method (using angular version 2.1.2). By following this approach, you have the freedom to define a personalized validation for your email inputs:

 this.profileForm = this.formBuilder.group({
    emails: [this.profile.emails, FormValidatorUtils.nonEmpty]
    // ......
  });

export class FormValidatorUtils {

  static nonEmpty(control: any) {
    if (!control.value || control.value.length === 0) {
      return { 'noElements': true };
    }
    return null;
  }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for activating just a single event, whether it's 'change' or 'click'

I am facing an issue with a number input tag that has two event listeners 'on-click' and 'on-change'. Whenever I click on the arrow, both event listeners get triggered. I want only one event to trigger first without removing any of the ...

Is there a way to generate a distinctive curved design using CSS for a

I am currently using CSS and div elements in an attempt to create these particular lines: https://i.stack.imgur.com/Ytowq.png .line { width: 1px; height: 100px; background-color: black; position: absolute; border-radius: 50%/100px 1 ...

Tips for extracting unique values from two arrays and returning them in a new array using JavaScript

Hello, I need assistance with combining two arrays. Array a contains [1,2,3] and array b contains [2,5]. I would like the result array to only include elements that are unique between the two arrays, such as [5]. Can you please provide guidance on how to ...

What is the reason for Jquery AJAX appending additional path to the relative path?

I am encountering an issue with the following code snippet $.ajax({ url: "search/prefetch", success: function (data) { $(".result").html(data); alert("Load was performed."); }, dataType: "json" } ...

Rewriting Next.js with custom headers

My web app allows users to create their own profiles with custom usernames, which can be accessed via the following URLs. ourplatform.com/john ourplatform.com/john/about ourplatform.com/john/contact ourplatform.com/jane ourplatform.com/jane/about ourplatf ...

How to use the Angular 7 CLI in a programmatic way

My goal is to set up a server that can launch an Angular 7 app. Another scenario where this capability would be useful is for more advanced generation tasks, such as adding multiple files directly after generating an Angular project. const angularClient = ...

obtain data from JSON using JavaScript

Greetings! I am dealing with a JSON output that looks like this: "{ \"max_output_watts\": 150, \"frame_length_inches\": \"62.20\", \"frame_width_inches\": \"31.81\" }" I am using it in a functi ...

What is the correct way to pass the res object into the callback function of a jest mock function?

Currently, I am working on developing a web server using Node.js and am in the process of ensuring comprehensive test coverage with Jest. One specific function, logout, requires testing within the if statement where it checks for errors. // app.js functio ...

Exploring navigation options in VueJS with the router

I recently completed a tutorial on integrating Okta OAuth with VueJS. In my application, I have set up a default page that displays the "App.vue" component and switches to the "About.vue" component upon clicking the "/about" route. However, I encountered a ...

Adding a class to a clicked button in Vue.js

A unique function of the code below is showcasing various products by brand. When a user clicks on a brand's button, it will display the corresponding products. This feature works seamlessly; however, I have implemented a filter on the brands' lo ...

Consistent Errors with AJAX POST Requests Despite CORS Enablement

Here is a function I have created for making an ajax post request: function POST(url, data) { $.ajax({ 'type' : "POST", 'url' : url, 'data' : data, headers : { 'Access-Cont ...

Could you assist me in retrieving information from an API request?

I can't seem to pinpoint my mistake, but I know it's there. After the user provides their state and city information and submits it, a fetch request is supposed to retrieve latitude and longitude values. These values are then used in another API ...

The fixed position setting does not anchor the elements to the bottom of a container

When applying the following styles: #fullpage-menu > .gradient { position: fixed; bottom: 0; left: 0; width: 100%; height: 0.3rem; } To the element with ID #fullpage-menu, which is styled as follows: #fullpage-menu { height: 100 ...

What is the best way to extract information from a JSON XHR request and incorporate it into my template?

I am brand new to using Ember. Right now, my main goal is to connect to an API that provides random text and then show that text on a webpage. The specific API endpoint I am using is "" which will give back a response in JSON format. app/controllers/rando ...

Component declaration in Typescript is being rejected due to the union type not being accepted

In my current project, I'm working on a component that should accept either an onClick or a to prop. const StickyButton: React.FC< ({ onClick: MouseEventHandler } | { to: string }) & { buttonComponent?: ({ onClick: MouseEventHandler }) =& ...

The $resources headers have not been updated

My objective is to include a header with each HTTP request for an ngResource (specifically, an auth token). This solution somewhat accomplishes that: app.factory('User', ['$resource','$window', function($resource,$window,l ...

Having trouble transitioning to Angular2 RC? Let's chat at [email protected] - we can help!

I encountered an error while attempting to upgrade angular2 to RC. Due to JWT dependencies on RC, I had to switch to @angular. M:\workspace\Angular2StartKit>npm install npm ERR! addLocal Could not install M:\workspace\Angular2StartK ...

Using Typescript to specify the parameter type of a function as a generic function

After creating a function called compose, it looks like this: const composeTyped = <T, U, R>(f: (x: T) => U, g: (y: U) => R) => (x: T) => g(f(x)); It appears to me that both functions f and g fall under the type fGeneric, which is define ...

Derive the property type based on the type of another property in TypeScript

interface customFeatureType<Properties=any, State=any> { defaultState: State; properties: Properties; analyzeState: (properties: Properties, state: State) => any; } const customFeatureComponent: customFeatureType = { defaultState: { lastN ...

Typescript defines types for parameters used in callbacks for an event bus

Encountering a TypeScript error with our custom event bus: TS2345: Argument of type 'unknown' is not assignable to parameter of type 'AccountInfo | undefined'. Type 'unknown The event bus utilizes unknown[] as an argument for ca ...