代码之家  ›  专栏  ›  技术社区  ›  David Ruyle

ngrx、rxjs和angular 5

  •  2
  • David Ruyle  · 技术社区  · 7 年前

    几周来,我一直试图找出一个可以观测到的简单计时器,但运气不好。上周我最初发布了以下内容: ngrx and angular 5 on stackoverflow 什么都没有得到。我尝试实施了建议,并进一步使用了我的原始解决方案。此时,我有一个计时器,它正在发出并输出倒计时,但只有在单击播放或暂停按钮时才发出。我正在尝试倒计时,以便在按下播放按钮时继续向显示组件发送值。我有控制台记录的计时器,它发出的值,而发挥是推罚款,但显示组件没有。我搞不懂这个。我是Angular 5和ngrx/rxjs的新手。

    我这里有Stackblitz工作表中的项目代码。 I have the project code available in a working form on Stackblitz here

    您可以使用用户:test登录密码:test

    计时器代码为 核心/服务/pomo计时器。ts

    容器组件为 书籍/容器/所选书籍页面。ts

    显示组件为 书籍/组件/书籍详细信息。ts

    此时,它应显示6秒,一旦按下播放按钮,它应发出并显示每秒倒计时,直到 暂停 按下按钮时应暂停,直到 再次单击。正如我提到的,当我安慰时。记录工作的值。只有在组件中显示时,它们才不会显示。

    从UI:使用测试/测试登录。搜索一本书。添加到集合。单击至详细信息页面。有一个播放和暂停按钮。页面上显示的是我从StackOverflow上找到的解决方案中尝试的计时器的三种变体。计时器以6秒开始,并向下计数至零。单击播放,计时器开始。单击暂停,计时器停止,直到再次单击播放。在显示页面上,发射值不倒计时。控制台打开时,它会对发出的值进行倒计时。

    计时器由核心/服务/pomo计时器处理。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();
            });
           }
    

    显示器由 应用程序/书籍/组件/书籍详细信息。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);
     }
    }
    

    通过以下方式处理与计时器服务的通信: 应用程序/书籍/容器/所选书籍页面。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;
     }
    }
    

    pomo计时器。ts通过输出计时器 this.remainingTime 如果您能提供任何帮助,我们将不胜感激。我也尝试了在Stackoverflow上找到的所有与此相关的例子。非常感谢你。

    1 回复  |  直到 7 年前
        1
  •  1
  •   Richard Matsen    7 年前

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

    我想在代码中重构很多东西,但在这里,我提供了使其与现有应用程序结构一起工作所需的最低分类。

    我应用的基本原则是:

    1. 使用异步订阅
      该服务随时间产生值,因此应在组件中作为可观察对象订阅,最好使用 async 管道,以便通过Angular自动清理订阅。

    2. 缓冲内部可见
      使用 Subject 作为计时器$与其消费组件之间的缓冲区。这使得组件始终可以看到有效的可观察对象,甚至在初始化计时器$之前。

    3. 带有ViewChild的访问按钮
      请勿使用 document.getElementById() 因为运行此行时,文档可能尚未准备就绪。使用角度 @ViewChild 而是将元素传递给init上的服务。

    这些是我制作的MOD。为了简洁起见,我删去了未更改的块,希望有足够的细节让您进行更改。

    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()
      }
    }
    

    书籍详细信息。ts-模板mods

    通过服务的 主题 异步
    将模板变量添加到按钮以在中使用 @查看子对象 属性。

    @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>
    
      `,
    

    书籍详细信息。ts-Javascript mods

    通过构造函数注入器将服务引入,以调用initTimer()。 使用 @查看子对象 将按钮发送到服务。

    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);
      }
    
    推荐文章