Вопрос или проблема
Я работаю над пользовательским компонентом Angular для таблицы, который использует липкоеPositioning для определенных ячеек. Моя цель – сделать так, чтобы первый столбец прилипал к левой стороне таблицы, а последующие липкие столбцы корректировали свое положение в зависимости от ширины предыдущих соседних ячеек.
Вот моя реализация ApUiTdComponent
:
import { CommonModule } from '@angular/common';
import { AfterViewInit, Component, ElementRef, HostBinding, Input, Renderer2 } from '@angular/core';
@Component({
selector: 'ui-td',
standalone: true,
templateUrl: './td.component.html',
styleUrls: ['./td.component.css'],
imports: [CommonModule],
})
export class ApUiTdComponent implements AfterViewInit {
@Input() public fixed: 'LEFT' | 'RIGHT' | null = null;
private _offsetLeft = 0;
constructor(
private el: ElementRef,
private renderer: Renderer2,
) {}
@HostBinding('style')
public get styleBindings(): { [key: string]: string | null } {
const baseStyles = {
position: this.fixed ? 'sticky' : null,
backgroundColor: 'white',
};
if (this.fixed === 'LEFT') {
return {
...baseStyles,
left: `${this._offsetLeft}px`,
boxShadow: '3px 0px 5px rgba(0, 0, 0, 0.1)',
};
} else if (this.fixed === 'RIGHT') {
return {
...baseStyles,
right: '0px',
boxShadow: '-3px 0px 5px rgba(0, 0, 0, 0.1)',
};
} else {
return {};
}
}
public ngAfterViewInit(): void {
this.calculateOffset();
}
private calculateOffset(): void {
if (this.fixed === 'LEFT') {
let offset = 0;
let sibling = this.el.nativeElement.previousElementSibling;
// Цикл по всем предыдущим соседям
while (sibling) {
const siblingTag = sibling.tagName;
const siblingWidth = sibling.offsetWidth;
const siblingFixedAttr = sibling.getAttribute('fixed');
console.log({
siblingTag,
siblingWidth,
siblingFixedAttr,
});
if (siblingTag === 'UI-TD' && siblingFixedAttr === 'LEFT') {
offset += siblingWidth;
}
sibling = sibling.previousElementSibling;
}
this._offsetLeft = offset;
}
}
}
В моем шаблоне я использую компонент таким образом:
@for (todo of (data$ | async)?.data; track $index) {
<ui-tr>
<ui-td
fixed="LEFT"
class="font-monospace oneline-ellipsis mw-175px"
Sticky Table Cell Offset Calculation Not Working in Custom Component
="todo.id"
>
{{ todo.id }}
</ui-td>
<ui-td [fixed]="'LEFT'" class="oneline-ellipsis mw-300px" Sticky Table Cell Offset Calculation Not Working in Custom Component
="todo.title">
{{ todo.title }}
</ui-td>
<ui-td class="font-monospace"> {{ todo.completed }} </ui-td>
</ui-tr>
}
Вывод в консоль, когда я регистрирую информацию о соседях, показывает, что ширина первой липкой ячейки правильно рассчитывается:
{
"siblingTag": "UI-TD",
"siblingWidth": 175,
"siblingFixedAttr": "LEFT",
"siblingTextContent": "text-content"
}
Тем не менее, вторая компонента ui-td, также отмеченная как fixed=”LEFT”, не корректирует свое положение и сохраняет стиль left: 0px, вместо того чтобы получать рассчитанный офсет.
Почему вторая компонента ui-td не получает правильный офсет?
Есть ли лучший подход к расчету офсетов для липких ячеек таблицы в Angular?
Ответ или решение
Чтобы решить проблему с вычислением смещения для столбцов с фиксированным положением в вашем кастомном компоненте Angular, давайте рассмотрим реализацию компонента ApUiTdComponent
и возможные недочёты.
Причины проблемы
-
Логика вычисления смещения: Ваш текущий код в методе
calculateOffset
базируется на определении суммы ширин только тех предыдущих ячеек, которые также имеют фиксированное положение слева. Это может быть неэффективным, если вы хотите учитывать ширины всех предыдущих ячеек, независимо от их фиксированного статуса. Вероятно, в вашем случае, второй элемент с фиксированным атрибутомLEFT
не получает корректное смещение из-за того, что он также считает первую ячейку фиксированной. -
Событие инициализации: Метод
ngAfterViewInit
вызывается один раз после инициализации представления, что может быть не достаточно для динамически меняющегося контента. Еслиdata$
обновляется после инициализации, смещения могут быть неверными.
Решение
- Измените метод
calculateOffset
для учета всех предыдущих ячеек, а не только фиксированных слева. Попробуйте следующее:
private calculateOffset(): void {
if (this.fixed === 'LEFT') {
let offset = 0;
let sibling = this.el.nativeElement.previousElementSibling;
// Цикл по всем предыдущим ячейкам
while (sibling) {
const siblingWidth = sibling.offsetWidth;
offset += siblingWidth;
sibling = sibling.previousElementSibling;
}
this._offsetLeft = offset;
}
}
- Обновление при изменении данных: Используйте
ChangeDetectorRef
для принудительного обновления, когда данные компонента меняются. Вы можете подписаться на изменения вашегоdata$
и вызыватьcalculateOffset
при каждой подписке, чтобы гарантировать, что смещения обновляются.
constructor(
private el: ElementRef,
private renderer: Renderer2,
private cdr: ChangeDetectorRef,
) {}
public ngAfterViewInit(): void {
this.calculateOffset();
}
// Предположим, у вас есть data$
ngOnInit(): void {
this.data$.subscribe(() => {
this.calculateOffset();
this.cdr.detectChanges();
});
}
Альтернативный подход
Вместо ручного вычисления смещений вы можете рассмотреть использование CSS-свойств для автоматического управления позициями. Например, вы можете использовать CSS Grid или Flexbox, которые позволяют легче управлять адаптивным расположением ячеек таблицы без особых вычислений смещения. Таким образом, фиксированные ячейки могут работать более предсказуемо.
Этот подход также будет полезен, если ваша таблица будет реагировать на изменения размера окна или контента, поскольку CSS будет управлять позиционированием автоматически.
Заключение
Если вы внести предложенные изменения и использовать более продвинутые подходы к вёрстке, ваша проблема с не корректным получением смещения должна быть устранена. Если у вас будут дополнительные вопросы или потребуется помощь, не стесняйтесь обращаться.