简体   繁体   English

在Angular中使用rxjs进行双向数据绑定

[英]Two-Way Data Binding using rxjs in Angular

I have an Angular template and component that I've adapted from Angular Material's dashboard schematic . 我有一个Angular模板和组件,这些模板和组件已从Angular Material的仪表板示意图中进行了修改

I would like to manipulate some of the properties on cards using events and two-way data binding. 我想使用事件和双向数据绑定来操纵cards上的某些属性。 At first glance, two-way data binding seems to work, as I can manipulate the editorContent property of a given index in the card using directives, and those changes are reflected in a simple 乍一看,双向数据绑定似乎可行,因为我可以使用指令在卡中操纵给定索引的editorContent属性,而这些更改将反映在一个简单的表中。

tag that I've added to the view for debugging. 我添加到视图中进行调试的标记。 However, this doesn't appear to actually update the cards object in the component. 但是,这似乎并未真正更新组件中的cards对象。

I've read that to manipulate observables, you must subscribe to them first. 我读过要操纵可观察物,必须先订阅它们。 The clearEditor method successfully gets the data from cards , but the contentEditor is not updated from the view, and setting it to null in the method does not appear to change the value of cards either, if I were to set it to a string that is not empty or null in the constructor. clearEditor方法成功地从cards获取数据,但是contentEditor不会从视图中更新,并且如果我将其设置为一个字符串,该方法中将其设置为null似乎也不会更改cards的值。在构造函数中不能为空或null。

 import { Component } from "@angular/core"; import { map } from "rxjs/operators"; import { Breakpoints, BreakpointObserver } from "@angular/cdk/layout"; import { Observable } from 'rxjs'; @Component({ selector: "app-repl", templateUrl: "./repl.component.html", styleUrls: ["./repl.component.scss"] }) export class REPLComponent { cards: Observable < any > ; constructor(private breakpointObserver: BreakpointObserver) { this.cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe( map(({ matches }) => { if (matches) { return [{ title: "HTML", content: "code", language: "html", cols: 1, rows: 1, editorContent: "" }, { title: "CSS", content: "code", language: "css", cols: 1, rows: 1, editorContent: "" }, { title: "PDF", content: "pdf", cols: 1, rows: 1 } ]; } return [{ title: "HTML", content: "code", language: "html", cols: 1, rows: 1, editorContent: "" }, { title: "PDF", content: "pdf", cols: 1, rows: 2 }, { title: "CSS", content: "code", language: "css", cols: 1, rows: 1, editorContent: "" } ]; }) ); } clearEditor(language: string) { this.cards.subscribe(cards => { cards.forEach(function(card) { if (card.language === language) { card.editorContent = null; } }); }); } } 
 <div class="grid-container"> <h1 class="mat-h1">REPL</h1> <mat-grid-list cols="2" rowHeight="400px"> <mat-grid-tile *ngFor="let card of cards | async" [colspan]="card.cols" [rowspan]="card.rows"> <mat-card class="dashboard-card"> <mat-card-header> <mat-card-title> {{card.title}} <button *ngIf="card.content==='code'" mat-icon-button class="more-button" [matMenuTriggerFor]="menu" aria-label="Toggle menu"> <mat-icon>more_vert</mat-icon> </button> <mat-menu #menu="matMenu" xPosition="before"> <button mat-menu-item (click)="clearEditor(card.language)">Clear</button> <button mat-menu-item>Download</button> </mat-menu> </mat-card-title> </mat-card-header> <mat-card-content *ngIf="card.content==='code'"> <td-code-editor style="height: 300px" theme="vs-dark" flex [language]="card.language" [(ngModel)]="card.editorContent"></td-code-editor> <p>{{card.editorContent}}</p> </mat-card-content> <mat-card-content *ngIf="card.content==='pdf'"> <pdf-viewer src="\\assets\\document.pdf" style="display: block; max-width: 490px; max-height: 100%;" [render-text]="false" [original-size]="false" [autoresize]="true" [show-all]="false" [page]="1"> </pdf-viewer> </mat-card-content> </mat-card> </mat-grid-tile> </mat-grid-list> <button mat-button> <mat-icon>cloud_upload</mat-icon> Generate PDF </button> <button mat-button> <mat-icon>save_alt</mat-icon> Download PDF </button> </div> 

Subscribing to an Observable does not allow you to manipulate the data in the Observable. 订阅可观察对象不允许您操纵可观察对象中的数据。 Think of Observables as an event stream. 将Observables视为事件流。 By subscribing to them, you can only read what's coming out of that stream. 订阅它们,您只能读取该流中的内容。 Depending on the scenario, there are different ways to deal with putting data into the stream. 根据场景的不同,有不同的方法可以将数据放入流中。 Here is a snippet that hopefully helps you deal with your scenario: 这是一个希望可以帮助您解决方案的代码段:

import { Subject } from 'rxjs';
import { map, switchMap, startWith } from 'rxjs/operators'

private language = new Subject<string>();

cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe(
  map(breakpoint => {
    /* content of the map operator that you showed in your question */
    return cards;
  }),
  switchMap(cards => {
    return this.language.pipe(
      map(language => {
        const card = cards.find(c => c.language === language);
        card.editorContent = null;
        return cards;
      }),
      // when the outter breakpoint Observable emits, we just 
      // want to emit the cards as is.
      startWith(cards)
    })
  )
);

clearEditor(language: string) {
  this.language.next(language);
}

In this case using a Subject allows one to call its next method when the clearEditor method is called. 在这种情况下,使用主体可以在调用clearEditor方法时调用其next方法。 Calling next on a Subject is "putting data into the event stream". next调用主题是“将数据放入事件流”。 Note that a Subject extends an Observable, therefore a Subject is an Observable. 请注意,主题扩展了可观察对象,因此主题是可观察对象。

switchMap is an operator that is used here to bring the language Subject and the breakpoint Observable into a single cards Observable. switchMap是一个运算符,在此用于将语言“主题”和“可观察到的断点”引入单个可观察到的卡片中。 Now every time next is called on the language Subject, the card Observable will emit the updated cards. 现在每次在主题语言上调用next ,Observable卡将发出更新的卡。

The following code is the approach we ended up taking: 以下代码是我们最终采用的方法:

 export class REPLComponent implements OnInit { columns: number = 2; cards: Array < Card > = [{ title: "HTML", content: "code", language: "html", cols: 1, rows: 1, editorContent: '<!DOCTYPE html><html><head><title>My First Newsletter</title><meta charset="UTF-8"></head><body></body></html>' }, { title: "PDF", content: "pdf", language: null, cols: 1, rows: 2, editorContent: null }, { title: "CSS", content: "code", language: "css", cols: 1, rows: 1, editorContent: "body {}" } ]; layoutChange: Observable < BreakpointState > ; constructor( private breakpointObserver: BreakpointObserver, private http: HttpClient ) {} ngOnInit() { this.layoutChange = this.breakpointObserver.observe(Breakpoints.Handset); this.layoutChange.subscribe((result: BreakpointState) => { this.columns = result.matches ? 1 : 2; this.cards.forEach((card: Card) => { card.cols = 1; card.rows = !result.matches && card.content == "pdf" ? this.columns : 1; }); }); } clearEditor(card: Card) { card.editorContent = ""; } } 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM