I have a list of labels in NativeScript app representing a status. I have method that changes the status of each label after tap. After I tap the label it should change the status value (number) and show new value on the screen right after the tap.
What I've done is partially working. I have to tap it twice to see the label changed, or tap once and pull to refresh to see the label change. Also update in the service is working as expected after tap. Any ideas how to implement this behavior properly?
Views: parent component:
<StackLayout *ngFor="let phase of (training$ | async)?.phases; let i = index">
<StackLayout *ngIf="(training$ | async)?.phases">
<StackLayout>
<ns-phase-item [phase]="phase" [training$]="training$" [i]="i"></ns-phase-item>
</StackLayout>
</StackLayout>
</StackLayout>
First child (ns-phase-item):
<StackLayout>
<Label [text]="phase.phaseTitle" [textWrap]="true" class="title-wrap"></Label>
<StackLayout *ngFor="let unit of (training$ | async).phases[i].units">
<ns-unit-item [unit]="unit" class="unit"></ns-unit-item>
</StackLayout>
</StackLayout>
Child of first child view (ns-unit-item) (where I call the update method):
<StackLayout [ngClass]="{ 'unit-completed': unit.status === 1, 'unit-not-completed': unit.status !== 1 }">
<GridLayout columns="4*, *" rows="auto">
<FlexboxLayout col="0" flexDirection="row" (tap)="onView(unit._id)">
<Label [text]="(unit.date | date: 'd. MMM') + ' - ' + unit.unitTitle"></Label>
<Label *ngIf="unit.duration || unit.distance" text=" ("></Label>
<Label *ngIf="unit.duration" [text]="' ' + unit.duration + ' min'"></Label>
<Label *ngIf="unit.duration && unit.distance" text=" / "></Label>
<Label *ngIf="unit.distance" [text]="unit.distance + ' km'"></Label>
<Label *ngIf="unit.duration || unit.distance" text=")"></Label>
<Label *ngIf="unit$" [text]="(unit$ | async).status"></Label>
</FlexboxLayout>
<StackLayout col="1">
<StackLayout *ngIf="(unit$ | async).status == 1">
// HERE I CALL UPDATE - it should change the value and icon after tap
<Image src="res://check" height="20" stretch="aspectFit" (tap)="onStatusUpdate(0)"></Image>
</StackLayout>
<StackLayout *ngIf="(unit$ | async).status == 0">
<Image src="res://checkgray" height="20" stretch="aspectFit" (tap)="onStatusUpdate(1)"></Image>
</StackLayout>
</StackLayout>
</GridLayout>
</StackLayout>
Child of first child controller (ns-unit-item component)
private _unit: BehaviorSubject<Unit> = new BehaviorSubject<Unit>(null)
getUnitObservable(): Observable<Unit> {
return this._unit.asObservable()
}
getUnitValue(): Unit {
return this._unit.getValue()
}
unitChanged(unit: Unit) {
this._unit.next(unit)
}
ngOnInit(): void {
this.unitChanged(this.unit)
this.unit = this.getUnitValue()
this.unit$ = this.getUnitObservable()
}
onStatusUpdate(status: number) {
this.trainingService.updateTrainingUnitStatus(this.unit._id, status).subscribe((unit) => {
this.unitChanged(unit)
this.unit$ = this.getUnitObservable()
})
}
Service where I call API
updateTrainingUnitStatus(id: string, status: number) {
return this.http.post<Unit>(`${this.API_URL_UNITS}/${id}/status`, { status: status })
}
I used RxJS BehaviorSubject to accomplish that. I have simplified the solution, and used BehaviorSubject with number only and not full object.
in the service:
public unitStatus: BehaviorSubject<Number> = new BehaviorSubject<Number>(0)
in component before calling the API to update I call next on BehaviorSubject
onStatusUpdate(status: number) {
console.log(status)
this.trainingService.unitStatus.next(status)
this.subscription = this.trainingService
.updateTrainingUnitStatus(this.unitId, status)
.pipe(tap((data) => console.log(data.status)))
.subscribe(() => {
this.status$ = this.trainingService.unitStatus.asObservable()
this.status = this.trainingService.unitStatus.getValue()
console.log(this.status)
})
}
In the view I use "status" property which is changing after button tap (and is inline with persistent data from DB)
<FlexboxLayout margin="0" padding="0">
<Button
*ngIf="status === 0"
text="MARK AS DONE"
(tap)="onStatusUpdate(1)"
class="-rounded -primary"
></Button>
<Button
*ngIf="status === 1"
text="MARK AS NOT DONE"
(tap)="onStatusUpdate(0)"
class="-rounded -primary"
></Button>
</FlexboxLayout>