Working with custom components for input generates "No value accessor for form control with path X->0->Y" - html

I have a working form taking the following HTML markup. No errors or warnings.
<div class="input-element">
<div class="input-caption">Title</div>
<input type="text"
formControlName="targetField"
class="form-control">
</div>
I transformed it into a custom component, which also works, as shown below.
<app-input-text [info]="'Title'"
formControlName="targetField"
ngDefaultControl></app-input-text>
In my next view, I need to use FormArray as follows - still working code.
<div formArrayName="stuff">
<div *ngFor="let thing of form.controls.stuff.controls; let i = index;"
[formGroupName]=i>
<div class="input-element">
<div class="input-caption">Title</div>
<input type="text"
formControlName="targetField"
class="form-control">
</div>
</div>
</div>
Now, I expected that combining both (i.e. being able to use custom input component and being able to form array for components) would post no problem. However, the sample below doesn't work.
<div formArrayName="stuff">
<div *ngFor="let thing of form.controls.stuff.controls; let i = index;"
[formGroupName]=i>
<app-input-text [info]="'Title'"
formControlName="targetField"
class="col-sm-6"></app-input-text>
</div>
</div>
It generates the following error.
No value accessor for form control with path: 'stuff -> 0 -> targetField'
The custom component is design like this (although given that it works in the explicit markup example, I'm not sure if it's relevant information). The only (wild) guess I have might be that value isn't jacked into the form array field somehow.
export class InputTextComponent implements OnInit {
constructor() { this.value = new EventEmitter<string>(); }
#Input() info: string;
#Output() value: EventEmitter<string>;
onEdit(value: any): void { this.value.emit(value); }
}
The group and array creating in the current view is done like this (not sure if this is of any relevance neither, as it works for the explicit HTML markup case).
this.form = builder.group({
id: "",
stuff: builder.array([
builder.group({ targetField: "aaa" }),
builder.group({ targetField: "bbbb" }),
builder.group({ targetField: "cc" })
])
});
Is there a limitation in Angular in this regard that I'm not aware of? I'm rather sure there's not and that I'm just doing something fairly clever simply missing a tiny detail.
I do understand the error but I can't see how it relates to the code. The form can't find the 0th element in the array or that element has no field of that name. Since I do get to see a few rows, I know there must be a 0th element. Since I specified the name of the field, I know there is indeed such. What else am I missing?

Related

Angular Checkbox - Cannot get the value of database to match the checkmarks in html checkbox

Looking for your kind assistance as I am still new to coding and angular.
I have this form which allows to me do CRUD data.
In adding the data, I have a several checkbox which I can manage to successfully stored in the database.
However, when I am trying to edit the data, the checkbox are no longer showing check markings based on the data in the table.
I have a Modal form and I am having a hard time matching the data in the checkbox to the ones in database.
Component.html
<div>
<div *ngFor="let item of _placarddetails">
<input type="checkbox" name="{{item.id}}" [(ngModel)]="item.isselected">
<label> {{item.name}}</label>
</div>
</div>
Component.TS
ngOnInit(): void {
this.loadPlacardQualityList();
}
loadPlacardQualityList(){
this.service.getAllEdcmTicketNo().subscribe((data:any)=>{
this.PlacardQualityList=data;
this.PlacardQualityId=this.pq.PlacardQualityId;
this.EdcmTicketNo=this.pq.EdcmTicketNo;
this.PQDeliveryDate=this.pq.PQDeliveryDate;
this.PlacardAppearance=this.pq.PlacardAppearance;
this.PlacardDetails=this.pq.PlacardDetails ;
this.PlacardAcceptance=this.pq.PlacardAcceptance;
this.Inserts=this.pq.Inserts;
this.CheckedBy=this.pq.CheckedBy;
this.PowerProduct=this.pq.PowerProduct;
this.Comment=this.pq.Comment;});
this.
}
addPlacardQuality()
{
var val = {
PlacardQualityId:this.PlacardQualityId,
EdcmTicketNo:this.EdcmTicketNo,
PQDeliveryDate:this.PQDeliveryDate,
PlacardAppearance:this.PlacardAppearance,
PlacardDetails:this.PlacardDetails = this._placarddetails.filter(x=>x.isselected==true).map(x=>x.name).join(","),
PlacardDetailsID:this.PlacardDetails = this._placarddetails.filter(x=>x.isselected==true).map(x=>x.name).join(","),
Inserts:this.Inserts,
CheckedBy:this.CheckedBy,
PowerProduct:this.PowerProduct,
Comment:this.Comment};
this.service.addPlacardQuality(val).subscribe(res=>{alert(res.toString());
});
}
updatePlacardQuality(){
var val = {
PlacardQualityId:this.PlacardQualityId,
EdcmTicketNo:this.EdcmTicketNo,
PQDeliveryDate:this.PQDeliveryDate,
PlacardAppearance:this.PlacardAppearance,
PlacardDetails:this.PlacardDetails = this._placarddetails.filter(x=>x.isselected==true).map(x=>x.name).join(","),
Inserts:this.Inserts,
CheckedBy:this.CheckedBy,
PowerProduct:this.PowerProduct,
Comment:this.Comment}
this.service.updatePlacardQuality(val).subscribe(res=>{alert(res.toString());});
}
getPlacardDetails()
{
this._placarddetails=[
{id:1,name:"Company Name",isselected:false},
{id:2,name:"Company Logo",isselected:false},
{id:3,name:"Certification Level",isselected:false},
{id:4,name:"Year",isselected:false},
{id:5,name:"Badge Name",isselected:false},
]
}
class PDetail{
id!: number;
name!: string;
isselected!: boolean;
}
Here is the sample screenshot whenever I open the edit button.
sample screenshot
I understand that the reason why it is not showing a checkmarks is because of the getPlacardDetails() which is showing false value.
Is there a way that you can recommend a method how I can fix this?
I'm running out of resources and logic lol.
Sorry, still in-experience and I still have lots to learn.
Thank you in advance!
Use "checked" attribute of the checkbox to make it checked. Code as below:
<input type="checkbox" name="{{item.id}}" [(ngModel)]="item.isselected" [checked]="item.isselected">
Also for more details you can refer to below link:
Angular 5, HTML, boolean on checkbox is checked

How to display saved filters in headers with PrimeNG?

I am able to save the filters locally using stateStorage="local" stateKey="myKey". So when the user leaves the component and returns, the data is still filtered based upon whatever filters they have set.
The problem is, the user has no idea what they are filtering on anymore, as the filter headers do not show them anything anymore, just the default label. I can access the filters via local storage, and delete them using localStorage.removeItem("myKey");, but I cannot for the life of me figure out how to get this filter information to display in the filter headers. We are not using lazyLoad, as suggested in another answer. You'd think this would be built in any time a filter is saved because not knowing what you are filtering on seems like a major flaw.
For more clarity, I have attached the primeFaces documentation below. If you select 'Red' 'White' and 'Green' from the multiselect dropdown, it will display your selected filter in the header (Red, White, Green) above. I need this information to display anytime the user enters the component if they have filters saved (both with text input, and with the dropdowns).
https://www.primefaces.org/primeng/#/table/filter
I am using multi-select dropdown filters, text input, as well as calendar filters. Here is a snippet of the html, which includes examples of these three filter types:
<th *ngFor="let col of columns" [ngSwitch]="col.field">
<input *ngSwitchCase="'userID'" pInputText type="text" size="12" placeholder="contains" (input)="table.filter($event.target.value, col.field, col.filterMatchMode)" [value]="table.filters['userID'] ? table.filters['userID'].value : ''">
<div class="ui-g ui-fluid">
<p-calendar #calendar1 class="ui-fluid" *ngSwitchCase="'myDate'" [monthNavigator]="true" [showOnFocus]="false" [yearNavigator]="true" yearRange="2010:2060" [showIcon]="true"
[showOtherMonths]="false" [showButtonBar]="true" [appendTo]="attach" [style]="{'overflow': 'visible'}"
[(ngModel)]="calendar1Filter"
(ngModelChange)="table.filter($event, 'myDate', calendar1Option)"
(onSelect)="table.filter($event, 'myDate', calendar1Option)">
<p-footer>
<div class="ui-grid-row">
<div class="ui-grid-col-3"><label style="font-weight: bold; color: #337ab7">Mode:</label></div>
<div class="ui-grid-col-6"><p-dropdown [options]="calendar1Options" [style]="{'width':'60px', 'padding-top': '0px'}" [(ngModel)]="calendar1Option" (onChange)="onChangeModCalOpt(calendar1, 1)" ></p-dropdown> </div>
</div>
</p-footer>
</p-calendar>
</div>
<div class="ui-fluid">
<p-multiSelect *ngSwitchCase="'myDropdown'" appendTo="body" [options]="dropdownOptions" pInputText type="text" size="12" [style]="{'width':'100%'}" defaultLabel="All" [(ngModel)]="myDropdownFilter" (onChange)="table.filter($event.value, col.field, 'in')"></p-multiSelect>
</div>
</th>
I did this almost a year ago; it involved some tricky code, as you'll see below, because the table element was beneath an *ngIf directive. I'm not sure if your case is the same, but if it is, here's what I had to to do get it to work. In the example, I have a fooTable that has a custom filter on the status column.
foo.component.ts:
import { ChangeDetectorRef, Component, ViewChild } from "#angular/core";
#Component({
selector : 'foo',
templateUrl : 'foo.component.html'
})
export class FooComponent {
// members /////////////////////////////////////////////////////////////////////////////////////////////////////////
showFooTable: boolean = true;
statusFilter: string[] = [];
// constructor(s) //////////////////////////////////////////////////////////////////////////////////////////////////
constructor(private cd: ChangeDetectorRef) { }
// getters and setters /////////////////////////////////////////////////////////////////////////////////////////////
/**
* Due to the Angular lifecycle, we have to do some tricky things here to pull the filters from the session,
* if they are present. The workarounds done in this function are as follows:
*
* 1) Wait until the element is accessible. This is not until the *ngIf is rendered, which is the second call to
* this function. The first call is simply 'undefined', which is why we check for that and ignore it.
* 2) Check and see if the filters for this object are even part of the template. If not, just skip this step.
* 3) If we find the filters in the session, then change this object's filters model to equal it and call the change
* detection manually to prevent Angular from throwing an ExpressionChangedAfterItHasBeenCheckedError error
* #param fooTable the reference to the foo table template
*/
#ViewChild('fooTable') set fooTable(fooTable: any) {
if(fooTable != undefined) {
let filters = fooTable.filters['status'];
if (filters != undefined && filters.value != undefined) {
this.statusFilter = filters.value;
}
this.cd.detectChanges();
}
}
}
foo.component.html:
<ng-container *ngIf="showFooTable">
<div id="filters">
<p-checkbox [(ngModel)]="statusFilter" value="up" (onChange)="fooTable.filter(statusFilter, 'status', 'in')"></p-checkbox> Up
<p-checkbox [(ngModel)]="statusFilter" value="down" (onChange)="fooTable.filter(statusFilter, 'status', 'in')"></p-checkbox> Down
<p-checkbox [(ngModel)]="statusFilter" value="unknown" (onChange)="fooTable.filter(statusFilter, 'status', 'in')"></p-checkbox> Unknown
</div>
<p-table #fooTable
stateStorage="session"
stateKey="foo-table-state">
</p-table>
</ng-container>
Turbo tables filters can be access like so. table.filters['myColumn']?.value. You will need to set the input values in the header, [value]="table.filters[col.field]?.value"
...
<tr>
<th *ngFor="let col of columns" [ngSwitch]="col.field" class="ui-fluid">
<input pInputText
type="text"
(input)="table.filter($event.target.value, col.field, col.filterMatchMode)"
[value]="table.filters[col.field]?.value">
</th>
</tr>
...
https://www.primefaces.org/primeng/#/table/state
Just figured this out. What I ended up doing was attaching the default label to a model like so:
<p-multiSelect [defaultLabel]="table.filters[col.field]?.value || 'All'">
If the table has the filter value in its state, it will populate the label else default to 'All'.

Angular 6: How to build a simple multiple checkbox to be checked/unchecked by the user?

I am writing this post after having read several threads concerning this topic but no one of them gives me what I need. This post seems to have the solution but I do not have to read the checked values from the json.
All I need is to:
read countries from an array of objects
build dinamically a list of checkbox representing each country
user should check and uncheck each checkbox
bonus:
get the value of the checked input and send it outside the component
I know It might be really dumb to do but all I have accomplished untile now is to have a list of uncheckable checkboxes and nothing more.
Here is the code:
Template:
<div class="form-group">
<div *ngFor="let country of countries">
<input type="checkbox"
name="countries"
value="{{country.id}}"
[(ngModel)]="country"/>
<label>{{country.name}}</label>
</div>
</div>
And TS:
countries = [
{id: 1, name: 'Italia'},
{id: 2, name: 'Brasile'},
{id: 3, name: 'Florida'},
{id: 4, name: 'Spagna'},
{id: 5, name: 'Santo Domingo'},
]
I tried to use the reactive forms but that gave me more issues then template driven (surely because of bad implementation of mine).
Please, help me, I do not know where to bump my head anymore
Here is a working example, where you can observe that an additional 'checked' value is added to each country, and bound to the value of each checkbox with [(ngModel)].
Stackblitz live example
template:
<p>
Test checkboxes
</p>
<div *ngFor="let country of countries; let i = index;">
<input type="checkbox" name="country{{country.id}}" [(ngModel)]="countries[i].checked">
<label for="country{{country.id}}">{{country.name}}</label>
</div>
<button type="button" (click)="sendCheckedCountries()" *ngIf="countries">Click to send the selected countries (see your javascript console)</button>
<p *ngIf="!countries">loading countries, please wait a second...</p>
<p *ngIf="countries">Debug info : live value of the 'countries' array:</p>
<pre>{{ countries | json }}</pre>
component :
//...
export class AppComponent implements OnInit {
public countries: Country[];
constructor(private countryService: CountryService) {}
public ngOnInit(): void {
// loading of countries, simulate some delay
setTimeout(() => {
this.countries = this.countryService.getCountries();
}, 1000);
}
// this function does the job of sending the selected countried out the component
public sendCheckedCountries(): void {
const selectedCountries = this.countries.filter( (country) => country.checked );
// you could use an EventEmitter and emit the selected values here, or send them to another API with some service
console.log (selectedCountries);
}
}
To use some proper TypeScript, I made an interface Country :
interface Country {
id: number;
name: string;
checked?: boolean;
}
I hope you get the idea now.
Note : the checked value is not "automatically there" at the beginning, but it doesn't matter.
When not there, it is the same as undefined, and this will be treated as false both in the checkbox and in the function that reads which country is checked.
For the "sending value" part :
The button will output the selected value to the browser's console, with some filter similar to what #Eliseo's answer suggests (I just used full country objects instead of ids)
For "real usecase" situation, you could use Angular's EventEmitters and have your component "emit" the value to a parent component, or call some service function that will make a POST request of your values to another API.
Your countries like
{id: 1, name: 'Italia',checked:false},
Your html like
<div *ngFor="let country of countries">
<input type="checkbox" [(ngModel)]="country.checked"/>
<label>{{country.name}}</label>
</div>
You'll get an array like, e.g.
[{id: 1, name: 'Italia',checked:false},{id: 2, name: 'Brasile',checked:tue}..]
you can do
result=this.countries.filter(x=>x.checked).map(x=>x.id)
//result becomes [2,...]
I had an error using [(ngModel)]
In case it serves anyone, I have solved the problem changing
[(ngModel)]
to:
[checked]="countries[i].checked" (change)="countries[i].checked= !countries[i].checked"

append String to ngModel to get an expression

I have a Business Object Car{id:2,name:ford,modelNo:123} I also have a MetaData class for CarMetaData{columnName:string,type:text,required:true}
each attribute has it's own CarMetaData object
how can I display Car data in a form in such a way using an Array of CarMetaData objects
In the template.html file
<form #carForm='ngForm'>
<div class="form-group" *ngFor="let metaData of metadata" >
<input [(ngModel)]="car[metaData.columnName]" name="metaData.columnName"
type="text">
</div>
</form>
In the component file
car:Car;
metadata:CarMetaData[];
The above method isn't working because of the [] in car[meta.columnName] in the
ngModel is there a way an expression can be worked out in *ngFor or [(ngModle)] to calculate columName
Very stupid mistake. Please try this:
<div *ngFor="let meta of metaData;">
<input [(ngModel)]="car[meta.columnName]" name="{{meta.columnName}}" />
{{meta.columnName}}
</div>
Old answer:
But first - your loop (meta in metaData) is wrong as you loop array, not object. So you better do let meta of metaData, let i = index.
And the interesting part is how ngModel sets the value of inputs in ngFor. If you try this:
<div *ngFor="let meta of metaData; let i = index">
<input [(ngModel)]="car[meta.columnName]" name="meta.columnName" />
{{meta.columnName}}
</div>
You will see that input value is the same of all inputs (it was picked from the first ngFor iteration and repeated to all inputs). However, {{meta.columnName}} prints correct values. So there is some scoping issue. Other strange thing - its definitely related with ngForm and input's name property. If you move that outside of the form - all happens as expected. And if you:
<div *ngFor="let meta of metaData; let i = index">
<input [(ngModel)]="car[meta.columnName]" name="{{metaData[i].columnName}}" />
{{meta.columnName}}
</div>
Inside the form - again, all works well. So that might be your workaround.
Here is a DEMO. Hopefully someone will explain it further.
I'd suggest to put both objects in a wrapper-object. e.g.
export class CarWrapper {
constructor(
car: Car,
carMetaData: CarMetaData
) {}
}
Then build a method that joins both objects in this wrapper
private carWrapper: Array<CarWrapper> = [];
private fillCarWrapper(): void {
// your code here
}
And in your template you act like this
<form #carForm='ngForm'>
<div class="form-group" *ngFor="let wrapper of carWrapper">
<input [(ngModel)]="wrapper.car" name="wrapper.carMetaData.columName" type="text">
</div>
</form>

angular2 custom directives inputs syntax

I create a custom directive and set the selector value to be "[unless-directive]".
The directive get a Boolean and use it to change the view as so:
import {Directive, TemplateRef, ViewContainerRef} from 'angular2/core';
#Directive({
selector: '[unless-directive]',
inputs: ['givenBoolean : myDirectiveFunction']
})
export class UnlessDirective {
private _templateRef: TemplateRef;
private _viewContainerRef: ViewContainerRef;
constructor(_templateRef: TemplateRef, _viewContainerRef: ViewContainerRef) {
this._templateRef = _templateRef;
this._viewContainerRef = _viewContainerRef;
}
set myDirectiveFunction(condition: boolean) {
!condition ? this._viewContainerRef.createEmbeddedView(this._templateRef)
: this._viewContainerRef.clear();
}
}
In my template I tried to pass the condition like so:
<div name="customDirective">
<h2>Custom Directive</h2>
<div>
Enter true or false:
<br/>
<input type="text" #condition (keyup)="0"/>
<div *unless-directive [givenBoolean]="condition.value != 'false'">
Only shown if 'false' wad enterded!
</div>
</div>
</div>
When I running the code I get this error:
EXCEPTION: Template parse errors: Can't bind to 'givenBoolean' since
it isn't a known native property (" ... Only shown if 'false' wad enterded!"): StructualDirectivesComponent#47:39
I guess my syntax is wrong, but I can't find where or why?
I looked it up on Angular2 Docs, but the example use the same name for the input and the selector, the thing that I'm trying to avoid.
Can anyone know a better way or can find my syntax problem?
Thanks.
The * prefix syntax is only a syntatic sugar. It expands the directive declaration.
The * prefix syntax is a convenient way to skip the <template> wrapper tags and focus directly on the HTML element to repeat or include. Angular sees the * and expands the HTML into the <template> tags for us.
This is documented in * and <template> and Directive decorator/Lifecycle hooks.
So, in your case, the [givenBoolean] property is not expected to be in the directive. In other words, this:
<div *unless-directive [givenBoolean]="condition.value != 'false'">
Only shown if 'false' wad enterded!
</div>
Becomes, actually:
<template [unless-directive]="">
<div [givenBoolean]="condition.value != 'false'">
Only shown if 'false' wad enterded!
</div>
</template>
And since givenBoolean is not a property in the component (not the directive), the error appears.
So if you want custom behavior, I suggest you experiment using the expanded version and only after it works you go to the * syntax, it will be simpler to reason about.