Skip to content

Instantly share code, notes, and snippets.

@secretorange
Last active June 3, 2022 09:01
Show Gist options
  • Save secretorange/6297bdcf0e656a3c39aa8709875f0f79 to your computer and use it in GitHub Desktop.
Save secretorange/6297bdcf0e656a3c39aa8709875f0f79 to your computer and use it in GitHub Desktop.
A simple single file Angular State Service
@Injectable({
providedIn: "root",
})
export class ExampleAppStateService extends StateService {
public get company(): StateItem<CompanyModel> {
return this.get("company", () => new StateItem<CompanyModel>());
}
public get project(): StateItem<ProjectModel> {
return this.get(
"project",
() =>
new StateItem<ProjectModel>((item: ProjectModel) => {
// Add code here to do suff when a project is refresh.
// Perhaps clear out related child state
})
);
}
}
export class StateService {
private items = {};
protected get<T>(key: string, build: () => StateItem<T>) {
if (!this.items[key]) {
this.items[key] = build();
}
return this.items[key];
}
}
export class StateItem<T> {
private onSet: (item: T) => void;
constructor(onSet: (item: T) => void = null) {
this.onSet = onSet;
}
private current: T = null;
private $ = new BehaviorSubject<T>(null);
// Used for adding items to an array
public add(item: any) {
if (!this.current || !Array.isArray(this.current)) {
return;
}
this.current.push(item);
this.$.next(this.current);
}
public get value(): T {
return this.current;
}
public get observable(): BehaviorSubject<T> {
return this.$;
}
public set value(item: T) {
// Update current *before* calling next()
this.current = item;
if (this.onSet) {
this.onSet(item);
}
this.$.next(item);
}
public clear() {
this.value = null;
}
public subscribe(next: (value: T) => void): Subscription {
return this.$.subscribe(next);
}
// Allow the client to wait until the StateItem has a value
public require(): Promise<T> {
if (this.value) {
return Promise.resolve(this.value);
}
return new Promise((resolve) => {
let resolved = false;
this.$.pipe(takeWhile((value) => !resolved)).subscribe((value) => {
if (value) {
resolved = true;
resolve(value);
}
});
});
}
}
@secretorange
Copy link
Author

secretorange commented Jun 3, 2022

After injecting your service you can do stuff like:

  // Get current state
  var currentProject = this.state.project.value;

  // Subscribe to changes
  this.state.project.subscribe((project) => {
    // Do stuff when the project changes
  });
  
 // Set the new state
 this.state.project.value = new Project();

// If you need to wait for some state to be loaded before being able to do something you can:
const project = await this.state.project.require();

// Do something with the project
const items = await getItemsFromTheApi(project.id);

Waiting for a particular state item to load is super useful when refreshing the page and data is being loaded asynchronously by multiple components.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment