Reputation: 513
Am using Angular2: 2.1.0
and Primeng: 1.0.0
,
I want Autocomplete
component to bind to my object's key
and show object's value
in UI.
Here the Object is,
[{
"user_id": 101,
"user_name": "John"
},
{
"user_id": 101,
"user_name": "Ganesh"
},
{
"user_id": 101,
"user_name": "Irfan"
}]
app.component.html
<p-autoComplete [(ngModel)]="userId" placeholder="User Search..." field="user_name" [suggestions]="suggestionList" (completeMethod)="userSearch($event)"></p-autoComplete>
Using field
attribute in autocomplete i can show my object's value
in UI screen, but the entire object is binded to userId
How can i make binding user_id
of selected object to userId
?
Upvotes: 8
Views: 18835
Reputation: 4154
We can simply wrap primeNG's autocomplete inside a custom autocomplete component that implements ControlValueAccessor
interface.
The custom component will customize the data binding if a dataKey
is defined as an @Input
or keeps primeNG's default behavior if no dataKey
is defined.
In the following code I use only properties and events I need, but it can be applied to all properties and events provided by primeNG's API.
Here is the HTML code :
<p-autoComplete (completeMethod)="completeMethod.emit($event)"
(onClear)="onClear.emit($event)"
(onDropdownClick)="onDropdownClick.emit($event)"
(onSelect)="select($event)"
[dataKey]="dataKey"
[delay]="delay"
[disabled]="disabled"
[dropdown]="dropdown"
[emptyMessage]="emptyMessage"
[field]="field"
[forceSelection]="forceSelection"
[maxlength]="maxLength"
[minLength]="minLength"
[multiple]="multiple"
[placeholder]="placeholder"
[readonly]="readonly"
[required]="required"
[styleClass]="styleClass"
[suggestions]="suggestions"
[unique]="unique"
[(ngModel)]="autoCompleteValue">
</p-autoComplete>
And here is the typescript code :
import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'mb-auto-complete',
templateUrl: './auto-complete.component.html',
styleUrls: ['./auto-complete.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AutoCompleteComponent),
multi: true
}
]
})
export class AutoCompleteComponent implements ControlValueAccessor {
@Input() dataKey: string = null;
@Input() delay: number = 300;
@Input() disabled: boolean;
@Input() dropdown: boolean = false;
@Input() emptyMessage: string = null;
@Input() field: any = null;
@Input() forceSelection: boolean = null;
@Input() maxLength: number = null;
@Input() minLength: number = 1;
@Input() multiple: boolean = false;
@Input() placeholder: string;
@Input() readonly: boolean = false;
@Input() required: boolean = false;
@Input() styleClass: string = null;
@Input() suggestions: any[] = [];
@Input() unique: boolean = true;
@Output() completeMethod: EventEmitter<any> = new EventEmitter<any>();
@Output() onClear: EventEmitter<any> = new EventEmitter<any>();
@Output() onDropdownClick: EventEmitter<any> = new EventEmitter<any>();
@Output() onSelect: EventEmitter<any> = new EventEmitter<any>();
private onChange = (value: any): void => { /**/ };
private onTouched = (): void => { /**/};
public autoCompleteValue: any;
public registerOnChange(fn: any): void {
this.onChange = fn;
}
public registerOnTouched(fn: any): void {
this.onTouched = fn;
}
public setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
public writeValue(value: any): void {
if (this.dataKey?.length > 0) {
this.autoCompleteValue = this.suggestions.filter((item: any) => item[this.dataKey] === value)[0];
} else {
this.autoCompleteValue = value;
}
}
public select(selectedValue: any): void {
const newValue: any = this.dataKey?.length > 0 ? selectedValue[this.dataKey] : selectedValue;
this.onSelect.emit(newValue);
this.onChange(newValue);
}
}
You can then use your custom component, everywhere you use <p-autoComplete ..>
you can replace it by <mb-autoComplete ..>
(of course except in the html of AutoCompleteComponent
where you must keep <p-autoComplete ..>
).
Upvotes: 0
Reputation: 513
I had found a solution an year ago and updating my answer for others.
As stefan's
answer we need model mapping, but his answer looks large process.
I used primeng autocomplete component and created a own component called user-search
with @Input()
and @Output()
events.
Template (user.search.component.html)
<p-autoComplete [(ngModel)]="userObject" placeholder="User Search..." field="user_name" [suggestions]="userSuggesstionList"
(onSelect)="onUserSelect($event)" (completeMethod)="search($event)">
</p-autoComplete>
Component (UserSearchComponent ),
@Component({
selector: 'user-search',
templateUrl: 'user.search.component.html'
})
export class UserSearchComponent implements OnInit {
userSuggesstionList: any[] = [];
userObject: any;
constructor(
) { }
ngOnInit() {
}
// note that this must be named as the input model name + "Change"
@Output() userSelected: any = new EventEmitter();
@Output() userIdChange: any = new EventEmitter();
@Input()
set userId(userId: string) {
if (userId != null && userId != '') {
this.userObject = // Load user object from local cache / from service.
} else {
this.userObject = null;
}
}
get userId(): string {
if (this.userObject != null) {
return this.userObject.userId;
} else {
return null;
}
}
search(event) {
// your search logic.
}
onUserSelect(event) {
this.userIdChange.emit(event.userId);
this.userSelected.emit(event);
}
}
And the usage of user-search component is,
<user-search [(userId)]="user_id"></user-search>
Here the user_id given as input to user-search
component, user-search
component loads actual user object from cache/ from server as based on user_id
. once the user object gets loaded then p-autocomplete
will bind with userObject
and displayed the username in autocomplete box.
Once user selected from suggestion list, A default change event is triggered to update user_id
value in parent component.
Also you can avail the UserObject ie. {user_id: 'xxx', user_name:'xxx'} in userSelected
event.
Upvotes: 0
Reputation: 249
To summarize my understanding of the question and discussion so far:
This can be achieved in a generic way by wrapping the ControlValueAccessor interface that autocomplete (and all other input components in angular) implements. This wrapper can do the transformation. ngModel, formControl or formControlName directive is then used on the wrapper.
I have created a plunkr to show this approach. It uses "Country" instead of "User":
<control-value-mapper [formControl]="control" [toModel]="idOfCountry" [fromModel]="countryForId" >
<p-autoComplete #cvDelegate
[suggestions]="results"
(completeMethod)="search($event)"
field="name"
dataKey="id">
</p-autoComplete>
</control-value-mapper>
The ControlValueMapper looks like this:
@Component({
selector: 'control-value-mapper',
template: '<ng-content></ng-content>',
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ControlValueMapper),
multi: true
}]
})
export class ControlValueMapper implements ControlValueAccessor {
@ContentChild('cvDelegate')
delegate: ControlValueAccessor
@Input()
fromModel: (any) => any;
@Input()
toModel: (any) => any;
setDisabledState(isDisabled: boolean) {
this.delegate.setDisabledState(isDisabled);
}
writeValue(obj: any) {
this.delegate.writeValue(this.fromModel(obj));
}
registerOnChange(fn: any) {
this.delegate.registerOnChange(value => fn(this.toModel(value)));
}
registerOnTouched(fn: any) {
this.delegate.registerOnTouched(value => fn(this.toModel(value)));
}
}
"toModel" and "fromModel" are the functions that map from Country to its id and vice versa.
Note that this solution is probably 'longer' than others, but it can be re-used in all similar situations (with other input components than autocomplete).
Upvotes: 0
Reputation: 4678
I had the same issue and actually ended using a separate method to capture the value
captureId(event: any) {
this.userId = event.user_id;
}
And the actual use
<p-autoComplete (onSelect)="captureId($event)" ...
Upvotes: 5
Reputation: 403
@NTN-JAVA I have done this my using field property.
<p-autoComplete [(ngModel)]="userName" [suggestions]="filteredBrands" name="guestType"
(completeMethod)="filterBrands($event)" [size]="12" [minLength]="1" field="user_name" inputStyleClass="txt-box" placeholder="Hint: type 'v' or 'f'" [dropdown]="true" (onDropdownClick)="handleDropdownClick($event)">
</p-autoComplete>
guestDetails =
[{
"user_id": 101,
"user_name": "John"
},
{
"user_id": 102,
"user_name": "Ganesh"
},
{
"user_id": 103,
"user_name": "Irfan"
}]
**Javascript**
handleDropdownClick() {
this.filteredBrands = [];
setTimeout(() => {
this.filteredBrands = guestDetails;
}, 100);
}
Upvotes: 1