javascript - get ElementRef.nativeElement width properties from component after data injection
问题描述
I have a component provided by an external Angular component library which is a private open-source project for the company I work for. The component has it's content injected into it from an Observable, and this data is not available at the initial render. The component in question is a Data Table Cell, part of a larger Data Table, much like the Material data table.
One of these cells has a long string that, depending on the data that is returned from the API, may overflow from the container. In this case, I need to shorten the string by chopping the middle out and replacing it with '...'. The string is a file path, e.g 'C:\really\really\really\really\really\long\path\to\file.txt'. Say the overflow was hidden from the '\path\to\file.txt' section, I would want the middle chopped out like so: 'C:\really\really...\long\path\to\file.txt'. This is similar to how macOS handles long file paths in Finder. I can do this fine, using a Pipe, however I'm unsure how to get the width of the component. I have tried using a ViewChild, like this:
@ViewChild('path') public path: ElementRef;
However, when I look at the measurement properties in the path.nativeElement
object, they're all 0 - for example:
scrollHeight: 0
scrollLeft: 0
scrollTop: 0
scrollWidth: 0
I think this is because the data is injected into the table after the load. I have tried looking at the path
variable in ngAfterViewInit
, and it's getting the right component, but the innerHTML, text, content attributes are all empty. How would I get the most recent version of this variable?
Alternatively, if you have any other suggestions for how I could achieve this, that would be excellent. Thank you.
EDIT: Some updates. I have now got two options: I can use a Pipe in the HTML, and send the path ElementRef to the pipe, like this:
<div #path>{{ value | truncate: path }}</div>
And the Pipe code looks like this:
@Pipe({ name: 'truncate' })
export class TruncatePipe implements PipeTransform {
public transform(value: string, element: any): string {
const { scrollWidth, clientWidth } = element;
// computation to figure out what the value of the new string should be
return value;
}
}
The element has the un-truncated value in, but the scrollWidth and the clientWidth are equal, which is incorrect when the value is too long for the container (so it overflows); the scrollWidth should be longer than the clientWidth. This means I cannot compute how many characters should be truncated, as the pipe has no idea how far it's overflowing. I think the reason for these two variables being the same value is that the data has not yet been injected, so the element (a div, so a block element) is taking up the width of it's container. If I inspect the element after, I can see that scrollWidth is larger than the clientWidth.
The other way I've tried is to use a proxy function in my component class, which then returns the value from the pipe, like this:
public truncatePath(value: string, element: any) {
return new TruncatePipe().transform(value, element);
}
and the HTML:
<div #path>{{ truncatePath(value, path) }}</div>
This works (clientWidth and scrollWidth are different), but it repeatedly runs the function, which is obviously hindering performance of the application.
EDIT 2: if I put the pipe code in a setTimeout
, the clientWidth is the correct value and the string gets truncated, however as it's in the setTimeout it doesn't render that string.
解决方案
我使用管道找到了答案。这是我的代码:
@Pipe({ name: 'truncate' })
export class TruncatePipe implements PipeTransform {
public transform(value: string, element: any): Observable<string> {
return new Observable<string>((observer: Observer<string>) => {
setTimeout(() => {
const { scrollWidth, clientWidth } = element;
if (scrollWidth > clientWidth) {
const avgCharWidth = Math.floor(scrollWidth / value.length);
const middleOfString = Math.floor(value.length / 2);
const numberToRemove = Math.ceil((scrollWidth - clientWidth) / avgCharWidth) + 1;
const parts = [
value.slice(0, middleOfString - (numberToRemove / 2)),
'…',
value.slice(middleOfString + (numberToRemove / 2))
];
observer.next(parts.join(''));
}
});
observer.next(value);
});
}
}
和 HTML:
<div #path [innerHTML]="value | truncate:path | async"></div>
一些注意事项:
- 管道返回 之外的值
setTimeout
,否则该元素没有被渲染一次,因此没有 DOM 属性。然后它再次运行并检查初始值是否溢出,如果溢出,则计算中间带有“...”的新字符串。这确实意味着 Pipe 对每个值运行两次,这可能会因 100 行而变得相当昂贵,但我现在没有其他方法可以做到这一点。 AsyncPipe
呈现来自 Observable 的最新值- 它需要在 setTimeout 内,以便它的渲染在单独的进程中
结果:
- 原始字符串:C:\really\really\really\really\long\path\to\file1.txt(溢出容器)
- 截断字符串:C:\really\really\real...long\path\to\file1.txt
- 流程:管道运行一次,返回原值。然后 setTimeout 代码运行,发现原始字符串溢出并计算新字符串。异步管道更新渲染中的值。
--
- 原始字符串:C:\path\to\file1.txt(适合容器)
- 截断的字符串:C:\path\to\file1.txt
- 流程:管道运行一次,返回原始值。setTimeout 代码运行,发现原始字符串 string 没有溢出,因此不会继续。
我很想知道是否有进一步的方法来改进这一点,以便 Pipe 不需要运行两次,并且删除 setTimeout 的方法也很棒。
推荐阅读
- xml - 将 2 个新字符串插入现有 XML 配置文件的 Powershell 脚本
- php - Doctrine / ExtJS:使用模型创建另一个模型
- angular - 如何在 Angular8 中的 svg 动画标签上调用 beginElement()
- maven - 依赖于 maven-model 的编译失败
- sas - 如何在 SAS 的数据步骤中为一行添加超过 1 行
- c - 有没有办法仅在 C 中使用 while 和 if 来查找最大和最小数?
- chatbot - 如何在 rasa Chatbot 中获得最新的机器人响应?
- python - 如何在 Pandas 1.1.2 中获取多个标签?
- react-native - 在 iOS 上 React Native 将图像上传到服务器
- javascript - 为什么 JS map 方法返回这个结果?