7
votes

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 ?

5

5 Answers

5
votes

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)" ...
1
votes

@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);
          }
0
votes

To summarize my understanding of the question and discussion so far:

  • the autocomplete gives us a User as model
  • but what we want is user_id
  • basically, we need a "model mapping" from User to user_id and also the other way around (if our model is initialized with a user_id, the according User should be pre-selected in the auto-complete )

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).

0
votes

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.

0
votes

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 ..>).