Collection Service provides methods to subscribe on changes from Firestore.
syncCollection
will subscribe to your collection, and update the store accordingly.
syncCollection(path: string | Observable<string> | QueryConstraint[], queryConstraints?: QueryConstraint[])
It takes a path
and/or firestore queryConstraints
. By default CollectionService
's path
will be used.
this.service.syncCollection().subscribe();
With queryConstraints
this.service.syncCollection([limit(10)]).subscribe();
For subcollections:
const queryConstraints = [orderBy('age')];
this.parentQuery
.selectActiveId()
.pipe(
map((id) => `parent/${id}/subcollection`),
switchMap((path) => this.service.syncCollection(path, queryConstraints))
)
.subscribe();
syncCollectionGroup
will subscribe to a firebase collection group and sync the store with the result.
`
syncCollectionGroup(queryConstraints?: QueryConstraint[]);
syncCollectionGroup(collectionId?: string, queryConstraints?: QueryConstraint[]);
If not provided, the method will use the currentPath
as collectionId.
path
is a subcollection, syncCollectionGroup
will take the last part of the path (eg: if path is movies/{movieId}/stakeholders
then the collectionId will be stakeholders
).
syncDoc
will subscribe to a specific document, and update the store accordingly.
syncDoc(options: { id: string } | { path: string });
It takes either an { id: string }
object OR a { path: string }
.
this.route.params
.pipe(switchMap(({ id }) => this.service.syncDoc({ id })))
.subscribe();
syncManyDocs
subscribes to a list of documents and update the store accordingly
syncManyDocs(ids: string[]);
Here is an example that sync all movie from the active user:
userQuery
.selectActive()
.pipe(
pluck('movieIds'),
distinctUntilChanges((x, y) => x.length === y.length), // trigger only when amount of movieIds changes
tap(() => movieStore.reset()), // Remove old ids from the store before sync
switchMap((movieIds) => movieService.syncManyDocs(movieIds))
)
.subscribe();
syncActive
is an helper that run syncDoc({id})
or syncManyDocs(ids)
and setActive(id)
.
this.service.syncActive(`movies/${movieId}`); // ActiveState
this.service.syncActive(['1', '2', '3']); // ManyActiveState
path: string;
The path
is the path of your Firestore collection. It can be overridden, which can be useful for subcollections.
collection: CollectionReference<E>
The collection
is a snapshot of the collection. It's mostly used for writing operations (add
, remove
, update
).
getValue(options?: Partial<SyncOptions>): Promise<E[]>
getValue(id: string, options?: Partial<SyncOptions>): Promise<E>
getValue(ids: string[], options?: Partial<SyncOptions>): Promise<E[]>
getValue(queryConstraints: QueryConstraint[], options?: Partial<SyncOptions>): Promise<E[]>
Returns a snapshot of the collection or a document in the collection. If no parameters are provided, will fetch the whole collection:
const movie = await movieService.getValue('star_wars');
const movies = await movieServie.getValue(userQuery.getActive().movieIds); // all movies of current user
const movies = await movieService.getValue([limit(10)]);
const stakeholders = await stakeholderService.getValue({ params: { movieId } }); // all sub-collection
// Collection Reference
getRef(options?: Partial<SyncOptions>): Promise<CollectionReference<E>>;
// Document Reference
getRef(ids?: string[], options?: Partial<SyncOptions>): Promise<DocumentReference<E>[]>;
getRef(id?: string, options?: Partial<SyncOptions>): Promise<DocumentReference<E>>;
Return the reference of the document or collection.
Listen on the changes of a document, list of documents or collection.
valueChanges(options?: Partial<PathParams>): Observable<E[]>
valueChanges(id: string, options?: Partial<PathParams>): Observable<E>
valueChanges(ids: string[], options?: Partial<PathParams>): Observable<E[]>
valueChanges(queryConstraints: QueryConstraint[], options?: Partial<PathParams>): Observable<E[]>
You can multicast observables on the docs queries by id by specifying the flat useMemorization
:
@Injectable({ providedIn: 'root' })
@CollectionConfig({ path: 'movies' })
export class MovieService extends CollectionService<MovieState> {
useMemorization = true; // Enable multicasting
constructor(store: MovieStore) {
super(store);
}
}
CollectionService
provides three methods to update Firestore. This library encourages you to sync your Akita store with Firestore (see above), so you shouldn't update the store yourself after add
, remove
or update
succeed.
batch(): WriteBatch
runTransaction<T>((tx: Transaction) => Promise<T>)
Create a batch object or run a transaction. Those methods are just alias for writeBatch(firestore)
& runTransaction(firestore)
.
This is god practice to use AtomicWrite when you operate several interdependant write operations.
add(entities: E[] | E, options?: WriteOptions): Promise<string | string[]>
Add one or several documents in your collection. And return the id(s).
add
will create an id on the client-side if not provided.
This example shows how to add a movie and a stakeholder for this movie with a batch:
const write = await movieService.batch();
const movieId = await movieService.add({ name: 'Star Wars' }, { write });
await stakeholderService.add(
{ name: 'Walt Disney' },
{ write, params: { movieId } }
);
write.commit();
remove(ids: string | string[], options?: WriteOptions)
Remove one or several documents from the collection.
To avoid wrong manipulatoin,
remove()
will not remove all document in a collection. UseremoveAll
for that.
removeAll(options?: WriteOptions)
Remove all document in a collection
This example shows how to remove a movie and all stakeholders in it's subcollection with a batch:
const movieId = movieQuery.getActiveId();
const write = await movieService.batch();
const movieId = await movieService.remove(movieId, { write });
await stakeholderService.removeAll({ write, params: { movieId } });
write.commit();
update(entity: Partial<E> | Partial<E>[], options?: WriteOptions)
update(id: string | string[], newState: Partial<E>, options?: WriteOptions)
update(id: string | string[], newStateFn: ((entity: Readonly<E>, tx: Transaction) => UpdateData<E>), options?: WriteOptions)
Update one or several documents in the collection.
When using a newStateFn, akita-ng-fire will use a transaction so it cannot be combine with a batch:
This example remove a movie from a user, and update the stakeholders of the movie:
const user = userQuery.getActive();
const movieId = movieQuery.getActiveId();
await userService.update(uid, async (user, tx) => {
const movieIds = user.movieId.filter((id) => id !== movieId);
await stakeholderService.remove(uid, { params: { movieId } }); // Remove user from stakeholders of movie
return { movieIds }; // Update user movieIds
});
upsert(entities: E[] | E, options?: WriteOptions): Promise<string | string[]>
Create or update one or a list of document.
If an array is provided, upsert
will check for every element if it exists. In this case, it's highly recommended to provide a transaction in the option parameter:
service.runTransaction((write) => service.upsert(manyDocs, { write }));
You can hook every write operation and chain them with atomic operations:
onCreate(entity: E, options: { write: AtomicWrite, ctx?: any })
onUpdate(entity: E, options: { write: AtomicWrite, ctx?: any })
onDelete(id: string, options: { write: AtomicWrite, ctx?: any })
The options
parameter is used to pass atomic writer and optional contextual data.
The write
parameter is either a batch
or a transaction
used to group several write operations.
The ctx
parameter is used to send contextual data for cascading writings.
For example, you can remove all stakeholders of a movie on deletion:
class MovieService extends CollectionService<Movie> {
constructor(
store: MovieStore,
private stakeholderService: StakeholderService
) {
super(store);
}
onDelete(movieId: string, { write, ctx }: WriteOptions) {
return this.stakeholderService.removeAll({ write, params: { movieId } });
}
}
You can also chain the atomic write:
class OrganizationService extends CollectionService<Organization> {
constructor(
store: OrganizationStore,
private userService: UserService,
private userQuery: UserQuery
) {
super(store);
}
onCreate(organization: Organization, options: WriteOptions) {
const uid = this.userQuery.getActiveId();
return this.userService.update(
uid,
(user) => {
return { orgIds: [...user.orgIds, organization.id] };
},
options
); // We pass the "options" parameter as 3rd argument of the update to do everything in one batch
}
}
You can format your data when it comes from Firestore with a custom function.
To do so you have to override the function formatFromFirestore
.
formatFromFirestore(stakeholder: Readonly<Stakeholder>): Stakeholder {
return { ...stakeholder, name: `The original name was ${stakeholder.name}, but now its formatFromFirestore` };
}