简体   繁体   中英

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 .

I would like to manipulate some of the properties on cards using events and two-way data binding. 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

tag that I've added to the view for debugging. However, this doesn't appear to actually update the cards object in the component.

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.

 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. 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. Calling next on a Subject is "putting data into the event stream". 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. Now every time next is called on the language Subject, the card Observable will emit the updated cards.

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 = ""; } } 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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