首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ngrx,rxjs和角5

ngrx,rxjs和角5
EN

Stack Overflow用户
提问于 2018-04-20 16:09:43
回答 1查看 532关注 0票数 2

我已经尝试找出一个简单的计时器,观察了几个星期,现在没有运气。我最初是在上周发布这篇文章的:堆栈溢出的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处理。

代码语言:javascript
复制
   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处理

代码语言:javascript
复制
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处理。

代码语言:javascript
复制
@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。非常感谢。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-04-22 00:52:52

我设法得到了一个正常工作的计时器服务。

我会在代码中重构很多东西,但是这里我已经给出了使它与您现有的应用程序结构一起工作所需的最小分类。

我所适用的基本原则是:

  1. 订阅异步 随着时间的推移,服务会产生值,因此应该在组件中作为可观察的对象进行订阅,最好是使用async管道,这样订阅就可以通过角度自动清除。
  2. 缓冲内部可观测 使用Subject作为计时器$及其消费组件之间的缓冲区。这使得组件始终看到一个有效的可观察的,甚至在计时器$被初始化之前。
  3. 带有 ViewChild的访问按钮 不要使用document.getElementById()访问按钮,因为在运行该行时文档可能还没有准备好。改用@ViewChild,并将元素传递给init上的服务。

这些是我做的mods。为了简洁起见,我已经删除了不变的块,希望有足够的细节供您进行更改。

PomoTimerService mods

代码语言:javascript
复制
// 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

通过服务的Subjectasync管道使用计时器值。

向按钮添加模板变量,以便在@ViewChild属性中使用。

代码语言:javascript
复制
@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向服务发送按钮。

代码语言:javascript
复制
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);
  }
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/49945761

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档