我使用observable从服务中填充可用的tile组件,该服务从远程json文件获取其内容(使用json-server和localhost)。这部分有效。
tile.service.ts:
@Injectable()
export class TileService {
tiles$ = new Subject<any>();
details$ = new Subject<any>();
messages$ = new Subject<any>();
private tilesUrl = 'http://localhost:3000/tiles';
private httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
constructor(private http: HttpClient) { }
getTiles(): Observable<Tile[]> {
return this.http.get<Tile[]>(this.tilesUrl);
}
tileStream(data) {
this.tiles$.next(data);
}
detailStream(data) {
this.details$.next(data);
}
messageStream(data) {
this.messages$.next(data);
}
createTile(t: Tile) {
return this.http.post<Tile>(this.tilesUrl, t, this.httpOptions).subscribe(
res => {
console.log(res);
},
(err: HttpErrorResponse) => {
console.log(err.error);
console.log(err.name);
console.log(err.message);
console.log(err.status);
}
);
}
}
但是,当我添加新磁贴时,可用的磁贴组件不会动态更新。
可供tiles.component.ts
@Component({
selector: 'app-available-tiles',
templateUrl: './available-tiles.component.html',
styleUrls: ['./available-tiles.component.css']
})
export class AvailableTilesComponent implements OnInit {
title = 'Available Tiles';
tiles: Tile[];
mockNewTile = {
id: uuid(),
title: 'GO',
description: 'Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.',
code: {
snippetA: 'something here.',
}
};
constructor(private tileService: TileService) { }
ngOnInit() { // Populates available-tiles on load. Works.
this.tileService.getTiles().subscribe(x => this.tiles = x);
}
addTile(t: Tile) { // For adding tile to dashboard.
this.tileService.tileStream(t);
this.tileService.messageStream( t.title + ' tile added.');
}
createTile() {
this.tileService.createTile(this.mockNewTile);
}
}
可供tiles.component.html
<aside>
<h3>{{ title }}</h3>
<button *ngFor="let tile of tiles" (click) = "addTile(tile)">{{ tile.title }}</button>
<button (click) = "createTile()">Create Tile</button>
</aside>
触发addTile()的第一次点击事件是填充仪表板。仪表板实际上确实动态更新。
dashboard.component.ts
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
title = 'Dashboard';
// Tiles to show on dashboard
tiles = [];
constructor(private tileService: TileService) { }
ngOnInit() {
this.tileService.tiles$.subscribe(x => this.populateTilesArr(x));
}
populateTilesArr(t: Tile) {
if (this.tiles.indexOf(t) === -1) {
this.tiles.push(t);
}
}
答案 0 :(得分:0)
问题是您在订阅过程之后完全错过/离开了ngZone的步骤/代码,这意味着在检测到例如更改后下面的代码构造
new Vue({
el: '#exercise',
data: {
yourRoll: '10',
passCheck: '10',
},
methods: {
roll20: function(){
this.yourRoll = Math.round(Math.random()*19)+1;
return this.yourRoll;
},
newCheck: function(){
this.passCheck = Math.round(Math.random()*19)+1;
}
}
});
您可以在此链接中详细了解使用ngZones所做的更改:https://blog.thoughtram.io/angular/2017/02/21/using-zones-in-angular-for-better-performance.html
答案 1 :(得分:0)
http.get()
一旦完成就会开火,所以
this.tileService.getTiles().subscribe(x => this.tiles = x)
也只会点燃一次。
也许这样做可以解决眼前的需求,
createTile() {
this.tileService.createTile(this.mockNewTile).subscribe(x => {
this.tileService.getTiles().subscribe(x => this.tiles = x);
});
}
并更改服务,以便您返回POST完成而不是订阅,
createTile(t: Tile) {
return this.http.post<Tile>(this.tilesUrl, t, this.httpOptions)
.map(res => console.log(res))
.catch((err: HttpErrorResponse) => {
...
});
更好的方法
但是,您可能会从其他位置发布新的图块,因此请订阅您的服务Subject
而不是
ngOnInit() {
this.tileService.tiles$.subscribe(x => this.tiles = x);
this.tileService.getTiles()
}
,服务需要
@Injectable()
export class TileService {
tiles$ = new Subject<Tile[]>();
private tilesUrl = 'http://localhost:3000/tiles';
private httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
constructor(private http: HttpClient) { }
getTiles(): void {
this.http.get<Tile[]>(this.tilesUrl)
.subscribe(tiles => this.tileStream(tiles));
}
tileStream(data) {
this.tiles$.next(data);
}
createTile(t: Tile) {
this.http.post<Tile>(this.tilesUrl, t, this.httpOptions)
.catch((err: HttpErrorResponse) => {
...
})
.subscribe(res => this.getTiles())
}
}
答案 2 :(得分:0)
在直接更新变量时,您通常必须使用NgZone才能在模板中更新。如果你想得到想象,可以在Observable序列上试用异步管道,例如使用ReplaySubject,它存储输入的所有值,而不是tile的数组。
<强> 1。使用NgZone运算符
dashboard.component.ts
import { NgZone } from '@angular/core';
...
constructor(private tileService: TileService, private zone: NgZone) { }
...
ngOnInit() {
this.tileService.tiles$.subscribe(x => this.zone.run(() => this.populateTilesArr(x)));
}
<强> 2。使用带有ReplaySubject的异步管道
可供tiles.component.html
<aside>
<h3>{{ title }}</h3>
<button *ngFor="let tile of tiles | async" (click) = "addTile(tile)">{{ tile.title }}</button>
<button (click) = "createTile()">Create Tile</button>
</aside>
dashboard.component.ts
...
tiles = (new ReplaySubject<any>()).distinct(); //Stores all values added, but only emits distinct ones
constructor(private tileService: TileService) { }
ngOnInit() {
this.tileService.tiles$.subscribe(x => this.populateTilesArr(x));
}
populateTilesArr(t: Tile) {
this.tiles.next(t);
}
答案 3 :(得分:0)
感谢所有回复的人。特别感谢Richard Matsen,他抽出时间帮助我解决这个问题。
tile.service.ts:
@Injectable()
export class TileService {
availableTiles$ = new Subject<Tile[]>();
tilesForDashboard$ = new Subject<Tile>();
details$ = new Subject<Tile>();
messages$ = new Subject<any>();
private tilesUrl = 'http://localhost:3000/tiles';
private httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
constructor(private http: HttpClient) { }
getTiles(): void {
this.http.get<Tile[]>(this.tilesUrl)
.subscribe(tiles => this.availableTilesStream(tiles));
}
availableTilesStream(data) {
this.availableTiles$.next(data);
}
dashboardStream(data) {
this.tilesForDashboard$.next(data);
}
detailStream(data) {
this.details$.next(data);
}
messageStream(data) {
this.messages$.next(data);
}
createTile(t: Tile) {
return this.http.post<Tile>(this.tilesUrl, t, this.httpOptions)
.catch((err: HttpErrorResponse) => {
console.log(err.error);
console.log(err.name);
console.log(err.message);
console.log(err.status);
return Observable.empty();
}).subscribe(res => this.getTiles());
}
}
可供tiles.ts:
@Component({
selector: 'app-available-tiles',
templateUrl: './available-tiles.component.html',
styleUrls: ['./available-tiles.component.css']
})
export class AvailableTilesComponent implements OnInit {
title = 'Available Tiles';
tiles: Tile[];
mockNewTile = {
id: uuid(),
title: 'GO',
description: 'Go is an open source programming language that...',
code: {
snippetA: 'something here.',
}
};
constructor(private tileService: TileService) { }
ngOnInit() {
this.tileService.availableTiles$.subscribe(x => this.tiles = x);
this.tileService.getTiles();
}
addTileToDashboard(t: Tile) {
this.tileService.dashboardStream(t);
this.tileService.messageStream( t.title + ' tile added.');
}
createTile() {
this.tileService.createTile(this.mockNewTile);
}
}
可供tiles.component.html:
<aside>
<h3>{{ title }}</h3>
<button *ngFor="let tile of tiles" (click) = "addTileToDashboard(tile)">{{ tile.title }}</button>
<button (click) = "createTile()">Create Tile</button>
</aside>
dashboard.component.ts
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
title = 'Dashboard';
// Tiles to show on dashboard
tiles = [];
// A property to hold the selected tile in the dashboard.
selectedTile: Tile;
editing = false;
constructor(private tileService: TileService) { }
ngOnInit() {
// We only want tiles that have been clicked on in available-tiles.component to show in the dashboarrd.
// The available-tiles.component adds to the tilesForDashboard$ subject when a tile is clicked.
// Populate the tiles array based on the data stream from the tilesForDashboard$ subject.
this.tileService.tilesForDashboard$.subscribe(x => this.uniqueTiles(x));
}
// Only add a single occurence of each tile to the tiles array
uniqueTiles(t: Tile) {
if (this.tiles.indexOf(t) === -1) {
this.tiles.push(t);
}
}
remove(t) {
// remove tile from array
const index = this.tiles.indexOf(t);
this.tiles.splice(index, 1);
// If the value passed into remove(tile) is equal to the selectedTile, then
// pass null to the details subject in the detailStream method of the service.
// Otherwise, if no check performed (simply passing null) then any
// detail showing gets removed when any tile is removed from the dashboard.
if (this.selectedTile === t) {
this.tileService.detailStream(null);
}
if (this.tiles.length < 1) {
this.editing = false;
}
this.tileService.messageStream( t.title + ' tile removed.');
}
displayDetails(t: Tile) {
this.tileService.detailStream(t);
this.selectedTile = t;
}
}
dashboard.component.html:
<ul>
<li *ngFor = "let tile of tiles" (click)="displayDetails(tile)">
<button *ngIf="editing" class="remove" (click)="remove(tile)">X</button>
<span class="title">{{tile.title}}</span>
<span class="desc">{{tile.description}}</span>
</li>
</ul>