2

For a few days now Ive been trying to create a form which will alow a user to create a product, and at the same time variations of the product which may have a different price to the parent product. eg Large widget is £10, Small Widget is £5. What I end up with every time is a FormArray within a FormArray, in other word I have a product with an array of variations, those variations have a price, but also have an array of attributes as well. The problem comes when I try to add the controls, I can get the variation price to show up just fine, but I can't get the path in order to add the variations.attributes controls, I just get an error about ngFor only working with arrays not [Object, Object] or control is null... I've had more errors than I can remember! anyway onto my code, which has been re-written alot, and is probably worse than it started out!

first the form in my oninit:

ngOnInit() {
    this.getAttributes(); // Get Attribute Categories      
    this.form = this.fb.group({
        name: [''],
        price: [''],
        description: [''],
        stockRef: [''],
        attributes: this.fb.array([{
            attributeCategoryId: [''],
            name: [''],
        }]),
        variations: this.fb.array([{
            vprice: this.vprice,
            vattributes: this.vattributes
        }]),
    });
}

The section for adding and removing attributes for the Master Product, which works fine:

addAttribute(id: any, name: any) {
    if (!id.value || !name.value)
        return;
    this.attributes = <FormArray>this.form.get('attributes');
    var newAttribute = this.fb.group({
        attributeCategoryId: [id.value],
        name: [name.value],
    });
    this.newlist.push({ name: [name.value].toString(), attributeCategoryId: [id.value].toString() });
    this.attributes.push(newAttribute);

    id.value = '';
    name.value = '';
}
removeAttr(i: any) {
    this.attributes.removeAt(i);
    this.list2 = [];
    this.newlist.splice(i, 1);
}

The part where I add variations, which works, it still has the code which I used to try and copy the attributes added to the main product, into the variation, which worked I think, but just couldn't get access to the values in order to display them, variations.attributes didn't work for the path.

initVariation() {
    let v = this.fb.group({
        price: [''],
        vattributes: this.attributes //COPIES main attributes            
    });
    this.attributes.reset(); //Reset the main attributes as they now      
    return v;                //belong to a variation 
}
addNewVar() {
    const control = <FormArray>this.form.controls['variations'];
    control.push(this.initVariation());

}

The part that adds attributes to the variation, which does not work, and is where I'm having issues in my component.ts

addAttrRow() {
    const control = <FormArray>this.form.controls['variations.vattributes']
    control.push(this.initVattr())
}
initVattr() {
    let va = this.fb.group({
        vattributeCategoryId: [''],
        vname: ['']
    })
    return va;
}

Finally, my html which is even more of a mess lol:

<h1>New Product</h1>
<form [formGroup]="form" (ngSubmit)="save()">
    <div class="row">
        <div class="col-md-6 col-sm-12">
            <div class="form-group">
                <label>Product Name</label>
                <div *ngIf="!form.get('name').valid" class="alert alert-danger">
                    {{ form.get('name').getError('remote') }}
                </div>
                <input [(ngModel)]="name" type="text" formControlName="name" class="form-control">
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-6 col-sm-12">
            <div class="form-group">
                <label>Price</label>
                <div *ngIf="!form.get('price').valid" class="alert alert-danger">
                    {{ form.get('price').getError('remote') }}
                </div>
                <input [(ngModel)]="price" type="number" formControlName="price" class="form-control">
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-6 col-sm-12">
            <div class="form-group">
                <label>Product Description</label>
                <div *ngIf="!form.get('description').valid" class="alert alert-danger">
                    {{ form.get('description').getError('remote') }}
                </div>
                <textarea formControlName="description" class="form-control"></textarea>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-6 col-sm-12">
            <div>
                <h4>Attributes</h4>
                <div class="form-inline" >
                    <div class="form-group">
                        <div formArrayName="attributes">
                            <select #ac name="attributeCategoryId">
                                <option value="" selected>Category</option>
                                <option *ngFor="let a of attriblist;let i = index" value="{{a.id}}">{{a.name}}</option>
                            </select>
                            <input #a name="name" placeholder="Attribute name" />
                        </div>
                    </div>
                    <button type="button" class="btn btn-default" (click)="addAttribute(ac,a)">Add Attribute</button>
                </div>
                <br>
                <table class="table-bordered table table-striped">
                    <thead>
                        <tr>
                            <th>Attr. Category</th>
                            <th>Attr.</th>
                            <th><button type="button" (click)="addNewVar()" class="btn btn-primary">Add Variation</button></th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr *ngFor="let a of form.value.attributes; let i = index;"  >
                            <td *ngIf="i > 0">{{a.attributeCategoryId}}</td>
                            <td *ngIf="i > 0">{{a.name}}</td>
                            <td *ngIf="i > 0"><button (click)="removeAttr(i)" class="btn btn-danger">X</button></td>
                        </tr>
                    </tbody>
                </table>

            </div>
        </div>
    </div>
    <!--Variations Start-->
    <div class="row" formArrayName="variations">
        <div *ngFor="let variation of form.controls.variations.controls; let i=index" [formGroupName]="i">
            <h5>Variation #{{ i + 1 }}</h5>
            <p></p>
            <div class="form-group">
                <label>Variation Price</label>
                <input name="vprice" style="max-width:50px" class="form-control">
            </div>
            <table>
                <thead>
                    <tr>
                        <th>Attr. Category</th>
                        <th>Attr.</th>
                        <th><button class="btn btn-success" (click)="addAttrRow()">+</button></th>
                    </tr>
                </thead>
                <tbody name="vattributes">
                    <tr *ngFor="let attribute of variation.get('vattributes'); let ii = index;">
                        <td><input type="text" name="vattributeCateforyId" /></td>
                        <td><input type="text" name="vname" /></td>
                        <td><button (click)="removeVAttr(ii)" class="btn btn-danger">X</button></td>
                    </tr>
                </tbody>
            </table>
            <button class="btn btn-danger" (click)="removeVariation(i)">Delete</button>

        </div>
        </div>
    <!--Variations End-->
    <br>
    <p>
        <button type="submit" class="btn btn-primary">Save</button>
    </p>
</form>
2
  • I have doubt about how your attributes and variations should be linked but we can start with this plnkr.co/edit/9FcYTmz15t7hkVlTtBdD?p=preview I remove attributes list after user adds new variation with these attributes Commented Aug 21, 2017 at 4:34
  • @yurzui Ahh so don't add the controls / arrays in the declaration, and when adding the attribute rows to the variation use "const control = <FormArray>this.form.get(['variations', index, 'vattributes']);" (I missed the index from the path) Now I understand the error when I was getting cant find control with variations > 0 > vattributes! Thank you. If you can make this an answer I will mark it accordingly. Commented Aug 21, 2017 at 10:56

1 Answer 1

3

I see a lot of mistakes in your code.

For example

1) you should be aware that we need not to forget about index when deal with getting control from FormArray

So instead of

const control = <FormArray>this.form.controls['variations.vattributes'];

we should use

addAttrRow(index) {
  const control = <FormArray>this.form.get(['variations', index, 'vattributes']);

2) If you don't specify type for button it will have submit as default value.

<button class="btn btn-success" (click)="addAttrRow()">+</button>

and it can lead to unpredictable situations.

So try to specify type="button"

3) You iterate over object

*ngFor="let attribute of variation.get('vattributes');

while you need to iterate over array

*ngFor="let variation of form.controls.variations.controls;

I created Plunker Example that can help you to realize what you did wrong

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

Comments

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.