+1

Angular Components: custom drop-down component

Angular Components: custom drop-down component

Một trong những nhiệm vụ mới nhất của mình là custom drop-down component hỗ trợ cả hai lựa chọn single và multiple.

Mình sẽ chia sẻ cách làm của mình với bạn với hy vọng bạn sẽ học được những điều mới. 😃 Bắt đầu nào !! Chuẩn bị với hai components - data-selectdata-optioncomponents.

@Component({
  selector: 'data-option',
  template: `
    <div class="data-option" [ngClass]="...">
     <ng-content></ng-content>
    </div>
  `,
})
export class DataOptionComponent implements OnInit {
}
@Component({
  selector: 'data-select',
  template: `<ng-content></ng-content>`
})
export class DataSelectComponent {
  @ContentChildren(DataOptionComponent) options: QueryList<DataOptionComponent>;
    
  ngAfterContentInit() {
  }
}

Mẹo đầu tiên


Muốn tạo ra là một drop-down hỗ trợ nhiều lựa chọn, mình cần thêm một dấu kiểm cho biết liệu một tùy chọn có được chọn hay không.

Một cách để đạt được điều này là bằng cách thêm một type="checkbox" input() và theo dõi giá trị của nó.

@Component({
  selector: 'data-option',
  template: `
    <div class="data-option" [ngClass]="...">
     <input type="checkbox" *ngIf="isMulti">
     <ng-content></ng-content>
    </div>
  `,
})
export class DataOptionComponent {
  @Input() isMulti = false;
}

Và year...nó hoạt động, nhưng nhược điểm của phương pháp này là mình thêm một kiểm tra cho mỗi tùy chọn vào Angular và trong một danh sách thả xuống với 10.000 tùy chọn, chúng ta đã thêm 10.000 kiểm tra@@ ,đây mới chỉ là một ngIf; thông thường nó sẽ được đi kèm với ng-class, ng-style, v.v. 😦

Trong trường hợp của mình, hiệu suất rất quan trọng, vì vậy mình quyết định tách multi-option thành một component khác kế thừa từ single-option để tránh kiểm tra dư thừa.

Nhưng vẫn có những phần mà cả hai đều có điểm chung, vì vậy mình sẽ sử dụng template để giữ mã của DRY. 😊

export function getOptionTemplate(isMulti = false) {
  return `
     <div class="data-option">
       ${isMulti ? '<input type="checkbox">' : ''}
       <ng-content></ng-content>
     </div>
  `
}

@Component({
  selector: 'data-option',
  template: getOptionTemplate(),
})
export class DataOptionComponent {
}
@Component({
  selector: 'data-option-multi',
  template: getOptionTemplate(true)
})
export class DataOptionMultiComponent extends DataOptionComponent {
 }

Tiếp nào


Thay đổi trước đó mang đến một vấn đề mới 😵 . Mình muốn sử dụng multi-select dropdown:

@Component({
  selector: 'data-select',
  template: `<ng-content></ng-content>`
})
export class DataSelectComponent {
  @ContentChildren(DataOptionComponent) options: QueryList<DataOptionComponent>;
}
<data-select>
  <data-option-multi *ngFor="let option of options">{{option}}</data-option-multi>
</data-select>

Điều này sẽ không hoạt động khi decorator ContentChildren đang gọi đếnDataOptionComponent. Mình ta muốn tham chiếu option-multi components nên sẽ cần thêm một query ContentChildren khác.

@ContentChildren(DataOptionComponent) options: QueryList<DataOptionComponent>;
@ContentChildren(DataOptionMultiComponent) optionsMulti: QueryList<DataOptionMultiComponent>;

Nhưng giữ cả hai dòng code này thì @@ mình muốn code đẹp bằng cách loại bỏ hai dòng này ukm 🤔 mình có thể giải quyết vấn đề này một cách thanh lịch hơn với sự giúp đỡ của Angular DI.

@Component({
  selector: 'dato-option-multi',
  template: getOptionTemplate(true),
  providers: [{provide: DataOptionComponent, useExisting: DataOptionMultiComponent}]
})
export class DataOptionMultiComponent extends DataOptionComponent {
}

Mình nói với Angular - khi cần DataOptionComponent, hãy sử dụng DataOptionMultiComponent hoàn hảo ✌️

Còn gì để cải thiện không nhỉ


Mình chưa hài lòng. Mình muốn sử dụng cùng một bộ chọn -data-option- cho cả single và multiple drop-down. Mình đã đổi multi-option thành giống nhau.

@Component({
  selector: 'data-option',
  ...
})
export class DataOptionMultiComponent extends DataOptionComponent {
}

Đó gặp vấn đề mới luôn. Angular doesn't cho phép các bộ chọn trùng lặp và đưa ra lỗi: 🤣

More than one component matched on this element.

Make sure that only one component’s selector can match a given element.

Dăm ba cái lỗi giải quyết đơn giản 🕵 Cách mình giải quyết là bằng cách tận dụng thực tế là Angular hỗ trợ :not CSS selector.

@Component({
  selector: 'data-option:not([multi])',
  ...
})
export class DataOptionComponent implements OnInit {
}
@Component({
  selector: 'data-option[multi]',
  ...
})
export class DataOptionMultiComponent extends DataOptionComponent {
}

Bây giờ mình có thể gọi tới cùng một bộ chọn và thêm thuộc tính multi-option khi cần. Điều này cảm thấy phù hợp hơn, thay vì gọi tới một bộ chọn hoàn toàn mới.

Có cải tiến được gì nữa không 😘


Drop-down với hộp tìm kiếm đang thực hiện tìm kiếm theo mặc định. Mình cũng cần tùy chọn để thực hiện tìm kiếm phía server-side.

Cách để làm điều này là tạo cả input()output():

@Component({
  selector: 'data-select',
  template: `<ng-content></ng-content>`
})
export class DataSelectComponent {
  @ContentChildren(DataOptionComponent) options: QueryList<DataOptionComponent>;
  @Input() internalSearch = true;
  @Output() search = new EventEmitter();
}

Nhưng hình như input là dư thừa và có thể tránh được 👼 Mình có thể biết được liệu có someone đang lắng nghe sự kiện search hay không và dựa vào đó quyết định có kích hoạt tìm kiếm hay không.

@Component({
  selector: 'data-select',
  template: `<ng-content></ng-content>`
})
export class DataSelectComponent {
  @ContentChildren(DataOptionComponent) options: QueryList<DataOptionComponent>;
  @Output() search = new EventEmitter();
  
  private internalSearch;
  
  ngOnInit() {
    this.internalSearch = this.search.observers.length === 0;
  }
}

Tổng kết

Trên đây chúng ta đã tiến hành custom drop-down component hỗ trợ cả hai lựa chọn single và multiple với Angular. Dăm ba cái custom cũng không quá rắc rối phải không ạ :3.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí