Gbuomprisco / ngx-chips

Tag Input component for Angular
MIT License
903 stars 359 forks source link

ngModel fails with async pipe #902

Closed vsarunov closed 5 years ago

vsarunov commented 5 years ago

PLEASE MAKE SURE THAT:

I'm submitting a ... (check one with "x")

[x] bug report => search github for a similar issue or PR before submitting
[ ] support request/question

Notice: feature requests will be ignored, submit a PR if you'd like

Current behavior

I have the following code

 <tag-input #tagInput [theme]="'foundation-theme'" [ngModel]="selectedTags$ | async" (click)="open()" [onlyFromAutocomplete]="false" (onAdd)="onAdd($event)" (onRemove)="onRemove($event)">
    <tag-input-dropdown #tagDropdown [minimumTextLength]="0" [keepOpen]="true" [showDropdownIfEmpty]="true" [autocompleteObservable]="requestAutocompleteItemsFake">
    </tag-input-dropdown>
  </tag-input>

there is an async pipe in ngModel in tag-input, I am using ngxs state management, so this is populated with state.

when ever I add a tag from a differen component and trigger an event to add a tag to the state this should add a tag a to the tag-input component and I should see it. I am able to do that first time, I am able to add many tags. However as soon as I remove one and then try to add a tag again it fails, the tag does not appear, it can be the same or different tag it still does not appear. I do not see any errors in the console and the state gets updated perfectly and other events concerning this tag get triggered and work fine.

On top of that I am removing the tags I add from the list that can be seen in the dropdown as it is as well state managed as can be noticed and I add them back to that list as soon as I have removed the items, however this did not actually play any role in the issue.

The type: DisplayFilter is a representation of your TagModel {value, display} named liked that for our clarification purposes.

this is the typescript code:

export class ChipTagsComponent implements OnInit {
  @ViewChild('tagInput') private tagInput: TagInputComponent;
  @ViewChild('tagDropdown') private tagInputDropdown: TagInputDropdown;
  @Select(AppState.getTags) tags$: Observable<Array<DisplayFilter>>;
  @Select(AppState.getSelectedTags) selectedTags$: Observable<Array<DisplayFilter>>;
  constructor(private store: Store) {
  }

  ngOnInit(): void {
  }

  public requestAutocompleteItemsFake = (text: string): Observable<Array<DisplayFilter>> => {
    return this.tags$;
  }

  public open(): void {
    this.tagInputDropdown.show();
  }

  public onAdd(filter: DisplayFilter): void {
    this.store.dispatch(new AddTag(filter.value));
  }

  public onRemove(filter: DisplayFilter): void {
    this.store.dispatch(new RemoveTag(filter.value));
  }
}

Please note tags can be added externally via state as well as via the dropdown and the state is triggered as well.

Expected behavior

Tags are added and removed and added back again and are shown in the tag-input.

Minimal reproduction of the problem with instructions (if applicable)

Create two different component one of which has a the tag-input and another triggers tag adding via the state management tool :ngxs, ngrx - this should not make a difference as I have figured out, make the ngModel input on model using async pipe like in the code above, add a tag or a couple and then remove one and try to add again.

What do you use to build your app?. Please specify the version

Angular-cli

Angular version:

angular version: 7.3.4

ngx-chips version:

ngx-chips version: 2.0.2

Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]

I have highlighted, but just in case, chrome and firefox both.

Gbuomprisco commented 5 years ago

I don't know Ngxs, but doesn't that go in a loop? Isn't it easier to use an initial local value, and update the state on model changes?

Also, maybe try to rewrite it with FormControl?

vsarunov commented 5 years ago

What do you mean by loop? So you are updating the state model, but this need to have influence on the selected tags list.

BTW guess this is still an issue, though your advice solved the problem, I have switched to formControl together with ngxs forms and it seems to be working like a charm, thus closing this. Thank you for the help.

For those who struggle with this a quick example of how I did it bellow:

used this: https://www.ngxs.io/v/master/plugins/form#actions

Initial setup in the component that uses the tags:

form: FormGroup = new FormBuilder().group({
    selectedTags: []
  });
<div class="container">
  <form [formGroup]="form" ngxsForm="appstore.selectedTagsForm">
    <tag-input #tagInput [theme]="'foundation-theme'" [formControlName] ="'selectedTags'" (click)="open()"
      [onlyFromAutocomplete]="false" (onAdd)="onAdd($event)" (onRemove)="onRemove($event)">
      <tag-input-dropdown #tagDropdown [minimumTextLength]="0" [keepOpen]="true" [showDropdownIfEmpty]="true"
        [autocompleteObservable]="requestAutocompleteItemsFake">
      </tag-input-dropdown>
    </tag-input>
  </form>
</div>

ngxs state model code:

export interface AppStateModel {
  selectedTagsForm: {
    model?: {
      selectedTags: Array<DisplayFilter>
    };
  };
}
@State<AppStateModel>({
  name: "appstore",
  defaults: {
    selectedTagsForm: {
      model: {
        selectedTags: []
      }
    },
  }
})