8

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 Answers 5

5

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)" ...
Sign up to request clarification or add additional context in comments.

6 Comments

Hi Blazej, t is not working for me.. When i start typing Jho userId also have value of 'Jho'. if i select Jhon in the suggestion list userId remains 'Jho' and its not changed to 101 (input) event is not triggering when i select one of from user suggession list.
I've corrected my anwser not to use ngModel, can you please try now?
Getting error, Can't bind to 'value' since it isn't a known property of 'p-autoComplete'.
Hi NTN-JAVA, sorry for the confusion, I've tested it again, rolled back to ngModel and plugged in the onSelect event that was missing. This could be simplified if you don't want to capture the user's input (like when he enters any texts and submits form) just to cover the selection.
Hi Blazej, Good to see you again, This shows user_id in UI instead of user_name after choosing user in suggession list. If you removed [ngModel]="userId" from ur answer then it is what exactly we want :-). But the problem is edit screen may not have user_name in autocomplete box bcz i removed [ngModel]="userId".
|
1

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

8 Comments

what is the value of reservationDetails.guestType for u?
it is binding to { "user_id": 101, "user_name": "John" } not with 101.
@NTN-JAVA that is my model name for 2 way binding from component.I have updated my post with proper data.
,I think u misunderstand my question. i want to bind with user_id(ie.101 not entire user object) where as suggestion list should show user_name. Simply the way we used label and name for drop down component. In ur case the entire user object will be in userName
@NTN-JAVA ya got your question now. I will come up with working answer.
|
0

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

Comments

0

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.

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.