DOM 使用 ngFor 更改数组后未更新
DOM not updated after array changes with ngFor
想要的行为:当按下停止按钮时,会生成一个音频链接,添加到一个数组中,ngFor 添加到列表中并更新 DOM。
实际行为:单击停止按钮后,音频片段并未直接添加到列表中。只有在调用新事件(例如再次单击停止按钮)时,ngFor 才会将其添加到 DOM(并且工作正常)。
为什么 ngFor 没有检测到 audioUrls 数组中的变化?
应用-component.html:
<button (click)="startRecording()">Start Recording</button>
<button (click)="stopRecording()">Stop Recording</button>
<ul>
<li *ngFor="let audioUrl of audioUrls">
<audio controls [src]="audioUrl"></audio>
</li>
</ul>
应用-component.ts:
import { RecordService } from './record.service';
import { Component, HostListener } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { SafeUrl, SafeResourceUrl } from '@angular/platform-browser';
import { OnInit } from '@angular/core/src/metadata/lifecycle_hooks';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
title = 'app';
audioUrls: SafeResourceUrl[];
constructor(private recordService:RecordService){
}
ngOnInit(){
this.recordService.audioUrlsChanged.subscribe(list => this.audioUrls = list);
}
startRecording(){
if(this.recordService.audioRecorder){
this.recordService.audioRecorder.start()
console.log(this.recordService.audioRecorder.state);
}
}
stopRecording(){
if(this.recordService.audioRecorder){
this.recordService.audioRecorder.stop()
console.log(this.recordService.audioRecorder.state);
}
}
}
服务:
import { WindowRefService } from './window-ref.service';
import { Injectable, HostListener } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { SafeUrl } from '@angular/platform-browser';
declare var MediaRecorder: any;
@Injectable()
export class RecordService {
audioRecorder:any;
chunks:any[] = [];
audioUrls:SafeResourceUrl[] = [];
audioUrlsChanged= new Subject<SafeResourceUrl[]>();
constructor(private windowRef:WindowRefService, private domSanitizer: DomSanitizer) {
this.audioRecorder = this.windowRef.nativeWindow.navigator.mediaDevices.getUserMedia (
// constraints - only audio needed for this app
{
audio: true
}).then(
stream => {
this.audioRecorder = new MediaRecorder(stream);
this.audioRecorder.ondataavailable = (e) => {
this.chunks.push(e.data);
}
this.audioRecorder.onstop = (e) => {
let blob = new Blob(this.chunks, { 'type' : 'audio/ogg; codecs=opus' });
this.chunks = []; //reset buffer
let url:SafeResourceUrl = this.domSanitizer.bypassSecurityTrustResourceUrl(this.windowRef.nativeWindow.URL.createObjectURL(blob));
this.audioUrls.push(url);
this.audioUrlsChanged.next(this.audioUrls.slice());
}
});
}
(Window refservice 只是 window 对象的包装器)
audioRecorder onstop 方法发生在 Angular 上下文之外,因为它是由 Angular.
外部的外部库生成的事件
因此,Angular 不会更新视图。您必须 运行 在 ngZone.run.
中
- 将变量
zone
添加到服务的构造函数中,键入 NgZone
。
- 在停止事件中,将代码包裹在
this.zone.run()
. 中
或者,您可以调用 ChangeDetectorRef.detectChanges()。
参见 this blog post 以获得更好的解释:
想要的行为:当按下停止按钮时,会生成一个音频链接,添加到一个数组中,ngFor 添加到列表中并更新 DOM。
实际行为:单击停止按钮后,音频片段并未直接添加到列表中。只有在调用新事件(例如再次单击停止按钮)时,ngFor 才会将其添加到 DOM(并且工作正常)。
为什么 ngFor 没有检测到 audioUrls 数组中的变化?
应用-component.html:
<button (click)="startRecording()">Start Recording</button>
<button (click)="stopRecording()">Stop Recording</button>
<ul>
<li *ngFor="let audioUrl of audioUrls">
<audio controls [src]="audioUrl"></audio>
</li>
</ul>
应用-component.ts:
import { RecordService } from './record.service';
import { Component, HostListener } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { SafeUrl, SafeResourceUrl } from '@angular/platform-browser';
import { OnInit } from '@angular/core/src/metadata/lifecycle_hooks';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
title = 'app';
audioUrls: SafeResourceUrl[];
constructor(private recordService:RecordService){
}
ngOnInit(){
this.recordService.audioUrlsChanged.subscribe(list => this.audioUrls = list);
}
startRecording(){
if(this.recordService.audioRecorder){
this.recordService.audioRecorder.start()
console.log(this.recordService.audioRecorder.state);
}
}
stopRecording(){
if(this.recordService.audioRecorder){
this.recordService.audioRecorder.stop()
console.log(this.recordService.audioRecorder.state);
}
}
}
服务:
import { WindowRefService } from './window-ref.service';
import { Injectable, HostListener } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { SafeUrl } from '@angular/platform-browser';
declare var MediaRecorder: any;
@Injectable()
export class RecordService {
audioRecorder:any;
chunks:any[] = [];
audioUrls:SafeResourceUrl[] = [];
audioUrlsChanged= new Subject<SafeResourceUrl[]>();
constructor(private windowRef:WindowRefService, private domSanitizer: DomSanitizer) {
this.audioRecorder = this.windowRef.nativeWindow.navigator.mediaDevices.getUserMedia (
// constraints - only audio needed for this app
{
audio: true
}).then(
stream => {
this.audioRecorder = new MediaRecorder(stream);
this.audioRecorder.ondataavailable = (e) => {
this.chunks.push(e.data);
}
this.audioRecorder.onstop = (e) => {
let blob = new Blob(this.chunks, { 'type' : 'audio/ogg; codecs=opus' });
this.chunks = []; //reset buffer
let url:SafeResourceUrl = this.domSanitizer.bypassSecurityTrustResourceUrl(this.windowRef.nativeWindow.URL.createObjectURL(blob));
this.audioUrls.push(url);
this.audioUrlsChanged.next(this.audioUrls.slice());
}
});
}
(Window refservice 只是 window 对象的包装器)
audioRecorder onstop 方法发生在 Angular 上下文之外,因为它是由 Angular.
外部的外部库生成的事件因此,Angular 不会更新视图。您必须 运行 在 ngZone.run.
中- 将变量
zone
添加到服务的构造函数中,键入NgZone
。 - 在停止事件中,将代码包裹在
this.zone.run()
. 中
或者,您可以调用 ChangeDetectorRef.detectChanges()。
参见 this blog post 以获得更好的解释: