1

I'm trying to build a demo app where I can plan students and their desks. When a classroom has too many desks I can put them in another classroom. I can then plan the students on said desks. I'm using cdkDropListGroup because the amount of classrooms is a dynamic.

When I drop a desk with students on the 'not assigned' student list, the desk remains in the class room, but the students drop into the list as expected. However when I try to drop the students on the desks, the predicate, which only allows products to be dropped in the list, doesn't allow them. When I remove the predicate however, the student are dropped in the classrooms list instead of the desks...

Dropping desks with students works properly:

Proper functioning of the app

Dropping students on desks fails:

Failing part

I need help with dropping the students specifically on the desks instead of the classrooms, causing dropStudent to trigger.

I thought raising the container for the desks to a higher Z-Index might help but that didn't work.

references:

James D
  • 1,975
  • 2
  • 17
  • 26

1 Answers1

3

You need to use the cdkDropListConnectedTo option on the not assigned students lists, and add all instances of the desk lists. That's beacuse the CdkDropListGroup only looks at sibling lists, not at children:

You need to add a template reference to your desk droplist, so you can query this in your component ts, or you can use the a string of IDs. I think the template reference is more versatile, but for the sake of example I'll use the ids:

<div cdkDropList 
      id="{{studentList.listName}}" 
      [cdkDropListData]="studentList.student"
      class="example-list"
      (cdkDropListDropped)="returnStudent($event)"
      [cdkDropListEnterPredicate]="userReturnPredicate"
      [cdkDropListConnectedTo]="desks">
  <div  *ngFor="let person of studentList.student"
        class="example-box" 
        cdkDrag
        [cdkDragData]="person">
    {{person.userName}}
  </div>
</div>

As you can see the cdkDropListConnectedTo has desks binded. In your component you can define desks like this:

desks: string[] = [];

this.desks = this.projectLists.map(
  (list) => list.products.map((product) => product.productName)
).flat(2);

To make it work with ViewChildren, you need something like this in your ngAfterViewInit:

ngAfterViewInit(): void {
  this.deskLists.changes.pipe(
    startWith(true),
    tap(() => {
      this.desks = this.deskLists.toArray();
      this.cd.markForCheck();
      this.cd.detectChanges();
    }),
  ).subscribe();
}

working example

Poul Kruijt
  • 69,713
  • 12
  • 145
  • 149
  • Thanks for the quick, full answer, with working example and explanation! I didn't know I could assign a String[] to `cdkDropListConnectedTo` – James D Feb 18 '20 at 13:39
  • Seems that after I move a desk, I can't drop a student anymore to that specific desk. When I add ` this.desks = this.deskLists.toArray(); this.cd.markForCheck(); this.cd.detectChanges();` to `dropStudent`, it'll work after dropping one student on another desk – James D Feb 18 '20 at 14:09
  • @JamesD Right, i noticed the string id list wasn't working anymore, so the 'working example' in the link actually uses the desklists with `ViewChildren`. It's the best option anyways :)/ O – Poul Kruijt Feb 18 '20 at 14:18
  • 1
    @JamesD I've updated my answer with a better way to handle changes in the lists. This will make sure the desks get updated. You can also use this as an observable without the subscribe, and use the `async` pipe in your template – Poul Kruijt Feb 18 '20 at 14:24
  • Found the answer to my secondary question when moving a desk. By adding `setTimeout(() =>{ this.desks = this.deskLists.toArray();}, 0);` at the end of the `drop` function everything works perfectly, even after moving desks – James D Feb 18 '20 at 14:24
  • @JamesD right, that would be the same as actually listening to changes on the QueryList :) which I think it's a bit more clean than using setTimeout – Poul Kruijt Feb 18 '20 at 14:26
  • Yeah your solution is definitly cleaner! Thanks! – James D Feb 18 '20 at 14:27
  • 1
    @JamesD You're welcome. It needs the `startWith(true)`, because otherwise it does not got triggered when the component is loaded, just fyi :D – Poul Kruijt Feb 18 '20 at 14:30