For some reason combineLatest
doesn't catch the last emitted value from Subject
.
service.ts
const BLOCKS = [
{
name: 'block 1',
id: 1,
},
{
name: 'block 2',
id: 2,
},
];
@Injectable({
providedIn: 'root',
})
export class Service {
blocks = BLOCKS;
blocksSubject = new Subject<any>();
blocks$ = this.blocksSubject.asObservable().pipe(
map((blocks) => blocks.map((block) => this.validation(block))),
map((blocks) => blocks.map((block) => this.asyncValidation(block))),
switchMap((blocks) => combineLatest(blocks))
);
addBlocks() {
this.blocksSubject.next(BLOCKS);
}
removeBlock(id: number) {
this.blocks = this.blocks.filter((block) => block.id !== id);
this.blocksSubject.next(this.blocks);
}
asyncValidation(block) {
return of({ ...block, asyncValid: true });
}
validation(block) {
return { ...block, valid: true };
}
}
Parent component
export class AppComponent {
blocks$: Observable<any>;
constructor(private readonly service: Service) {
this.blocks$ = this.service.blocks$;
}
add() {
this.service.addBlocks();
}
}
Child component
@Component({
selector: 'hello',
template: `<ng-container *ngFor="let block of blocks">
<div >
<div> Block name: {{ block.name }} </div>
<div> Block id: {{ block.id }} </div>
<div *ngIf="block.valid"> valid: {{ block.valid }} </div>
<div *ngIf="block.asyncValid"> asyncValid: {{ block.asyncValid }} </div>
<button (click)="remove(block.id)">Remove</button>
</div>
</ng-container>`,
styles: [`h1 { font-family: Lato; } .container { line-height: 18px; width: 100px; display: flex; flex-direction: column; padding: 10px;}`]
})
export class HelloComponent {
@Input() blocks: any;
constructor(private readonly service: Service) {}
remove(id) {
this.service.removeBlock(id);
}
}
Here is the stackBlitz example: https://stackblitz.com/edit/angular-ivy-db7viw?file=src/app/hello.component.ts
When I remove asyncValidation - everything works fine. With asyncValidation blocks$ observable receives new values, except the last one, so last is item never removed.
CodePudding user response:
Good question. :-)
When you remove the last block combineLatest does not emit because the array of (validated) block observables is empty.
Solution: fallback to an empty list.
blocks$ = this.blocksSubject.asObservable().pipe(
switchMap((blocks) => {
if (blocks.length) {
return combineLatest(
blocks.map((block) => this.asyncValidation(block))
)
.pipe(defaultIfEmpty([])); // <-- fallback to empty list
}
return of(blocks);
}),
map((blocks) => blocks.map((block) => this.validation(block)))
);
Or, alternatively, start with an empty array:
return combineLatest(
blocks.map((block) => this.asyncValidation(block))
)
.pipe(startWith<any>([]));