Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error while binding array #25

Open
yonathan06 opened this issue May 29, 2017 · 4 comments
Open

Error while binding array #25

yonathan06 opened this issue May 29, 2017 · 4 comments

Comments

@yonathan06
Copy link

yonathan06 commented May 29, 2017

This is a...

  • bug report

What toolchain are you using for transpilation/bundling?

  • angular/cli

Environment

NodeJS Version: 6.10
Typescript Version: 2.3.0
Angular Version: 4.0.3
angular-redux/store version: 6.2.0
angular/cli version: 1.0.0-beta.32.3
OS: windows 10

Getting error Cannot read property 'arrivalTime' of undefined

The problematic areas are surrounded by comment ERROR HERE

my html:

<div class="form-panel">
  <div class="steps-panel">
    <div class="step" *ngFor="let step of steps; let i = index; let isLast = last" [ngClass]="{current: i + 1 == (addClientForm$ | async)?.currentPosition, pass: i + 1 < (addClientForm$ | async)?.currentPosition}">
      <div class="circle-holder">
        <div class="circle">
          <span *ngIf="i + 1 >= (addClientForm$ | async)?.currentPosition">{{i + 1}}</span>
          <md-icon *ngIf="i + 1 < (addClientForm$ | async)?.currentPosition">done</md-icon>
        </div>
        <div class="label">{{step}}</div>
      </div>
      <div *ngIf="!isLast" class="line"></div>
    </div>
  </div>
  <ng-container [ngSwitch]="(addClientForm$ | async)?.currentPosition">
    <form class="form" *ngSwitchCase="1" [connect]="['hqAddClientForm', 'clientDetails']">
      <div class="regular-input-holder">
        <div class="label">שם העסק</div>
        <input type="text" name="businessName" ngControl ngModel>
      </div>
      <div class="regular-input-holder">
        <div class="label">מספר העסק</div>
        <input type="text" name="businessNumber" ngControl ngModel>
      </div>
      <div class="regular-input-holder">
        <div class="label">כתובת</div>
        <input type="text" name="address" ngControl ngModel>
      </div>
      <div class="regular-input-holder">
        <div class="label">טלפון</div>
        <input type="text" name="phone" ngControl ngModel>
      </div>
      <div class="regular-input-holder">
        <div class="label">פקס</div>
        <input type="text" name="fax" ngControl ngModel>
      </div>
      <div class="save-and-continue">
        <button md-raised-button class="md-primary-button" (click)="saveContinue()">
          <md-icon>done</md-icon>שמור והמשך
        </button>
      </div>
    </form>
    <form class="form" *ngSwitchCase="2" [connect]="['hqAddClientForm', 'paymentDetails']">
      <div class="regular-input-holder">
        <div class="label">אופן תשלום</div>
        <input type="text" name="paymentMethod" ngControl ngModel>
      </div>
      <div class="regular-input-holder">
        <div class="label">אחוזי הנחה</div>
        <input type="text" name="discountPercentage" ngControl ngModel>
      </div>
      <div class="save-and-continue">
        <button md-raised-button class="md-primary-button" (click)="saveContinue()">
          <md-icon>done</md-icon>שמור והמשך
        </button>
      </div>
    </form>
    <ng-container *ngSwitchCase="3">
      <form class="form" [connect]="['hqAddClientForm', 'managers', 'tmp']">
        <div class="regular-input-holder">
          <div class="label">שם ושם משפחה</div>
          <input type="text" name="firstAndLastName" ngControl ngModel>
        </div>
        <div class="regular-input-holder">
          <div class="label">אימייל</div>
          <input type="email" name="email" ngControl ngModel>
        </div>
        <div class="regular-input-holder">
          <div class="label">נייד</div>
          <input type="phone" name="phone" ngControl ngModel>
        </div>
        <div class="save-and-continue">
          <button md-raised-button class="md-primary-button" (click)="addManager()">
            <md-icon>add</md-icon>הוספת מנהל
          </button>
        </div>
      </form>
      <div class="managers">
        <div class="manager" *ngFor="let manager of (addClientForm$ | async)?.managers?.managers">
          <div class="info">
            <div class="full-name">{{manager.firstAndLastName}}</div>
            <div class="phone-email">
              <span>{{manager.phone}}</span>
              <span>{{manager.email}}</span>
            </div>
          </div>
          <md-icon class="edit">edit</md-icon>
        </div>
      </div>
      <div class="save-and-continue">
        <button md-raised-button class="md-primary-button" (click)="saveContinue()">
          <md-icon>done</md-icon>שמור והמשך
        </button>
      </div>
    </ng-container>
    <div class="system-type form" *ngSwitchCase="4">
      <div class="switch-buttons">
        <div class="button-option" [ngClass]="{clicked: (addClientForm$ | async)?.clientSystemType?.type === 'shifts'}" (click)="toggleSystemType()">משמרות</div>
        <div class="button-option" [ngClass]="{clicked: (addClientForm$ | async)?.clientSystemType?.type === 'general'}" (click)="toggleSystemType()">כללי</div>
      </div>
      <form *ngIf="(addClientForm$ | async)?.clientSystemType?.type === 'general'" class="form" [connect]="['hqAddClientForm', 'clientSystemType', 'general']">
        <div class="regular-input-holder">
          <div class="label">סוג לוח</div>
          <input type="text" name="calendarType" ngControl ngModel>
        </div>
      </form>
      <form *ngIf="(addClientForm$ | async)?.clientSystemType?.type === 'shifts'" class="form" [connect]="['hqAddClientForm', 'clientSystemType', 'shiftsType']">
        <!-- ERROR HERE -->
        <ng-template connectArray let-index connectArrayOf="shifts">
          <div class="regular-input-holder" [ngModelGroup]="index">
            <div class="header">משמרת {{index + 1}}</div>
            <select ngControl ngModel name="arrivalTime">
              <option *ngFor="let num of times" [value]="num">{{num}}</option>
            </select>
            <select ngControl ngModel name="departureTime">
              <option *ngFor="let num of times" [value]="num">{{num}}</option>
            </select>
          </div>
        </ng-template>
       <!-- END ERROR HERE -->
      </form>
      <div class="save-and-continue">
        <button md-raised-button class="md-primary-button" (click)="saveContinue()">
          <md-icon>done</md-icon>שמור והמשך
        </button>
      </div>
    </div>
  </ng-container>
</div>

The rest of the forms are working fine

here is my interface:

export interface HQAddClientForm {
	currentPosition: number,
	clientDetails: {
		businessName: string,
		businessNumber: number,
		// belongsTo: string,
		// businessType: string,
		address: string,
		phone: string,
		fax: string,
	},
	paymentDetails: {
		paymentMethod: string,
		discountPercentage: number
	},
	managers: {
		tmp: HQClientManager,
		managers: HQClientManager[]
	},
	clientSystemType: {
		type: 'general' | 'shifts',
		generalType: {
			calendarType: string
		},
               // --- ERROR HERE ---
		shiftsType: {
			numberOfShifts: number,
			shifts: {
				arrivalTime: string,
				departureTime: string
			}[]
		}
               // --- END ERROR HERE ---
	},
	routes: {
		tmp: HQClientRoute,
		routes: HQClientRoute[]
	}
}

export interface HQClientManager {
	firstAndLastName: string,
	email: string,
	phone: string
}

export interface HQClientRoute {
	origin: string[],
	destination: string[],
	totalKM: number,
	permanentDriver: {
		driverKey: string,
		name: string
	},
	priceExpiritionDate: string,
	driverPayment: {
		amount: number,
		incluedTax: boolean
	},
	totalPayment: {
		amount: number,
		incluedTax: boolean
	}
}

export const initHQAddClientForm: HQAddClientForm = {
	currentPosition: 1,
	clientDetails: {
		businessName: null,
		businessNumber: null,
		// belongsTo: null,
		// businessType: null,
		address: null,
		phone: null,
		fax: null,
	},
	paymentDetails: {
		paymentMethod: null,
		discountPercentage: null
	},
	managers: {
		tmp: {
			firstAndLastName: null,
			email: null,
			phone: null
		},
		managers: []
	},
	clientSystemType: {
		type: 'shifts',
		generalType: {
			calendarType: null
		},
               // --- ERROR HERE ---
		shiftsType: {
			numberOfShifts: null,
			shifts: [{
				arrivalTime: null,
				departureTime: null
			}]
		}
               // --- END ERROR HERE ---
	},
	routes: {
		tmp: {
			origin: [],
			destination: [],
			totalKM: 0,
			permanentDriver: {
				driverKey: null,
				name: null
			},
			priceExpiritionDate: null,
			driverPayment: {
				amount: 0,
				incluedTax: null
			},
			totalPayment: {
				amount: 0,
				incluedTax: null
			}
		},
		routes: []
	}
}

this is the stack trace:

core.es5.js:1084 ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'arrivalTime' of undefined
TypeError: Cannot read property 'arrivalTime' of undefined
    at http://localhost:4200/vendor.bundle.js:30431:22
    at http://localhost:4200/vendor.bundle.js:30356:66
    at Array.forEach (native)
    at FormGroup._forEachChild (http://localhost:4200/vendor.bundle.js:30356:36)
    at FormGroup._checkAllValuesPresent (http://localhost:4200/vendor.bundle.js:30430:14)
    at FormGroup.setValue (http://localhost:4200/vendor.bundle.js:30237:14)
    at http://localhost:4200/vendor.bundle.js:30936:18
    at ZoneDelegate.invoke (http://localhost:4200/vendor.bundle.js:156556:26)
    at Object.onInvoke (http://localhost:4200/vendor.bundle.js:8978:37)
    at ZoneDelegate.invoke (http://localhost:4200/vendor.bundle.js:156555:32)
    at http://localhost:4200/vendor.bundle.js:30431:22
    at http://localhost:4200/vendor.bundle.js:30356:66
    at Array.forEach (native)
    at FormGroup._forEachChild (http://localhost:4200/vendor.bundle.js:30356:36)
    at FormGroup._checkAllValuesPresent (http://localhost:4200/vendor.bundle.js:30430:14)
    at FormGroup.setValue (http://localhost:4200/vendor.bundle.js:30237:14)
    at http://localhost:4200/vendor.bundle.js:30936:18
    at ZoneDelegate.invoke (http://localhost:4200/vendor.bundle.js:156556:26)
    at Object.onInvoke (http://localhost:4200/vendor.bundle.js:8978:37)
    at ZoneDelegate.invoke (http://localhost:4200/vendor.bundle.js:156555:32)
    at resolvePromise (http://localhost:4200/vendor.bundle.js:156934:31)
    at http://localhost:4200/vendor.bundle.js:156985:17
    at ZoneDelegate.invokeTask (http://localhost:4200/vendor.bundle.js:156589:31)
    at Object.onInvokeTask (http://localhost:4200/vendor.bundle.js:8969:37)
    at ZoneDelegate.invokeTask (http://localhost:4200/vendor.bundle.js:156588:36)
    at Zone.runTask (http://localhost:4200/vendor.bundle.js:156356:47)
    at drainMicroTaskQueue (http://localhost:4200/vendor.bundle.js:156749:35)
    at <anonymous>

Thought

When I change the value in the problematic (with name "arrivalTime"), I see change in my store, but it creates a new object 0 in shiftType. perhaps the ref tree isn't right, but I checked it multiple times and seems it is like writing in the docs. Thanks

@jkachurek
Copy link

I am also experiencing this in a similar scenario. I have a parent form that is nested three layers deep in the state tree, which contains an array of child subforms. During the FORM_CHANGED action, the changes from the child form are placed in the parent form instead. Below I'll put a simplified version of the form, which should illustrate the general problem.
I have a parent form at the path: ['workflow', 'page1', 'form'].
It contains an array of child forms, childForms, which is therefore at the path ['workflow', 'page1', 'form', 'childForms'].
This child array is populated with a default first value before the page loads, so my state tree before the FORM_CHANGED event ostensibly looks like the following:

{
  workflow: {
    page1: {
      form: {
        input1: "loadedInput",
        childForms: [
          {
            childInput1: "loadedChildInput1"
          }
        ]
      }
    }
  }
}

Following this generic example, in the HTML template, I have:

<form [connect]="['workflow', 'page1', 'form']">
  <input type="text"
    name="input1"
    ngControl ngModel>
  <ng-template connectArray let-index connectArrayOf="childForms">
    <div class="row" [ngModelGroup]="index">
      <input type="text"
        name="childInput1"
        ngControl ngModel>
    </div>
  </ng-template>
</form>

NOTE: While the documentation shows the connect directive invoked without the brackets, I have tried it with and without them and the same error occurs in either case.
After the FORM_CHANGED event that occurs on the page load, the state tree from above looks like the following:

{
  workflow: {
    page1: {
      form: {
        0: {
          childInput1: "loadedChildInput1"
        },
        input1: "loadedInput",
        childForms: [
          {
            childInput1: "loadedChildInput1"
          }
        ]
      }
    }
  }
}

Right before this initial FORM_CHANGED event, I see the exact same error that @yonathan06 posted (with a different field name, obviously).
So, to me, it seems that the reducer is posting the changed input value to the parent state, not the child state. Perhaps this is not a problem if the parent state is at the root of the state tree, as is the case in the example from the docs? But even if that is the case, it should be fixed so it works with nested states as well.

@aguerere
Copy link

I am having this issue as well. The parent state is nested one level down the state tree, so it looks like this is the source of the problem. Any clues? Thanks!

@el-davo
Copy link

el-davo commented Sep 4, 2017

@jkachurek if you do the following you can get it at the right level

<form [connect]="['workflow', 'page1', 'form']">
  <input type="text"
    name="input1"
    ngControl ngModel>
  <ng-template connectArray let-index connectArrayOf="childForms">
    <div [ngModelGroup]="'childForms'"> <!--  <-- Add this-->
        <div class="row" [ngModelGroup]="index">
          <input type="text"
            name="childInput1"
            ngControl ngModel>
        </div>
    </div>
  </ng-template>
</form>

This works, However if your loading dyncamic content from the server it always seems to set your values back to empty. But at least its poiting at the correct level

@methgaard
Copy link

methgaard commented Mar 7, 2018

Hi guys. Im facing the very same issue, only somewhat later. Are there any solution to this? @el-davo's solution didn't work for me :/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants