State Management for NativeScript Apps
by Alexander Vakrilov
Alex
Alex
ufsa
vakrilov
What is state
Login
Locations
Selected Location
Current Page
Markers
Map Viewport
Component Based Architecture
Componentized App
Manage State not UI
Managing DOM
$(() => {
$("#add-todo-item").on('click', (e) => {
var todoItem = $("#new-todo-item").val();
$("#todo-list").append(
"<li>" + todoItem +
"<button class='remove-btn'>Delete</button>" +
"</li>"
);
$("#new-todo-item").val("");
});
}
Managing State
// Template
<input [(ngModel)]="newTodo">
<button (click)="addTodo()">add</button>
<ul>
<li *ngFor="let todo of todos"> {{ todo }}</li>
</ul>
// JS
addTodo(){
this.todos.push(this.newTodo);
this.newTodo = "";
}
Data Projection
State
UI
Handle Changes
State
UI
Manage State not UI
“Just do it”
Map Component Template
<ActivityIndicator *ngIf="loading" …/>
<ns-map-list-item
*ngFor="let sensor of sensors"
[sensor]="sensor"
[selected]="sensor === currentSensor"
(select)="selectSensor(sensor)">
</ns-map-list-item>
Map Component Code
sensors: Array<Sensor>;
currentSensor: Sensor;
loading: boolean;
constructor(public service: SensorService) { }
selectSensor(selected: Sensor) {
this.currentSensor = selected;
}
ngOnInit(): void {
this.loading = true;
this.service.getItems()
.subscribe((items) => {
this.sensors = items;
this.loading = false;
});
}
State
Mutate State
Map Component UI
<GridLayout>
<Mapbox (mapReady)="onMapReady($event)" …/>
<!-- rest of the ui -->
</GridLayout>
Adding Map Markers
onMapReady(args): void {
this.mapView = args.map;
const marker = <MapboxMarker>{
id: 1,
lat: 52.3602160,
lng: 4.8891680,
title: 'One-line title here'};
this.mapView.addMarkers([marker]);
}
// select marker
marker.update({ ...marker, selected: true });
// delete marker
this.mapView.removeMarkers([marker.id]);
Just Code It
… 2 hours later
(summarized in 9 slides)
Result
Manage State not UI
and
@Component({
selector: "ns-map-marker"
})
export class MapMarkerComponent {
@Input() title: string;
@Input() lat: number;
@Input() lng: number;
//…
private marker: MapboxMarker;
ngOnChanges(changes: SimpleChanges): void {
// handle updates
}
ngOnDestroy(changes: SimpleChanges): void {
// remove updates
}
}
@Component({
selector: "ns-map-marker"
})
export class MapMarkerComponent {
@Input() title: string;
@Input() lat: number;
@Input() lng: number;
//…
private marker: MapboxMarker;
ngOnChanges(changes: SimpleChanges): void {
// handle updates
}
ngOnDestroy(changes: SimpleChanges): void {
// remove updates
}
}
<Mapbox (mapReady)="mapBoxViewApi = $event.map">
<map-marker
*ngFor="let sensor of sensors$ | async"
[mapBoxViewApi]="mapBoxViewApi"
[title]="sensor.name"
[lat]="sensor.location.lat"
[lng]="sensor.location.lng"
(tap)="selectSensor(sensor)" … >
</map-marker>
</Mapbox>
We don’t live in a perfect world
“
Adopt a State Management Approach
Why
Why
Why Not
Make HMR Work for You
let cachedUrl: string;
onBeforeLivesync.subscribe(moduleRef => {
const router = <Router>moduleRef.injector.get(Router);
cachedUrl = router.url;
});
onAfterLivesync.subscribe(({ moduleRef, error }) => {
const router = <RouterExtensions>moduleRef.injector.get(RouterExtensions);
router.navigateByUrl(cachedUrl, { animated: false, clearHistory: true });
});
Single Source Of Truth
Single Source Of Truth
to save and load
import { persistState } from '@datorama/akita';
let cache = {};
const inMemoryStorage = {
setItem: (key, value) => cache[key] = value,
getItem: (key) => cache[key],
clear: () => cache = {}
};
onBeforeLivesync.subscribe(moduleRef => {
// ...
persistState({ storage: inMemoryStorage });
});
Re-cap
Lessons Learned
Thanks!
ufsa
Some Text
Some Text
Right
Some Text
Left
55
SlidesCarnival icons are editable shapes.
This means that you can:
Isn’t that nice? :)
Examples:
createMarker(s: Sensor): MapboxMarker {
return {
id: s.id,
...s.location,
title: s.name,
onTap: () => this.selectSensor(s),
};
}
createMarker(s: Sensor): MapboxMarker { … }
onMapReady(args): void {
this.mapView = args.map;
const markers = this.sensors.map(this.createMarker);
this.mapView.addMarkers(markers);
}
createMarker(s: Sensor): MapboxMarker { … }
onMapReady(args): void {
this.mapView = args.map;
this.renderMap();
}
renderMap(args): void {
if (!this.mapView || !this.sensors) return;
const markers = this.sensors.map(this.createMarker);
this.mapView.addMarkers(markers);
}
createMarker(s: Sensor): MapboxMarker { … }
onMapReady(args): void {
this.mapView = args.map;
this.renderMap();
}
ngOnInit(): void {
this.loading = true;
this.service.getItems().subscribe((items) => {
this.sensors = items;
this.loading = false;
this.renderMap();
});
}
renderMap(args): void {
if (!this.mapView || !this.sensors) return;
const markers = this.sensors.map(this.createMarker);
this.mapView.addMarkers(markers);
}
createMarker(s: SensorVM): MapboxMarker { … }
onMapReady(args): void {
this.mapView = args.map;
this.renderMap();
}
ngOnInit(): void {
this.loading = true;
this.service.getItems().subscribe((items) => {
this.sensors = items;
this.loading = false;
this.renderMap();
});
}
type SensorVM = Sensor & { marker?: MapboxMarker };
renderMap(args): void {
if (!this.mapView || !this.sensors) return;
// Create markers for each sensor
this.sensors.forEach((sensor) => {
sensor.marker = this.createMarker(sensor);
});
this.mapView.addMarkers(
this.sensors.map(s => s.marker)
);
}
createMarker(s: SensorVM): MapboxMarker { … }
onMapReady(args): void {
this.mapView = args.map;
this.renderMap();
}
ngOnInit(): void {
this.loading = true;
this.service.getItems().subscribe((items) => {
this.sensors = items;
this.loading = false;
this.renderMap();
});
}
type SensorVM = Sensor & { marker?: MapboxMarker };
renderMap(args): void { … }
createMarker(s: SensorVM): MapboxMarker { … }
onMapReady(args): void {
this.mapView = args.map;
this.renderMap();
}
ngOnInit(): void {
this.loading = true;
this.service.getItems().subscribe((items) => {
this.sensors = items;
this.loading = false;
this.renderMap();
});
}
type SensorVM = Sensor & { marker?: MapboxMarker };
renderMap(args): void { … }
selectSensor(selected: SensorVM) {
const isSame = this.currentSensor === selected;
if (!isSame) {
selected.marker.update({
...selected.marker,
selected:true
});
}
this.currentSensor = selected;
}
createMarker(s: SensorVM): MapboxMarker { … }
onMapReady(args): void {
this.mapView = args.map;
this.renderMap();
}
ngOnInit(): void {
this.loading = true;
this.service.getItems().subscribe((items) => {
this.sensors = items;
this.loading = false;
this.renderMap();
});
}
type SensorVM = Sensor & { marker?: MapboxMarker };
renderMap(args): void { … }
selectSensor(selected: SensorVM) { … }
addSensor(selected: SensorVM) { … }
removeSensor(selected: SensorVM) { … }