Angular2:更改自动滚动指令的检测时间
Angular2: Change detection timing for an auto-scroll directive
我一直在为聊天显示开发一个简单的自动滚动指令:
@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
设置为默认值时,该指令将起作用,但是当在 "dev mode" 中时,我得到一个异常。我可以将 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() {
}
}
这是在开发模式下触发的异常:
EXCEPTION: Expression 'this.scrollHeight in ChatDisplay@1:40' has changed after it was checked. Previous value: '417'. Current value: '420' in [this.scrollHeight in ChatDisplay@1:40]
angular2.dev.js (23083,9)
我找到了解决这个问题的方法,它涉及将聊天显示分成两个独立的部分并使用内容投影。因此,存在从父级到子级的变化流,并且不会在同一个组件中有两个功能,一个触发另一个的变化。我可以在开发模式下使用默认的 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 */ }
自动滚动指令与原始 post 相同,试图弄清楚是否有办法将指令功能组合到组件中。它现在只是充当一个容器。
@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 的工作代码,link。
Is there a way to better structure this code so that DOM will be updated then the Scroll function can be called?
DOM 应该在调用 ngAfterViewChecked()
之前更新。看看这样的东西是否有效:
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());
}
我一直在为聊天显示开发一个简单的自动滚动指令:
@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
设置为默认值时,该指令将起作用,但是当在 "dev mode" 中时,我得到一个异常。我可以将 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() {
}
}
这是在开发模式下触发的异常:
EXCEPTION: Expression 'this.scrollHeight in ChatDisplay@1:40' has changed after it was checked. Previous value: '417'. Current value: '420' in [this.scrollHeight in ChatDisplay@1:40] angular2.dev.js (23083,9)
我找到了解决这个问题的方法,它涉及将聊天显示分成两个独立的部分并使用内容投影。因此,存在从父级到子级的变化流,并且不会在同一个组件中有两个功能,一个触发另一个的变化。我可以在开发模式下使用默认的 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 */ }
自动滚动指令与原始 post 相同,试图弄清楚是否有办法将指令功能组合到组件中。它现在只是充当一个容器。
@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 的工作代码,link。
Is there a way to better structure this code so that DOM will be updated then the Scroll function can be called?
DOM 应该在调用 ngAfterViewChecked()
之前更新。看看这样的东西是否有效:
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());
}