I'm bringing back RestAngular
July 9, 2026
Let's take a trip back to roughly 2013. AngularJS was the hottest thing on the
block, we all had strong opinions about $scope, and there was this little
library called Restangular that made
talking to REST APIs feel like magic.
If you never used it, here's the pitch: you fetched a list of things, and the things came back enriched. Every object carried its own CRUD methods:
// AngularJS, once upon a time
Restangular.all('players').getList().then(function (players) {
var alice = players[0];
alice.score += 3;
alice.put(); // saves alice
alice.remove(); // deletes alice
});
No services to write, no URLs to concatenate, no boilerplate. The object was the API. For a whole lot of everyday CRUD apps, this was genuinely great - you stopped thinking about HTTP and started thinking about your domain.
And then Angular 2 happened, and the pattern quietly died.
- Why it died (and why that was fair)
- The accidental rediscovery
- Let's build it: players for a board game
- The one crucial difference
- Let's see this in action
- Should this be a library?
Why it died (and why that was fair)
RestAngular's magic depended on something AngularJS was uniquely comfortable with: long-lived mutable objects. Dirty checking didn't care how your object changed, so an object that mutated itself and PUT itself to a server was a perfectly good citizen.
Modern Angular is built on the opposite contract. OnPush, immutable updates,
and now signals - state changes by replacement, not mutation. An object
that carries its own .put() method breaks down immediately:
- the first
{ ...player }spread silently drops the methods, - a method that closes over the object points at a stale snapshot after the first update,
- and dev-mode state freezing throws the moment anything mutates itself.
So the ecosystem moved the methods off the data and into services and stores. Correct, robust - and, let's be honest, a little less fun.
The accidental rediscovery
Recently I was working through an NgRx SignalStore
lab and stumbled over a detail in the (experimental) withResource feature
from the ngrx-toolkit:
when you register a named resource, its value doesn't just sit there as a
read-only signal - it becomes regular store state that you can patchState.
Loaded from the server, but locally writable…
That's when it clicked: I can't put methods on the data anymore - but I can put them on a handle around the data. Same ergonomics, signal-compatible mechanics. RestAngular is back, baby. Almost.
Let's build it: players for a board game
Say we're building a companion app for board game nights. There's a backend
with a /players endpoint, and a player looks like this:
export interface Player {
id: number;
name: string;
color: string;
score: number;
}
A slim client using Angular's httpResource for reading and plain
HttpClient for writing:
@Injectable({ providedIn: 'root' })
export class PlayerClient {
private http = inject(HttpClient);
private baseUrl = '/api/players';
findPlayersResource() {
return httpResource<Player[]>(() => this.baseUrl, { defaultValue: [] });
}
create(player: Omit<Player, 'id'>): Observable<Player> {
return this.http.post<Player>(this.baseUrl, player);
}
update(player: Player): Observable<Player> {
return this.http.put<Player>(`${this.baseUrl}/${player.id}`, player);
}
delete(id: number): Observable<void> {
return this.http.delete<void>(`${this.baseUrl}/${id}`);
}
}
Now the store. The named resource players gives us playersValue,
playersIsLoading, playersError - and, crucially, playersValue is
patchable state:
import { computed, inject } from '@angular/core';
import {
patchState,
signalStore,
withMethods,
withProps,
} from '@ngrx/signals';
// withResource is the experimental one from the ngrx-toolkit,
// not to be confused with @ngrx/signals itself
import { withResource } from '@angular-architects/ngrx-toolkit';
import { firstValueFrom } from 'rxjs';
import { Player } from './player';
import { PlayerClient } from './player-client';
export const PlayerStore = signalStore(
{ providedIn: 'root' },
withProps(() => ({
_client: inject(PlayerClient),
})),
withResource(
(store) => ({
players: store._client.findPlayersResource(),
}),
// on error, keep the last good list instead of undefined
{ errorHandling: 'previous value' },
),
// ...and here comes the time machine 👇
);
By the way: notice what's not here. There's no getPlayers() method on the
client and no loadPlayers() on the store to fill it. The resource is the
loader - it fires the request when the store comes to life and delivers the
result straight into playersValue (plus playersIsLoading and
playersError, free of charge). Need a refresh after game night gets messy?
withResource also generates a _playersReload() method for exactly that.
If you catch yourself writing an imperative fetch-then-patch method right
next to a resource, one of the two is about to be redundant.
The handle
Here's the whole trick. A handle wraps a player id - never the player data itself - and combines a reactive view with entity-scoped operations:
withMethods((store) => {
function playerHandle(id: number) {
const value = computed(() =>
store.playersValue().find((p) => p.id === id),
);
const update = (changes: Partial<Player>): void => {
patchState(store, (state) => ({
playersValue: state.playersValue.map((p) =>
p.id === id ? { ...p, ...changes } : p,
),
}));
};
const save = async (): Promise<void> => {
const player = value();
if (!player) return;
const saved = await firstValueFrom(store._client.update(player));
patchState(store, (state) => ({
playersValue: state.playersValue.map((p) => (p.id === id ? saved : p)),
}));
};
const remove = async (): Promise<void> => {
await firstValueFrom(store._client.delete(id));
patchState(store, (state) => ({
playersValue: state.playersValue.filter((p) => p.id !== id),
}));
};
return { value, update, save, remove };
}
return {
player: playerHandle,
async createPlayer(draft: Omit<Player, 'id'>) {
const saved = await firstValueFrom(store._client.create(draft));
patchState(store, (state) => ({
playersValue: [...state.playersValue, saved],
}));
return playerHandle(saved.id);
},
};
}),
And now, with everything setup we can use it like this:
const store = inject(PlayerStore);
// game night begins
const alice = await store.createPlayer({
name: 'Alice',
color: 'teal',
score: 0,
});
// Alice builds a settlement
alice.update({ score: alice.value()!.score + 2 });
await alice.save();
// Bob flips the table and leaves
const bob = store.player(2);
await bob.remove();
That's RestAngular ergonomics, thirteen years later, on top of signals.
The one crucial difference
RestAngular enriched the data. The handle enriches a reference to the
data: it holds only an id and a computed. That's exactly why it works in
the signals world where the original pattern can't:
alice.value()re-reads the store on every call, so the handle survives any number of immutable state replacements - it cannot go stale,- all writes go through
patchState, so immutability,OnPush, and the Redux DevTools timeline keep working (you actually see one tidy state transition per handle operation - try explaining that to your 2013 self), - the data stays plain, serializable, and boring. The fun lives in the handle.
There's one asymmetry the old library had too, just hidden: create can't be
a handle method. Before the server responds there is no id, so there's
nothing to wrap. That's why createPlayer lives on the store - and returning
a fresh handle for the created player closes the loop nicely.
Let's see this in action
You can find a running example at this Stackblitz.
I don't have a /players endpoint and just mocked it with an interceptor. But you can also use this with local storages that are stored in a service
or even on top of your existing Observable Service with rxResource.
Should this be a library?
I know what you're thinking, and I thought it too - for about an hour. Then I looked at the graveyard: ngx-restangular (last release many moons ago), angular-restmod (archived), @ngrx/data (maintenance mode). This niche eats libraries for breakfast.
The honest truth: the handle is ~40 lines, and most of those lines encode
your app's decisions - pessimistic vs. optimistic saves, what save sends,
how reloads interact with local edits. As a dependency it would be all
configuration; as a pattern it's a coffee break to implement.
So no, (unfortunately?) I'm not shipping RestAngular back. I'm bringing back the part worth keeping: the feeling that your objects - fine, your handles - know how to take care of themselves.
Let me know what you think :)
← Back to Blog List