问题
I have a list of songs setup with Subject and Observable (shown with | async
in view), and now I want to delete a song off the list, do some filter()
and call next()
on the Subject.
How and where do I filter? Right now I am doing getValue()
on Subject and passing that to next()
on, well, Subject. This just seems wrong and circularish.
I also tried subscribing to the Subject and getting the data that way, filtering it and calling next()
inside subscribe()
, but I got a RangeError.
I could filter the Observable by storing all deleted id's. The Subject's list then becomes out of sync by having deleted songs on there and also every observer would have to have the deleted-id's-list which seems ludicrous. I'm rapidly growing old and mental. Please help me internet :(
export class ArtistComponent implements OnInit {
private repertoire$;
private repertoireSubject;
constructor(
private route: ActivatedRoute,
private service: ArtistService
) {
this.getRepertoire().subscribe(
songs => this.repertoireSubject.next(songs)
);
}
getRepertoire() {
return this.route.paramMap
.switchMap((params: ParamMap) =>
this.service.fetchRepertoire(params.get('id')));
}
//THIS IS WHERE I'M HAVING TROUBLE
delete(id): void {
this.repertoireSubject.next(
this.repertoireSubject.getValue().filter(song => (song.id !== id))
);
// TODO remove song from repertoire API
}
ngOnInit() {
this.repertoireSubject = new BehaviorSubject<any>(null);
this.repertoire$ = this.repertoireSubject.asObservable();
}
}
回答1:
i recommand you to create new attribute on your component, where you will last store state. (here understands array of songs).
Is always better conceptualize your code by, internal property who represent your state (or store) and another attribute who have the role to sync rest of your application (by observable / event).
Another tips is to strong type your code by model. Will be easier to debug and maintain.
Then you just have to update it according to your logic and next on your Subject
export interface SongModel {
id: number;
title: string;
artiste: string;
}
export class ArtistComponent implements OnInit {
private repertoire$ : Observable<SongModel[]>;
private repertoireSubject: BehaviorSubject<SongModel[]>;
//Array of song, should be same type than repertoireSubject.
private songs: SongModel[];
constructor(
private route: ActivatedRoute,
private service: ArtistService
) {
//We push all actual references.
this.getRepertoire().subscribe(
songs => {
this.songs = songs;
this.repertoireSubject.next(this.songs);
}
);
}
ngOnInit() {
//Because is suject of array, you should init by empty array.
this.repertoireSubject = new BehaviorSubject<SongModel[]>([]);
this.repertoire$ = this.repertoireSubject.asObservable();
}
getRepertoire() {
return this.route.paramMap
.switchMap((params: ParamMap) =>
this.service.fetchRepertoire(params.get('id')));
}
//THIS IS WHERE I'M HAVING TROUBLE
delete(id: number): void {
// Update your array referencial.
this.songs = this.songs.filter(songs => songs.id !== id);
// Notify rest of your application.
this.repertoireSubject.next(this.songs);
}
}
回答2:
If you stop relying on the async pipe and use a variable to handle your songs, it gets way easier :
import { filter } from 'rxjs/operators';
export class ArtistComponent implements OnInit {
private songs: any;
constructor(
private route: ActivatedRoute,
private service: ArtistService
) {
this.getRepertoire().subscribe(songs => this.songs = songs);
}
getRepertoire() {
return this.route.paramMap
.switchMap((params: ParamMap) =>
this.service.fetchRepertoire(params.get('id')));
}
delete(id): void {
this.songs = this.songs.filter(song => song.id !== id);
}
}
This way, you can simply filter like it is a simple array of object.
回答3:
If you want to keep everything in streams then you could take a page from the Redux playbook and do something like this:
const actions = new Rx.Subject();
const ActionType = {
SET: '[Song] SET',
DELETE: '[Song] DELETE'
};
const songs = [
{ id: 1, name: 'First' },
{ id: 2, name: 'Second' },
{ id: 3, name: 'Third' },
{ id: 4, name: 'Fourth' },
{ id: 5, name: 'Fifth' }
];
actions
.do(x => { console.log(x.type, x.payload); })
.scan((state, action) => {
switch(action.type) {
case ActionType.SET:
return action.payload;
case ActionType.DELETE:
return state.filter(x => x.id !== action.payload);
}
return state;
}, [])
.subscribe(x => { console.log('State:', x); });
window.setTimeout(() => {
actions.next({ type: ActionType.SET, payload: songs });
}, 1000);
window.setTimeout(() => {
actions.next({ type: ActionType.DELETE, payload: 2 });
}, 2000);
window.setTimeout(() => {
actions.next({ type: ActionType.DELETE, payload: 5 });
}, 3000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.7/Rx.min.js"></script>
Or something like this:
const deletes = new Rx.Subject();
const songs = Rx.Observable.of([
{ id: 1, name: 'First' },
{ id: 2, name: 'Second' },
{ id: 3, name: 'Third' },
{ id: 4, name: 'Fourth' },
{ id: 5, name: 'Fifth' }
]);
window.setTimeout(() => {
deletes.next(2);
}, 1000);
window.setTimeout(() => {
deletes.next(5);
}, 2000);
songs.switchMap(state => {
return deletes.scan((state, id) => {
console.log('Delete: ', id);
return state.filter(x => x.id !== id);
}, state)
.startWith(state);
}).subscribe(x => { console.log('State:', x); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.7/Rx.min.js"></script>
来源:https://stackoverflow.com/questions/49385487/subject-and-observable-how-to-delete-item-filter-list-and-next