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.