【问题标题】:Angular - stop scroll snapping to the top on subscription timerAngular - 在订阅计时器上停止滚动捕捉到顶部
【发布时间】:2021-08-25 16:03:06
【问题描述】:

我有一个可滚动的自我更新订单列表。这是父组件,其中列表在 2 分钟的计时器上更新,设置如下:

ngOnInit(): void {
    this.internalError = false;
    this.subscription = timer(0, 120 * 1000).pipe(
        switchMap(() => this.shopService.getPreferencesAsObservable()),
        filter(preferences => !!preferences), // emit only if `preferences` is defined and truthy
        tap(() => {
            this.isDeliverySlotsActive = this.shopService.isDeliverySlotsActive();
        }),
        switchMap(() => 
            forkJoin([
                    this.getInitialPendingOrders(true), 
                    this.getInitialPendingOrders(false),
                    this.getInitialDeliveredOrders(true),
                    this.getInitialDeliveredOrders(false),
                    this.getInitialCancelledOrders(true),
                    this.getInitialCancelledOrders(false)
                ]
            )
        )
      ).subscribe(res => {
            /* pending slots */
            this.loadedPendingSlotsOrders = [...res[0]["results"]];
            this.pendingSlotsOrderTimelines = this.ordersService.createTimelines(this.loadedPendingSlotsOrders, true);
            /* pending no slots */
            this.loadedPendingNoSlotsOrders = this.ordersService.filterOrders(res[1]["results"], OrderStatus.PENDING, false);
            this.loadedProcessedNoSlotsOrders = this.ordersService.filterOrders(res[1]["results"], OrderStatus.PROCESSED, false);
            this.loadedPendingNoSlotsOrders = [...this.loadedPendingNoSlotsOrders, ...this.loadedProcessedNoSlotsOrders];
            this.pendingNoSlotsOrderTimelines = this.ordersService.createTimelines(this.loadedPendingNoSlotsOrders, false);
            /* delivered slots */
            this.loadedDeliveredSlotsOrders = [...res[2]["results"]];
            this.deliveredSlotsOrderTimelines = this.ordersService.createTimelines(this.loadedDeliveredSlotsOrders, true);
            /* delivered no slots */
            this.loadedDeliveredNoSlotsOrders = this.ordersService.filterOrders(res[3]["results"], OrderStatus.DELIVERED, false);
            this.deliveredNoSlotsOrderTimelines = this.ordersService.createTimelines(this.loadedDeliveredNoSlotsOrders, false);
            /* cancelled slots*/
            this.loadedCancelledSlotsOrders = [...res[4]["results"]];
            this.cancelledSlotsOrderTimelines = this.ordersService.createTimelines(this.loadedCancelledSlotsOrders, true);
            /* cancelled no slots */
            this.loadedCancelledNoSlotsOrders = this.ordersService.filterOrders(res[5]["results"], OrderStatus.CANCELLED, false);
            this.cancelledNoSlotsOrderTimelines = this.ordersService.createTimelines(this.loadedCancelledNoSlotsOrders, false);
            /* data is ready, we can stop showing spinner */
            this.isLoaded = Promise.resolve(true);
    });
}

getInitialPendingOrders(hasSlots: boolean){
    return this.apiService.fetchShopOrders("status=PENDING&status=FULFILLED" + (hasSlots ? "&only_slots=true" : ''));
}

getInitialDeliveredOrders(hasSlots: boolean){
    return this.apiService.fetchShopOrders("status=DELIVERED" + (hasSlots ? "&only_slots=true" : ''));
}

getInitialCancelledOrders(hasSlots: boolean){
    return this.apiService.fetchShopOrders("status=CANCELLED" + (hasSlots ? "&only_slots=true" : ''));
}

另外,组件树如下:

OrdersComponent (prepare data to send to children) -> OrdersListComponent (*ngFor of sections) -> OrdersListSectionComponent (*ngFor of orders) -> OrdersListItemComponent (single order)

在这里您可以看到列表的外观(它是一个内部可滚动的 div):

可滚动div的css:

.scrollable {
    min-height: 10vh;
    max-height: 50vh; 
    overflow: auto;
    background: 
        linear-gradient(white 33%, rgba(179,86,216, 0)),
        linear-gradient(rgba(179,86,216, 0), white 66%) 0 100%,
        radial-gradient(farthest-side at 50% 0, rgba(34,34,34, 0.5), rgba(0,0,0,0)),
        radial-gradient(farthest-side at 50% 100%, rgba(34,34,34, 0.5), rgba(0,0,0,0)) 0 100%;
    background-repeat: no-repeat;
    background-attachment: local, local, scroll, scroll;
    background-size: 100% 1.5rem, 100% 1.5rem, 100% 0.5rem, 100% 0.5rem;
    scroll-behavior: smooth;
}

OrdersListSectionComponent 的模板:

<div *ngFor="let order of sectionOrders">
    <app-orders-list-item
        [order]="order"
        [timeline]="timeline"
    ></app-orders-list-item>
    <hr class="order-divider"/>
</div>

我遇到了这样一种情况,即当触发计时器时,此列表的滚动条会卡到顶部。这很糟糕,因为如果用户正在滚动列表,则滚动不应该回到顶部。

有谁知道我可以如何阻止这种不需要的滚动行为?

【问题讨论】:

  • 如果你使用 ngFor 来迭代项目然后尝试添加 trackBy 函数
  • @Chellappanவ 请解释这将如何解决我的问题...
  • 它不会重新创建已经渲染的项目。所以使用 trackby 可以解决这个问题。你试过了吗?
  • 不幸的是没有工作。
  • 首先,您订阅了无数次 - 在订阅中订阅。这是一种反模式,也是因为您无法控制正在发生的事情。在filter() 之后,您应该插入switchMap(() =&gt; this.shopService.getPreferencesAsObservable()) 并删除内部订阅。我建议您采用这种模式。最后,我们不知道 getOrders 里面是什么,所以很难知道是什么导致了返回顶部。

标签: html css angular typescript rxjs


【解决方案1】:

您可以尝试使用element.scrollTop 保存滚动条的位置,然后使用保存的值将 scrollTop 属性设置为调用定时函数时的位置。

(https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop?retiredLocale=it - https://www.javascripttutorial.net/dom/css/get-and-set-scroll-position-of-an-element/)

【讨论】:

  • 这种方法的一种变体对我有用。我跟踪了滚动的位置,并在计时器订阅中使用filter() 运算符来过滤掉滚动位置不是起始位置的排放。
【解决方案2】:

订阅您的计时器,然后滚动重置您的计时器,例如:

timer = timer(0, 120 * 1000);
timersubscription: Subscription;

initializeTimer(): void { {
    this.timersubscription = this.timer.subscribe(() => console.log("Timer triggered");
}

refreshTimer(): void {
    this.timersubscription.unsubscribe();
    this.initializeTimer();
}

<div (scroll)="refreshTimer()"></div>

【讨论】:

  • 重新订阅的行为本身就是将滚动条拉到顶部。
猜你喜欢
  • 2011-07-04
  • 1970-01-01
  • 2016-09-13
  • 2019-11-10
  • 1970-01-01
  • 2016-07-25
  • 1970-01-01
  • 2011-02-22
  • 1970-01-01
相关资源
最近更新 更多