我已经尝试找出一个简单的计时器,观察了几个星期,现在没有运气。我最初是在上周发布这篇文章的:堆栈溢出的ngrx和角5,但什么也没得到。我试着实现了所建议的内容,并在最初的解决方案中得到了进一步的进展。在这一点上,我有一个计时器发出和输出倒计时,但只有当一个播放或暂停按钮被点击。当按下play按钮时,我试图让倒计时继续向显示组件发出值。我有控制台记录计时器,它发出值,而play被推送的很好,但是显示组件没有。我搞不懂这个。我对角5和ngrx/rxjs都很陌生。
我在这里有一个在Stackblitz上工作形式的项目代码。我在这里有一个关于Stackblitz的工作形式的项目代码。。
您可以使用user: test密码: test登录。
计时器代码在core/services/pomo-timer.ts中
容器组件是books/containers/selected-book-page.ts。
显示组件是books/components/book-detail.ts。
此时,它应该显示6秒,一旦按下播放按钮,它就应该发出并显示每秒钟的倒计时,直到暂停按钮被按下,此时它应该暂停,直到再次单击 play 。正如我提到的,当我console.log时,工作的价值是很好的。只有当它们显示在组件中时,它们才不会显示。
从UI:使用test/test登录。找一本书。添加到集合。单击到详细信息页。有一个播放和暂停按钮。显示在页面上的是我从StackOverflow上找到的解决方案中尝试过的三种计时器。计时器从6秒开始,计数到零。播放被点击,计时器开始。暂停被点击,计时器停止,直到播放再次点击。在显示页上,发出的值不计数。当控制台打开时,它会发出倒计时值。
定时器由core/services/poo-timer.ts处理。
startTimer() {
const resumeButton = document.getElementById('resume');
const pauseButton = document.getElementById('pause');
const resetButton = document.getElementById('reset');
const interval$: any = interval(1000).pipe(mapTo(-1));
const pause$ = fromEvent(pauseButton, 'click').pipe(mapTo(false));
const resume$ = fromEvent(resumeButton, 'click').pipe(mapTo(true));
const timer$ = merge(pause$, resume$).pipe(
startWith(interval$),
switchMap(val => (val ? interval$ : empty())),
scan((acc, curr) => (curr ? curr + acc : acc),
this.countdownSeconds$),
takeWhile(v => v >= 0),
)
.subscribe(
val => { this.timeRemaining = val;
console.log(this.timeRemaining);
},
val => { this.checkTime.emit(val); },
() => {
this.resetTimer();
});
}显示由app/books/components/book-detail.ts处理
export class BookDetailComponent {
@Input() simpleObservable: number;
@Input() seconds: string;
@Input() timeRemaining: number;
@Input() timerSubscription: Subscription;
@Input() book: Book;
@Input() inCollection: boolean;
@Output() add = new EventEmitter<Book>();
@Output() remove = new EventEmitter<Book>();
@Output() resumeClicked = new EventEmitter();
@Output() checkTime: EventEmitter<number> = new EventEmitter();
get id() {
return this.book.id;
}
get title() {
return this.book.volumeInfo.title;
}
get subtitle() {
return this.book.volumeInfo.subtitle;
}
get description() {
return this.book.volumeInfo.description;
}
get thumbnail() {
return (
this.book.volumeInfo.imageLinks &&
this.book.volumeInfo.imageLinks.smallThumbnail
);
}
get time() {
return this.timeRemaining;
}
resumeCommand(action: any) {
this.resumeClicked.emit(action);
}
}与计时器服务的通信由:app/books/containers/selected-book-page.ts处理。
@Component({
selector: 'bc-selected-book-page',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<bc-book-detail
[book]="book$ | async"
[inCollection]="isSelectedBookInCollection$ | async"
[timeRemaining]="this.pomoTimerService.timeRemaining"
[simpleObservable]="this.simpleObservable | async"
[seconds]="this.pomoTimerService.timeRemaining"
(checkTime)="checkCurrentTime($event)"
(add)="addToCollection($event)"
(remove)="removeFromCollection($event)"
(resumeClicked)="resumeClicked($event)"
(resumeClicked)="resumeClicked($event)"
(reset)="resumeClicked($event)">
</bc-book-detail>
`,
})
export class SelectedBookPageComponent implements OnInit {
book$: Observable<Book>;
isSelectedBookInCollection$: Observable<boolean>;
timeRemaining: any;
private timerSubscription: Subscription;
timerSource = new Subject<any>();
simpleObservable;
countDown: any;
counter: number;
seconds: string;
private subscription: Subscription;
checkTime;
constructor(public pomoTimerService: PomoTimerService, private store:
Store<fromBooks.State>) {
this.book$ = store.pipe(select(fromBooks.getSelectedBook));
this.isSelectedBookInCollection$ = store.pipe(
select(fromBooks.isSelectedBookInCollection)
);
}
ngOnInit(): void {
this.pomoTimerService.pomoCount$ = 0;
this.pomoTimerService.pomosCompleted$ = 0;
this.pomoTimerService.pomoTitle$ = 'Time to Work';
this.pomoTimerService.initTimer();
}
addToCollection(book: Book) {
this.store.dispatch(new collection.AddBook(book));
}
removeFromCollection(book: Book) {
this.store.dispatch(new collection.RemoveBook(book));
}
resumeClicked(event) {
console.log(event);
console.log(event.target);
console.log(event.srcElement);
console.log(event.type);
console.log(event.currentTarget.attributes.name.nodeValue);
console.log(event.currentTarget.attributes.id.nodeValue);
if (event.currentTarget.attributes.id.nodeValue === 'resume' &&
!this.pomoTimerService.timerStarted) {
this.pomoTimerService.timerStarted = true;
this.pomoTimerService.startTimer();
}
}
checkCurrentTime(event) {
this.counter = event;
}
}通过this.remainingTime输出计时器,您可以提供的任何帮助都将不胜感激。我已经尝试了所有的例子,甚至是远程相关的例子,我在这里也找到了Stackoverflow。非常感谢。
发布于 2018-04-22 00:52:52
我设法得到了一个正常工作的计时器服务。
我会在代码中重构很多东西,但是这里我已经给出了使它与您现有的应用程序结构一起工作所需的最小分类。
我所适用的基本原则是:
async管道,这样订阅就可以通过角度自动清除。Subject作为计时器$及其消费组件之间的缓冲区。这使得组件始终看到一个有效的可观察的,甚至在计时器$被初始化之前。document.getElementById()访问按钮,因为在运行该行时文档可能还没有准备好。改用@ViewChild,并将元素传递给init上的服务。这些是我做的mods。为了简洁起见,我已经删除了不变的块,希望有足够的细节供您进行更改。
PomoTimerService mods
// imports as before, plus
import { tap } from 'rxjs/operators';
@Injectable()
export class PomoTimerService {
timerSource$ = new Subject<any>(); // added '$' to this property, for clarity
// other properties same as before
private buttons; // to receive button references passed in
// Create a new version of init, which is called once and receives the buttons
initTimer (buttons) {
this.buttons = buttons;
this.initTimerParameters();
}
// Renamed the original initTimer() method to initTimerParamters,
// as it is called on true init and also in reset
initTimerParameters() {
// same statements as original initTimer() method
}
startTimer() {
this.timerStarted = true; // moved from component
const interval$: any = interval(1000).pipe(mapTo(-1));
const pause$ = fromEvent(this.buttons.pauseButton.nativeElement, 'click').pipe(mapTo(false));
const resume$ = fromEvent(this.buttons.resumeButton.nativeElement, 'click').pipe(mapTo(true));
const timer$ = merge(pause$, resume$).pipe(
startWith(true), // previously startWith(interval$), but that looks suspect
switchMap(val => (val ? interval$ : empty())),
scan((acc, curr) => (curr ? curr + acc : acc), this.countdownSeconds$),
takeWhile(v => v >= 0),
tap(val => console.log('timeRemaining', val)), // use tap (not subscribe) to monitor on console
tap(val => { // resetting this.timerStarted is a 'side-effect', best done with tap operator rather than finally callback of subscribe
if (val === 0) {
this.timerStarted = false;
}
}),
);
timer$.subscribe(val => this.timerSource$.next(val)) // send values to Subject
}
resetTimer() {
this.initTimerParameters(); // was calling this.initTimer()
}
}图书-细节.mods模板mods
通过服务的Subject和async管道使用计时器值。
向按钮添加模板变量,以便在@ViewChild属性中使用。
@Component({
selector: 'bc-book-detail',
template: `
<mat-card *ngIf="book">
...
<mat-card-subtitle>Original {{ timerService.timerSource$ | async }}
</mat-card-subtitle>
...
<button #resume id="resume" ...</button>
<button #pause id="pause" ...</button>
<button #reset id="reset" ...</button>
</mat-card-actions>
</mat-card>
`,图书-细节.mods javacript mods
将服务引入via构造函数注入器,以调用initTimer()。使用@ViewChild向服务发送按钮。
export class BookDetailComponent implements AfterViewInit {
// @Inputs and @Outputs as previously defined
constructor(public timerService: PomoTimerService) {}
@ViewChild('resume', {read: ElementRef}) resumeButton;
@ViewChild('pause', {read: ElementRef}) pauseButton;
@ViewChild('reset', {read: ElementRef}) resetButton;
ngAfterViewInit() {
const buttons = {
resumeButton: this.resumeButton,
pauseButton: this.pauseButton,
resetButton: this.resetButton
};
this.timerService.initTimer(buttons);
}https://stackoverflow.com/questions/49945761
复制相似问题