Console error <ng-select>: No value accessor for form controll with name: - angular6

I have added ng-select in my project for multiselect-searchable-grouping dropdown. I have followed the steps from https://ng-select.github.io/ng-select#/grouping, but unfortunately getting:
ERROR Error: No value accessor for form control with name: 'optProject'
I am using angular 6
Please see my code as below:
I have added NgSelectModule to module.ts
html
<ng-select formControlName="optProject"
[items]="projects"
bindLabel="title"
bindValue="id"
groupBy="subprojects"
[multiple]="true"
[(ngModel)]="selectedProjects">
<ng-template ng-optgroup-tmp let-item="item">
{{item.title}}
</ng-template>
<ng-template ng-option-tmp let-item="item">
{{item.title}}
</ng-template>
</ng-select>
component.ts
import { FormGroup, FormControl} from '#angular/forms';
.
.
.
public selectedProjects;
public projects;
ngOnInit() {
this.selectedProjects = [];
this.projects = [
{
id: 'p1',
title: 'Project A',
subprojects: [
{ title: 'Subproject 1 of A', id: 's1p1' },
{ title: 'Subproject 2 of A', id: 's2p1' },
]
},
{
id: 'p2',
title: 'Project B',
subprojects: [
{ title: 'Subproject 1 of B', id: 's1p2' },
{ title: 'Subproject 2 of B', id: 's2p2' },
]
}
]
}
projectForm = new FormGroup({
optProject: new FormControl(null),
});
While searching on Google, I found that this kind of error occurs while adding formCorntrolName to labels/div instead of input controls. But is the case here, with ng-select ? Whats wrong with my code...! Thanks in advance...
Just to add, I have added ngDefaultControl to ng-select, because I found: Third party controls require a ControlValueAccessor to function with angular forms (What is ngDefaultControl in Angular?), but still no luck!!! The error goes but no content display within <ng-select>

Related

Angular - Implement NgxMatSelectSearch in mat-select

I am trying to use a search box in a mat-select that works correctly only when using data loaded by default. I want to use data from an api. But it does not work properly, the data is not displayed in the mat-select when loading the page, but it is displayed when a focus occurs in the mat-select tag.
I have a model where I use the data from a test API
export interface DataModel {
id: number;
title: string;
userId: number;
}
export const DataModels: DataModel[] = [
{ id: 1, title: 'Option A', userId: 23 },
{ id: 2, title: 'Option B', userId: 24 },
{ id: 3, title: 'Option C', userId: 25 },
{ id: 4, title: 'Option D', userId: 26 }
];
My service where I make the call
#Injectable()
export class DataloadService {
constructor(private http: HttpClient) {}
LoadData(): Observable<any> {
return this.http.get('https://jsonplaceholder.typicode.com/albums');
}
}
The component where the search filter is performed and controls are set. Following the documentation NgxMatSelectSearch
constructor(private service: DataloadService) {}
dataModel: DataModel[] = []; //DataModels
dataCtrl: FormControl = new FormControl();
dataFilterCtrl: FormControl = new FormControl();
filteredData: ReplaySubject<DataModel[]> = new ReplaySubject<DataModel[]>(1);
#ViewChild('singleSelect', { static: true }) singleSelect: MatSelect;
_onDestroy = new Subject<void>();
ngOnInit() {
this.load();
this.filteredData.next(this.dataModel.slice());
this.dataFilterCtrl.valueChanges
.pipe(takeUntil(this._onDestroy))
.subscribe(() => {
this.filterData();
});
}
ngOnDestroy() {
this._onDestroy.next();
this._onDestroy.complete();
}
filterData() {
if (!this.dataModel) {
return;
}
let search = this.dataFilterCtrl.value;
if (!search) {
this.filteredData.next(this.dataModel.slice());
return;
} else {
search = search.toLowerCase();
}
this.filteredData.next(
this.dataModel.filter(
(x: any) => x.title.toLowerCase().indexOf(search) > -1
)
);
}
load() {
return this.service.LoadData().subscribe(res => {
this.dataModel = res;
});
}
And the HTML
<mat-card>
<mat-toolbar>Demo</mat-toolbar><br />
<mat-card-content>
<mat-select [formControl]="dataCtrl" placeholder="Data" #singleSelect>
<mat-option>
<ngx-mat-select-search
[formControl]="dataFilterCtrl"
></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let x of filteredData | async" [value]="x.id">
{{x.title}}
</mat-option>
</mat-select>
</mat-card-content>
</mat-card>
If I use the data that is by default in the model to simulate the process using "dataModels"
dataModel: DataModel[] = []; //DataModels
Instead of initializing it empty. It works normally but if I load the data with the request made to the API, the problem arises that it is not loaded after a focus occurs.
The demo I have in Stackblitz: Demo Stackblitz
You should add this line
this.filteredData.next(this.dataModel.slice());
into subscribe event of this.service.LoadData() as it is asynchronous. So that when the response result is returned, the filteredData is bonded with the response result.
load() {
return this.service.LoadData().subscribe(res => {
this.dataModel = res;
this.filteredData.next(this.dataModel.slice());
});
}
Sample Solution on StackBlitz

How to disable all buttons coming from ngFor except the button which has been clicked in angular 8

Please open it in full browser https://stackblitz.com/edit/angular-skzgno?file=src%2Fapp%2Fapp.component.html , Then click button/image of any li tag,The button will be change to different image as like active .Even if you refresh also this active will not be change since we are adding active=true into localstorage.Now the problem is,on page load when you click button of any li,except that button,buttons of other li should be disable and when we refresh also nothing will be change until you clear localstorage.Please find the code below
app.component.html
<hello name="{{ name }}"></hello>
<p>
Start editing to see some magic happen :)
</p>
<div>
<pre>
</pre>
<ul>
<li *ngFor="let item of statusdata">
<span>{{item.id}}</span>
<span>{{item.name}}</span>
<button style="margin-left:10px" (click)="toggleActive(item, !item.active)">
<img style="width:50px;margin-left:10px" *ngIf="!item?.active || item?.active === false" src ="https://dummyimage.com/qvga" />
<img style="width:50px;margin-left:10px" style="width:50px;margin-left:10px" *ngIf="item?.active === true" src ="https://dummyimage.com/300.png/09f/fff" />
</button>
</li>
</ul>
</div>
app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
statusdata: any;
ngOnInit() {
this.statusdata = [
{ id: 1, name: "Angular 2" },
{ id: 2, name: "Angular 4" },
{ id: 3, name: "Angular 5" },
{ id: 4, name: "Angular 6" },
{ id: 5, name: "Angular 7" }
];
this.statusdata.forEach(item => {
this.getCacheItemStatus(item);
});
}
toggleActive(item, activeStatus = true) {
if(!this.statusdata.some(d => d.active)){
item.active = activeStatus;
localStorage.setItem(`item:${item.id}`, JSON.stringify(item));
}
}
getCacheItemStatus(item) {
const cachedItem = localStorage.getItem(`item:${item.id}`);
if (cachedItem) {
const parse = JSON.parse(cachedItem); // Parse cached version
item.active = parse.active; // If the cached storage item is active
}
}
}
I'm so so sorry for not being able to adapt this to the code in the question. I faced this challenge and did not want to forget sharing.
say we have this in the typescript file;
movies: any[] = [
{ name: 'Wakanda', year: 2010, rating: 4.5 },
{ name: 'Super', year: 2000, rating: 5 },
{ name: 'Deli', year: 2002, rating: 3 },
{ name: 'Fury', year: 2020, rating: 4 },
];
isDisabled: boolean = false;
Then this in the HTML...
<div *ngFor="let movie of movies;index as i">
<div class="wrapper">
<button (click)="disableBtn('btn' + i)" [disabled]="isDisabled && isDisabled !== 'btn'+i"
id=btn{{i}}>Vote</button>
</div>
</div>
The disableBtn function takes the current button and assigns it to the isDisabled variable then [disabled] attribute checks if isDisabled is truthy and if isDisabled is strictly not equal to the current button. This will be true for all other buttons except the one clicked.
The function disables(toggles it) all other buttons except the one clicked.
disableBtn(btn) {
if (this.isDisabled === btn) { //also re-enables all the buttons
this.isDisabled = null;
return;
}
this.isDisabled = btn;
}
Have you tried the [disabled] property?
<button (click)="toggleActive(item, !item.active)" [disabled]="shouldDisable(item)">
shouldDisable(item): boolean {
return !item.active && this.statusdata.some((status) => status.active);
}

Add "Select All'' button to ion-select

I'm using ionic 4 and I want to set custom buttons on ion-select through interfaceOptions
HTML
<ion-item>
<ion-label>Lines</ion-label>
<ion-select multiple="true" [(ngModel)]="SelectedLines" [interfaceOptions]="customAlertOptions">
<ion-select-option [value]="line" *ngFor="let line of Lines">{{linea.Name}}</ion-select-option>
</ion-select>
</ion-item>
TS
customAlertOptions: any = {
buttons: [
{
text: 'Select All',
handler: (blah) => {
console.log('Select All Clicked');
},
{
text: 'No',
handler: (blah) => {
console.log('Confirm Cancel: blah');
}
}, {
text: 'Okay',
handler: () => {
console.log('Confirm Okay');
}
}
]
};
However, only the default buttons are showing (Ok and Cancel)
Docs say it should be possible
https://ionicframework.com/docs/api/select
I can see this has been reported for previous versions of Ionic
https://forum.ionicframework.com/t/custom-button-for-ion-select-through-selectoptions-not-working/157305
Is it possible to make this work on Ionic 4? Is there a workaround?
EDIT: I tried with the PopOver interface with the same results
What you are trying to do isn't possible from what I can see.
The documentation actually only says you can set the buttons text:
ion-select#select-buttons - Ionic Documentation
By default, the alert has two buttons: Cancel and OK. Each button's text can be customized using the cancelText and okText properties.
It doesn't say that the buttons can be customised.
You can pass in the interfaceOptions but its overridden later by the default button set:
https://github.com/ionic-team/ionic/blob/master/core/src/components/select/select.tsx#L339
The code looks like this:
const alertOpts: AlertOptions = {
mode,
...interfaceOptions,
header: interfaceOptions.header ? interfaceOptions.header : labelText,
inputs: this.createAlertInputs(this.childOpts, inputType),
buttons: [
{
text: this.cancelText,
role: 'cancel',
handler: () => {
this.ionCancel.emit();
}
},
{
text: this.okText,
handler: (selectedValues: any) => {
this.value = selectedValues;
}
}
],
cssClass: ['select-alert', interfaceOptions.cssClass,
(this.multiple ? 'multiple-select-alert' : 'single-select-alert')]
};
return alertController.create(alertOpts);
So as you can see the ...interfaceOptions, is passed in at the start, then the buttons are set to the defaults, with the only customisation options being the ok or cancel text.
i am working with AlertController from ionic and i can custom it, just take a look at my screen .
You just need to import AlertController and after you can do something like this for exemple :
home.page.ts
async addAlertHome(adresse: string, lat: string, lon: string) {
const alert = await this.alertController.create({
header: 'Ajouter aux favoris',
message: 'Êtes vous sûr de vouloir ajouter cette adresse à vos favoris ?',
buttons: [
{
text: 'Non',
role: 'cancel',
cssClass: 'secondary'
}, {
text: 'Oui',
handler: () => {
alert.dismiss().then(() => {
this.addFavoriteHome(adresse, lat, lon);
});
console.log('Confirm Okay');
}
}
]
});
await alert.present();
}
And use it where you want on html :
home.page.html
<ion-icon name="heart-empty" (click)="addAlert(location.display_name, location.lat, location.lon)" end>
</ion-icon>
And don't forget on your constructor :
public alertController: AlertController

How to access object property of a formControl in Angular?

I have a Angular FormControl with an array of objects.
One object looks like:
{id: 'id', title: 'title'}
I have a formControl within a formGroup with other formControls.
fc= null;
this.fc= new FormControl(this.array);
this.form = this.formBuilder.group({
fc: this.fc,
...
});
Now a have a mat select in my html:
<mat-select formControlName="fc" placeholder="Test" multiple>
<mat-option *ngFor="let op of test" [value]="op.title">{{ op.title }}</mat-option>
</mat-select>
How can I say that the formControl should use the title property of the object to show the initital value that is in the array? If I map the array only to the title property all works fine.
this.fc= new FormControl(this.array.map(x=>x.title));
But I need the whole object in my form.
Give this a try:
import { Component } from '#angular/core';
import { FormGroup, FormBuilder, FormControl } from '#angular/forms';
/** #title Select with multiple selection */
#Component({
selector: 'select-multiple-example',
templateUrl: 'select-multiple-example.html',
styleUrls: ['select-multiple-example.css'],
})
export class SelectMultipleExample {
test = [
{ id: '1', title: 'Title 1' },
{ id: '2', title: 'Title 2' },
{ id: '3', title: 'Title 3' },
{ id: '4', title: 'Title 4' },
{ id: '5', title: 'Title 5' },
];
form: FormGroup;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.form = this.formBuilder.group({
fc: [['2', '4']],
});
}
}
/** Copyright 2019 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license */
And in your template:
<form [formGroup]="form">
<mat-form-field>
<mat-label>Toppings</mat-label>
<mat-select formControlName="fc" multiple>
<mat-option *ngFor="let item of test" [value]="item.id">{{item.title}}</mat-option>
</mat-select>
</mat-form-field>
</form>
<!-- Copyright 2019 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license -->
Here's a Working Sample StackBlitz for your ref.
If your mat-option is like
<mat-option *ngFor="let item of test" [value]="item.id">{{item.title}}</mat-option>
Your formControl must be,e,g -see that is an array or string-
fc: [['2','4']]
If your mat-option is like
<mat-option *ngFor="let item of test" [value]="item">{{item.title}}</mat-option>
Your formControl must be,e,g, -see that is an array of objects-, and in this case you need use a reference of the object
fc: [this.test[1],this.test[3]] //<--use reference to the object
//be careful!!
//fc:[{id:'2',title:'Title2'},{id:'4',title:'Title 4'}] //<--ERROR

Angular 2 Kendo UI Grid display foreign key text

I have a cart like system for a PO using Angular 4 and ASP.NET CORE WEB APIs. I have two main calls getting the PO details and then all of the "Cart" line items for a specific PO. I have related funding category ids and project ids in the table because finance people need to adjust these. I need to get the Kendo UI grid to display the text of the related foreign key. I will be implementing edit soon hence the ng-template but working on having the non-edit view with the text value displayed. The office id is a simple integer and the office data returns id and name in JSON
HTML
<kendo-grid
[data]="view | async"
[pageSize]="gridState.take"
[skip]="gridState.skip"
let-dataItem="dataItem">
<kendo-grid-column field="productName" title="Product Name">
<ng-template kendoGridEditTemplate let-dataItem="dataItem">
<input [(ngModel)]="dataItem.productName" name="productName" class="k-textbox" />
</ng-template>
<kendo-grid-column>
<kendo-grid-column field="officeId" **<!--Fix here??-->** title="Office">
<ng-template kendoGridEditTemplate let-dataItem="dataItem">
<kendo-dropdownlist name="officeName"
[data]="office"
[textField]="'name'"
[valueField]="'id'"
[valuePrimitive]="true"
[(ngModel)]="dataItem.officeId">
</kendo-dropdownlist>
</ng-template>
<kendo-grid-column>
...
</kendo-grid>
Typescript
public po: PO = {
id: 0,
poNumber: '',
...
}
public cart: Cart[] = [{
id: 0,
productName: '',
officeId: 0,
...
}];
office: any[];
...
constructor(
private route: ActivatedRoute,
private router: Router,
private cartService: CartService, //has cart items
private referenceService: ReferenceService //has office FK and text value
#Inject(CartEditService) editServiceFactory: any){
route.params.subscribe(p => {
this.po.id = +p['id'] || 0;
});
this.cartEditService = editServiceFactory();
this.view = this.cartEditService.map(data => process(data, this.gridState));
}
ngOnInit(){
//todo implement check for new po or existing
this.cartService.getPo(this.po.id).subscribe(po => this.po = po);
this.cartEditService.read(this.po.id);
this.referenceService.getOffices().subscribe(office => this.office = office)
...
}
//To Do add the action handlers for grid
Added solution thanks to topalkata
HTML
<kendo-grid-column title="Office">
<ng-template kendoGridCellTemplate let-dataItem>
{{getOfficeNameById(dataItem.officeId)}}
</ng-template>
<ng-template kendoGridEditTemplate let-dataItem="dataItem">
<kendo-dropdownlist name="officeName"
[data]="office"
[textField]="'name'"
[valueField]="'id'"
[valuePrimitive]="true"
[(ngModel)]="dataItem.officeId">
</kendo-dropdownlist>
</ng-template>
<kendo-grid-column>
Typescript
public office: Reference[] = [{
id: 0,
name: ''
}];
...
public getOfficeNameById(id: number){
return this.office.find(office => office.id === id).name;
}
Thanks again!! I don't have enough rep to up vote answer.
You can use Cell template and bind the content to a method that will return the Office name by ID (the ID is available as part of the dataItem, accessible in the template), e.g.:
<kendo-grid-column field="CategoryID" title="Category">
<ng-template kendoGridCellTemplate let-dataItem>
{{getCategoryNameById(dataItem.CategoryID)}}
</ng-template>
</kendo-grid-column>
...
public categories = [{
CategoryID: 1,
CategoryName: 'Beverages'
}, {
CategoryID: 2,
CategoryName: 'Condiments'
}, {
CategoryID: 7,
CategoryName: "Produce",
}, {
CategoryID: 6,
CategoryName: "Meat/Poultry",
}, {
CategoryID: 8,
CategoryName: "Seafood",
}];
public getCategoryNameById(id: number) {
return this.categories.find(c => c.CategoryID === id).CategoryName;
}
EXAMPLE