Problems encountered when grouping options in ng select


In the compilation of resident choice components, my original idea was to achieve the effect like above, showing the name, telephone number, ID number of the residents, and grouping them according to the plot and building, but there were some bug in the group.
When I get residents again, I get all residents first and then pass filter to obtain the required residents.

residents = residents.filter(residents => 
  this.isContained(districtIds, residents.houses.map(houses => houses.unit.building.village.community.id))
)
  isContained(numbers: Array<Number>, newNumbers: Array<Number>): boolean {
    return newNumbers.filter((x) => {
      return numbers.includes(x);
    }).length != 0;
  }

The isContained function is used to judge whether the two arrays have an intersection. If so, it returns true. In this way, as long as the resident has a house within the specified range, it will be obtained.
However, problems arise when grouping with groupBy and groupValue

 groupBy(resident: Resident) {
    return resident.houses[0].unit.building.id;
  };

  groupValue(key: string | object, children: Resident[]): Building {
    const building = children[0].houses[0].unit.building;
    Assert.isDefined(building.name, 'name must be defined');
    Assert.isDefined(building.village.name, 'name must be defined');
    return building;
  }

At first, I wanted to classify directly according to the first house, but after the test, it was found that if the resident also has houses in other areas and is the first, it will be classified according to this house. Although the resident belongs to the management area, the classification category does not belong to the management area, which obviously does not meet our needs.
So I made the following changes:
At first, I thought that I only need to add judgment in groupBy and groupValue, find houses within the jurisdiction in the received resident, and return.

  groupBy(resident: Resident) {
    const ids = resident.houses.map(houses => houses.unit.building.village.community.id);
    const index = ids.indexOf(this.districtIds[0]);
    return resident.houses[index].unit.building.id;
  };

groupValue(key: string | object, children: Resident[]): Building {
    const buildings = children[0].houses.map(houses => houses.unit.building);
    const communityIds = buildings.map(buildings => buildings.village.community.id);
    const index = communityIds.indexOf(this.districtIds[0]);
    const building = buildings[index];
    Assert.isDefined(building.name, 'name must be defined');
    Assert.isDefined(building.village.name, 'name must be defined');
    return building;
}

This DistrictIds is obtained by calling the corresponding M layer method in ngOnInit.

 this.districtService.getDistrictsOfCurrentLoginUser(DISTRICT_TYPE.community.value)
.subscribe(districts => {
  const districtIds = districts.map(value => value.id);
        this.districtIds = districtIds;
        console.log("ngOnInit");
        console.log(this.districtIds);
        ...
}

However, an error will be reported at runtime - typeerror: cannot read properties of undefined (reading 'partitions'), that is, this is empty. Then try to output such an output in groupBy - console log(this);, The result is undefined;
After querying on Google, I found that someone's situation is a little similar to mine - this is undefined, but his problem is to use this in a callback function. When it is executed, the range is different.
For example, we input the following code in the compiler:

    var Bob = {
      sname: "Bob",
      friends: ["Jack", "Rose", "Tom", "Jerry"],
      intr() {
        this.friends.forEach(function (ele) {
          console.log(this.sname + "know" + ele);@An error is reported in this line
        });
      }
    }

The compiler will report an error on the @ line and give a solution - change to the arrow function, that is

intr() {
        this.friends.forEach((ele) => {
          console.log(this.sname + "know" + ele);
        });
      }

Therefore, I guess the groupBy function here is also regarded as a callback function, but it is obviously not applicable to change the method to the arrow function here. After query, I found another method - bind this with this method with the bind method. It will create a new function with the same body as the original function, and this in the new function points to the incoming object.
We can call this method in the constructor as shown below.

  constructor(private residentService: ResidentService,
              private districtService: DistrictService) {
    this.groupBy = this.groupBy.bind(this);
    this.groupValue = this.groupValue.bind(this);
  }

Then we can use this pointer here to execute as we think.

Keywords: Front-end angular TypeScript

Added by rcmehta_14 on Sat, 26 Feb 2022 12:05:12 +0200