FormArray getting ExpressionChangedAfterItHasBeenCheckedError - angular6

I have a FormArray where I am passing an IsEditMode:boolean flag to toggle the view from Edit Mode to Read-Only mode.
When I change the value of IsEditMode, I am getting error
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: false'. Current value: 'ngIf: true'
I tried looking for an example but was not able to find anything. Please suggest if there is a better way to do this as well.
createForm(): void {
this.itemForm = this.fb.group({
items: this.fb.array([])
});
}
createItem(jp: Item & EditMode): FormGroup {
return this.fb.group({
id: jp.id,
profileId: jp.profileId,
name: [jp.name, Validators.required],
isEditMode: [jp.isEditMode]
});
}
addNewItem() {
this.items = this.jobPreferenceForm.get(
'items'
) as FormArray;
this.items.push(
this.crateItem({
id: 0,
profileId: '',
name: '',
isEditMode: true
})
);
}
<table class="table table-condensed table-striped table-hover">
<tbody>
<tr formArrayName="items" *ngFor="let item of itemForm.get('items').controls; let i = index;">
<td>
<span *ngIf="!item.value.isEditMode">{{item.value.name}}</span>
<div *ngIf="item.value.isEditMode" [formGroupName]="i">
<input type="text" class="form-control" formControlName="name">
</div>
</td>
<td>
<a *ngIf="!item.value.isEditMode" (click)="item.value.isEditMode = !item.value.isEditMode">Edit</a>
<a *ngIf="item.value.isEditMode" class="text-muted mr-2" [attr.disabled]="!item.valid" (click)="cancel(item)">Cancel</a>
<a *ngIf="item.value.isEditMode" class="text-success mr-2" (click)="save(item)">Save</a>
<a *ngIf="item.value.isEditMode" class="text-danger">Delete</a>
</td>
</tr>
</tbody>
</table>

Related

Regarding Angular PrimeNG table formArray

I am trying to create the PrimeNg editable table with angular forms. I don't understand how to use formArray. I tried so many times but I was struck with formGroupName error.
This is current behavior
※ Actual behavior ⇒ only touched and invalid component(Input field) border should output in red color.
※ I think here we should use formArray to check the validation.
※ Add Row button in another component.
HTML
<p-table [loading]="loading" styleClass="text-sm " [ngClass]="{'p-datatable-is-loading': loading}" [value]="dataSource"
[scrollable]="true" scrollHeight="calc(100% + 2px)" scrollDirection="both">
<ng-template pTemplate="header">
<tr>
<th style="width: 250px;">Project</th>
<th style="width: 250px;">Process</th>
<th style="width: 200px;">Working Hours</th>
<th style="width: 168px;">Billed To</th>
<th style="width: 350px;">What did you work on?</th>
<th style="width: 350px;">Blockers</th>
<th alignFrozen="right" pFrozenColumn class="p-frozen-column p-frozen-border-left" style="width: 114px;"></th>
</tr>
</ng-template>
<ng-template pTemplate="body" [formGroup]="_formHelper.form" let-source let-index="rowIndex">
<tr *ngIf="dataSource[index].editing; else viewingMode;" class="editing editing-mode">
<td style="width: 250px; overflow: visible">
<p-autoComplete appendTo="body" #project name="project" [(ngModel)]="dataSource[index].project_id" [suggestions]="projectSuggestions"
field="name" (onSelect)="handleSelectProject($event, index)"
(onBlur)="_formHelper.validateControl('project')" formControlName="project"
(completeMethod)="handleSearchProject($event, index)" (onFocus)="project.show()" placeholder="Search project">
</p-autoComplete>
</td>
</tr>
</ng-template>
</p-table>
TS
export class DailyReportRegularTableComponent extends DailyReportLog implements OnInit {
helper!: HelperService
_formHelper: FormService = new FormService;
_model: UIProjectLogForm = {
id: '',
process: '',
minutes: undefined,
hour: undefined,
work_note: '',
blocker_note: '',
daily_time_log_id: '',
billed_to: '',
project_id: '',
project_name: '',
};
constructor(private _helper: HelperService,private formBuilder: FormBuilder) {
super();
this.helper = this._helper;
this.initForm();
}
initForm() {
this._formHelper.form = this.formBuilder.group({
project: new FormControl(this._model.project_id, {
validators: Validators.required,
updateOn: 'blur'
})
});
}
}

How to change boolean value within json file using a button in angular & typescript?

My question is, how do I write a function in my table-viewer.component.ts file to change the value from false to true within the JSON file based on whether the user clicks the cancelled button?
I have a JSON file containing the following details.
db.json
"firstName": "Hamza",
"lastName": "Gallagher",
"carReg": "FG67 POI",
"parkingNumber": "00003",
"date": "2021-01-04",
"cancelled": false
etc ..
And I am displaying them in a table with angular, which shows the following:
Image of table
Table-viewer.component.html
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Car Registration</th>
<th>Parking Number</th>
<th>Date</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let booking of bookings">
<td>{{booking.firstName}}</td>
<td>{{booking.lastName}}</td>
<td>{{booking.carReg}}</td>
<td>{{booking.parkingNumber}}</td>
<td>{{booking.date}}</td>
<td>
<div *ngIf="booking.cancelled" class="red">Cancelled</div>
<div *ngIf="!booking.cancelled" class="green">Booked</div>
</td>
<td>
<button class="btn btn-danger mr-2" (click)="cancelBooking()">Cancel</button>
</td>
</tr>
table-viewer.component.ts
export class TableViewerComponent implements OnInit {
bookings: Booking[] = [];
getBookings(): void {
this.bookingService.getAll().subscribe((book: Booking[]) => {
this.bookings = book;
});
}
constructor(private bookingService: BookingService) {
}
ngOnInit(): void {
this.getBookings();
}
cancelBooking(): void {
// Help
}
}
booking.ts
export interface Booking {
'firstName': string;
'lastName': string;
'carReg': string;
'parkingNumber': string;
'date': string;
'cancelled': boolean;
}
I think it is as simple as:
cancelBooking(booking:Booking){
booking.cancelled = true;
}
And just pass it in the view to the method
Please use this code in your componenet.ts
cancelBooking(index): void {
// Help
this.bookings[index].cancelled = true;
}
Template :
Change the loop code with below code:
<tr *ngFor="let booking of bookings; let i = index">
<td>{{booking.firstName}}</td>
<td>{{booking.lastName}}</td>
<td>{{booking.carReg}}</td>
<td>{{booking.parkingNumber}}</td>
<td>{{booking.date}}</td>
<td>
<div *ngIf="booking.cancelled" class="red">Cancelled</div>
<div *ngIf="!booking.cancelled" class="green">Booked</div>
</td>
<td>
<button class="btn btn-danger mr-2" (click)="cancelBooking(i)">Cancel</button>
</td>
</tr>
Table-viewer.component.html
<table class="table table-striped table-bordered table-hover">
...
<td>
<button class="btn btn-danger mr-2" (click)="cancelBooking(booking: Booking)">Cancel</button>
</td>
</tr>
table-viewer.component.ts
export class TableViewerComponent implements OnInit {
...
cancelBooking(booking): void {
booking.cancelled = !booking.cancelled
// or booking.cancelled = false; or whatever you want to do
}
}
Now, you have the change in the bookings array.
Next, you should implement a service to rewrite a json file.
https://stackoverflow.com/a/52242184/11218448

How to access specific object in JSON using Angular?

I'm trying to access certain data from an API and having trouble doing so. Here is the structure of the JSON from the API:
I'm wanting to access Name, StateName & CityName within value to iterate through a table. I'm getting the obvious error "Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays", because ngFor isn't iterating through the array. How can I fix my code to iterate through the data to fill my table?
component.ts:
#Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {
customer: any;
constructor(private Service: CustomerDataService) { }
ngOnInit(): void {
this.getCustomerData();
}
public getCustomerData() {
this.Service.getCustomers().subscribe((data)=>{
console.log(data);
this.customer = data;
})
}
}
HTML:
<div class="container">
<form class="form-inline">
<div class="input-group mb-2">
<div class="has-clear">
<input type="text" name="search" required class="form-control" placeholder="Name" />
<button class="clear-data" type="reset"><i class="icon-close-bold-sm"></i></button>
</div>
<button type="submit" class="btn btn-primary mb-2 ml-3">Search</button>
</div>
</form>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">City</th>
<th scope="col">State</th>
</tr>
</thead>
<tbody>
<tr *ngFor = "let customers of customer">
<td scope="row">{{customers.value.Name}}</td>
<td scope="row">{{customers.value.StateName}}</td>
<td scope="row">{{customers.value.CityName}}</td>
</tr>
</tbody>
</table>
</div>
</div>
The value is an array and not object, so
public getCustomerData() {
this.Service.getCustomers().subscribe((data)=>{
console.log(data);
this.customer = data.value;
})
}
and in your template
<tr *ngFor = "let customers of customer">
<td scope="row">{{customers.Name}}</td>
<td scope="row">{{customers.StateName}}</td>
<td scope="row">{{customers.CityName}}</td>
</tr>
Or you can just edit your template
<tr *ngFor = "let customers of customer.value">
<td scope="row">{{customers.Name}}</td>
<td scope="row">{{customers.StateName}}</td>
<td scope="row">{{customers.CityName}}</td>
</tr>
Consider using | async to access the observable directly in your html
<tr *ngFor = "let customer of customers$ | async">
<td scope="row">{{customer.Name}}</td>
<td scope="row">{{customer.StateName}}</td>
<td scope="row">{{customer.CityName}}</td>
</tr>
and in your component
customers$ : Observable<Customer> = this.getCustomerData();
You can update your getCustomerData with a map operator to return the list of Customers you have inside value property.

Why are the form inputs in my html table unclickable?

I am trying to render a table in html that is the representation of a reactive form array.
I loop over the complex object (FormGroup > FormArray > FormGroup > FormArray) With the final array being the data that we want to represent to the UI in its transformed format. (Happens in the function groupFormGroup())
Everything works fine, the data is correctly transformed and displayed.
However, now I am trying to add inputs (NOT formControlName inputs) to the table and when they load, they load without me being able to click into, or change their value. They are not disabled, just unclickable. No errors come up in the console.
What I have tried:
Modifying the class names so it is no longer class='form-control'
Increasing the z-index of inputs to be on the forefront
Making the inputs position relative
Here is the code:
<div class="row" [formGroup]="address">
<div class="col-12">
<table class="w-100">
<thead>
<th></th>
<th>Order ID</th>
<th>GP Code</th>
<th>Product</th>
<th>Quantity</th>
<th>Preset Suite Number</th>
<th></th>
</thead>
<tbody>
<ng-template formArrayName="addressOrders" ngFor let-addressOrder [ngForOf]="address.get('addressOrders').controls" let-ao="index">
<ng-container [formGroup]="addressOrder">
<ng-template ngFor let-fulfillment [ngForOf]="groupFormGroup(addressOrder.get('fulfillments').getRawValue(),'orderDetailId')" let-f="index">
<tr>
<td class='table-input'>
<input type="checkbox"
(change)="updateFulfillmentFormControl(fulfillment,addressOrder,'selected',$event)"/>
</td>
<td>{{addressOrder.get('orderId').value}}</td>
<td>{{fulfillment['gpItemNumber']}}</td>
<td>{{fulfillment['name']}}</td>
<td>{{fulfillment['quantity']}}</td>
<td>
<ng-container *ngIf="fulfillment['isSuite']">
<!-- <input class="form-control" (keydown)="updateFulfillmentFormControl(fulfillment,addressOrder,'suiteNumber',$event)" /> -->
<input type="text" class="form-control" />
</ng-container>
</td>
<td>Edit Detail</td>
</tr>
</ng-template>
</ng-container>
</ng-template>
</tbody>
</table>
</div>
</div>
And the code generating the Form Group:
draw() {
this.formGroup = this.initFormGroup(this.ordersToDisplay)
}
initFormGroup(ordersToDisplay: Array<BulkFulfillCustomer>):FormGroup {
var queueList = this.queues.map(q => q['queue_id']);
return this.fb.group({
customers: this.fb.array(
ordersToDisplay.map(otd => {
return this.fb.group({
gpCustomerNumber:[otd.gpCustomerNumber],
customerName:[otd.customerName],
customerAddresses: this.initAddressForm(otd.addressOrders, queueList)
})
})
)
})
}
initAddressForm(addressOrders: Array<BulkFulfillAddress>, queues:Array<string>):FormArray {
return this.fb.array(
addressOrders.map(ao => {
return this.fb.group({
addressLine:[ao.addressLine],
bundleShipment:[true],
addressOrders: this.initAddressOrdersForm(ao.orders,queues)
})
})
)
}
initAddressOrdersForm(orders: Array<BulkFulfillOrder>, queues:Array<string>):FormArray {
return this.fb.array(
orders.map(o => {
return this.fb.group({
orderId: [o.order.id],
fulfillments: this.initFulfillmentsForm(o.orderDetailFulfillments,queues)
})
})
)
}
initFulfillmentsForm(fulfillments: Array<FulfillmentLine>, queues:Array<string>) {
return this.fb.array(
fulfillments.map(f => {
return this.fb.group({
selected:[true],
expandDetail:[false],
isSuite:[f.inventory.isSuite],
suitePrefix:[f.inventory.suitePrefix],
gpItemNumber:[f.inventory.gpItemNumber],
name:[f.inventory.name],
queueId:[{value:f.inventory.pulseQueueId,disabled:(!this.isPulseFulfill && f.inventory.fulfillmentMethod != INVENTORY_FULFILLMENT_PULSE)}, Validators.compose([Validators.required, ContainsListValidator(queues,'Queue ID')])],
orderDetailId:[f.orderDetailFulfillment.orderDetailId],
pulseOrderId:[f.orderDetailFulfillment.pulseOrderId],
suiteNumber:[{value:f.orderDetailFulfillment.suiteNumber,disabled:!this.isPulseFulfill || !f.inventory.isSuite}, Validators.required],
quantity:[{value:f.orderDetailFulfillment.quantity,disabled:f.orderDetailFulfillment.fulfillmentMethod != 1},Validators.compose([Validators.required, Validators.min(1),Validators.max(f.orderDetailFulfillment.quantity)])]
})
})
)
}

merge divs with same value

I am trying to merge the divs that have the same number in order to see the week and its number.
This is how my html looks like
<div class="wrapper clearfix">
<div class="left">
<label class="kw">WeekNo.</label>
</div>
<div class="right week-number" *ngFor="let day of days">
<label class="number-label"><span>{{day.weekNumber}} </span></label>
</div>
</div>
do I have to use *ngIf or just css?
I have read this How to merge two divs, but I don't know where ti include the if statement
this is the class I am using
export class Day {
number: number;
weekDay: number;
name: string;
weekNumber: number;
constructor(number: number, weekDay: number, name: string, weekNumber: number
) {
this.number = number;
this.weekDay = weekDay;
this.name = name;
this.weekNumber = weekNumber;
}
}
and this is how it should look afet merging
You can do it the following way by creating a groupBy pipe and including Bootstrap 4 in the project. Here is a plain working demo (you will have to do the styling) demo:
groupby.pipe.ts
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({name: 'groupBy'})
export class GroupByPipe implements PipeTransform {
transform(collection: any[], property: string): any[] {
// prevents the application from breaking if the array of objects doesn't exist yet
if(!collection) {
return null;
}
const groupedCollection = collection.reduce((previous, current)=> {
if(!previous[current[property]]) {
previous[current[property]] = [current];
} else {
previous[current[property]].push(current);
}
return previous;
}, {});
// this will return an array of objects, each object containing a group of objects
return Object.keys(groupedCollection).map(key => ({ key, value: groupedCollection[key] }));
}
}
html code
<div class="table table-borderless ">
<div class="col-md-12 text-center border"><span>{{currentMonthName}}</span> <span>{{currentYear}}</span></div>
<div class="d-flex">
<div class=" text-center m-2 border" *ngFor="let day of days | groupBy:'weekNumber'">
{{day.key}}
<table class="table">
<tbody>
<tr>
<th scope="row" *ngFor="let x of day.value ">{{x.number}}</th>
</tr>
<tr>
<th scope="row" *ngFor="let y of day.value ">{{y.name}}</th>
</tr>
</tbody>
</table>
</div>
</div>
</div>