【问题标题】:Angular2: Change detection timing for an auto-scroll directiveAngular2:更改自动滚动指令的检测时间
【发布时间】:2016-07-13 05:03:24
【问题描述】:

我一直在研究一个简单的聊天显示自动滚动指令:

@Directive({
    selector: "[autoScroll]"
})
export class AutoScroll {
    @Input() inScrollHeight;
    @Input() inClientHeight;

    @HostBinding("scrollTop") outScrollTop;

    ngOnChanges(changes: {[propName: string]: SimpleChange}) {
        if (changes["inScrollHeight"] || changes["inClientHeight"]) {
            this.scroll();
        }
    };

    scroll() {
        this.outScrollTop = this.inScrollHeight - this.inClientHeight;
    };
}

当我设置enableProdMode() 并且ChangeDetectionStrategy 设置为默认值时,该指令将起作用,但在“开发模式”下我得到一个异常。我可以将ChangeDetectionStrategy 设置为onPush,在这种情况下不会发生异常但滚动会滞后。

有没有办法更好地构造这段代码,以便更新 Dom 然后调用 Scroll 函数?我尝试过setTimeout(),但这会使延迟更糟,尝试使用ChangeDetectorRef 并订阅可观察对象以触发markForCheck()。使用ngAfterViewChecked() 会导致浏览器崩溃。

@Component({
    selector: "chat-display",
    template: `
            <div class="chat-box" #this [inScrollHeight]="this.scrollHeight" [inClientHeight]="this.clientHeight" autoScroll>
                <p *ngFor="#msg of messages | async | messageFilter:username:inSelectedTarget:inTargetFilter:inDirectionFilter" [ngClass]="msg.type">{{msg.message}}</p>
            </div>
       `,
    styles: [`.whisper {
            color: rosybrown;
        }`],
    directives: [NgClass, AutoScroll],
    pipes: [AsyncPipe, MessageFilterPipe],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChatDisplay implements OnInit {

    username: string;
    @Input() inSelectedTarget: string;
    @Input() inTargetFilter: boolean;
    @Input() inDirectionFilter: boolean;

    messages: Observable<ChatType[]>;

    constructor(private socketService_: SocketService, private authService_: AuthService) {
        this.username = this.authService_.username;
    };

    ngOnInit() {
    }

}

这是在开发模式下触发的异常:

例外:表达式 'this.scrollHeight in ChatDisplay@1:40' 在检查后已更改。以前的值:“417”。当前值: [this.scrollHeight in ChatDisplay@1:40] 中的“420” angular2.dev.js (23083,9)

【问题讨论】:

  • “异常”到底是什么会很有趣。
  • 这是在开发模式下发生两次更改检测时,检查之间的表达式“this.scrollHeight”不同。就像 observable 将消息推送到显示器一样,​​这会增加 scrollHeight,所以检查之间是不同的,我认为这就是正在发生的事情。
  • 您能否在您的问题中添加确切的错误消息?
  • 您在哪里订阅scroll 事件?什么叫scroll()
  • 添加了异常消息。当指令检测到 ngOnChanges 生命周期钩子中输入属性的任何更改时,就会发生滚动。

标签: angular angular2-directives angular2-changedetection


【解决方案1】:

我找到了解决这个问题的一种方法,它将聊天显示分成两个独立的组件并使用内容投影。因此存在从父级到子级的更改流程,并且在同一个组件中没有两个功能,其中一个触发另一个更改。我可以在开发模式下使用默认的 changeDetectionStrategy 而不会出现异常。

@Component({
    selector: "chat-display",
    template: `
    <auto-scroll-display>
        <chat-message *ngFor="#chat of chats | async | messageFilter:username:inSelectedTarget:inTargetFilter:inDirectionFilter" [message]="chat.message" [type]="chat.type"></chat-message>
    </auto-scroll-display>
    `,
    directives: [NgClass, AutoScrollComponent, ChatMessageComponent],
    pipes: [AsyncPipe, MessageFilterPipe]
})
export class ChatDisplay implements OnInit { /* unchanged code */ }

自动滚动指令与原始帖子相同,试图找出是否有办法将指令功能组合到组件中。它现在只是充当容器。

@Component({
    selector: "auto-scroll-display",
    template: `
    <div #this class="chat-box" [inScrollHeight]="this.scrollHeight" [inClientHeight]="this.clientHeight" autoScroll>
        <ng-content></ng-content>
    </div>
    `,
    directives: [AutoscrollDirective]
})
export class AutoScrollComponent{ }

这是一个带有工作代码的 github 链接,link

【讨论】:

    【解决方案2】:

    有没有办法更好地构造这段代码,以便更新 DOM,然后调用 Scroll 函数?

    在调用ngAfterViewChecked() 之前应该更新DOM。看看这样的事情是否有效:

    ngOnChanges(changes: {[propName: string]: SimpleChange}) {
        // detect the change here
        if (changes["inScrollHeight"] || changes["inClientHeight"]) {
            this.scrollAfterDomUpdates = true;
        }
    };
    ngAfterViewChecked() {
        // but scroll here, after the DOM was updated
        if(this.scrollAfterDomUpdates) {
           this.scrollAfterDomUpdates = false;
           this.scroll();
        }
    }
    

    如果这不起作用,请尝试将滚动调用包装在 setTimeout 中:

        if(this.scrollAfterDomUpdates) {
           this.scrollAfterDomUpdates = false;
           this.setTimeout( _ => this.scroll());
        }
    

    【讨论】:

    • 嘿,我尝试了类似的方法,但它仍然给出了属性绑定在几轮更改检测之间更改的例外情况。我做了一个简化的项目,试图让类似的东西工作,link。这是一个按照您建议的方式进行内容投影的实现,link
    猜你喜欢
    • 2016-07-07
    • 2017-02-13
    • 2016-09-27
    • 2016-12-31
    • 2012-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-20
    相关资源
    最近更新 更多