Loona is a state management library that's built on top of Apollo Client, a client-side library you use to communicate with a GraphQL service.
State management libraries can help with the following:
1. Create your Ionic 4 Project
ionic start ionicLoona --type=angular
2. Install GraphQL and the Apollo Client libraries
npm install graphql graphql-tag apollo-client apollo-angular apollo-angular-link-http apollo-cache apollo-cache-inmemory --save
3. Install the loona libraries
npm install @loona/angular
4. Import ApolloModule into the NgModule of your app.module.ts
//STEP 4 CODE
import {ApolloModule} from "apollo-angular";
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
//STEP 4 CODE
ApolloModule,
BrowserModule, IonicModule.forRoot(), AppRoutingModule],
providers: [
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent]
})
export class AppModule {}
Insert the commented code snippets into src/app/app.module.ts
5. Add the LoonaModule and a cache to app.module.ts
//STEP 5 CODE
import { LoonaModule, LOONA_CACHE } from "@loona/angular";
import { InMemoryCache } from "apollo-cache-inmemory";
//STEP 5 CODE
const cache = new InMemoryCache();
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [ApolloModule,
//STEP 5 CODE
LoonaModule.forRoot(),
BrowserModule, IonicModule.forRoot(), AppRoutingModule],
providers: [
//STEP 5 CODE
{
provide : LOONA_CACHE,
useValue : cache
},
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent]
})
export class AppModule {}
Insert the commented code snippets into src/app/app.module.ts
6. Create an Apollo instance and add the LoonaLink to it.
//STEP 6 CODE
import {ApolloModule, Apollo} from "apollo-angular";
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [ApolloModule, LoonaModule.forRoot(), BrowserModule, IonicModule.forRoot(), AppRoutingModule],
providers: [{
provide : LOONA_CACHE,
useValue : cache
},
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent]
})
export class AppModule {
//STEP 6 CODE
constuctor(apollo: Apollo, loona: LoonaLink){
apollo.create({
link : loona,
cache
})
}
}
7. Define the graphql queries.
import gql from 'graphql-tag';
export const GET_ALL_BOOKS = gql`
query GetAllBooks {
books @client {
id
title
}
}
`
export const ADD_NEW_BOOK = gql`
mutation AddNewBook($title: String!) {
addBook(title: $title) @client {
id
title
}
}
`;
export const DELETE_BOOK = gql`
mutation DeleteBook($id: ID!) {
deleteBook(id: $id) @client {
id
}
}
`;
Insert all of the following code in the src/app/queries.ts
8. Define the state & schemas.
import { State, Mutation, Update } from '@loona/angular';
import gql from 'graphql-tag';
import { GET_ALL_BOOKS, ADD_NEW_BOOK } from './queries'
@State({
typeDefs: `
type Book {
id: ID
title: String
}
type Query {
books: [Book]
}
`,
defaults: {
books: [{
id: 100,
title: "PITA - What Life Is Sometimes",
__typename: 'Book'
}],
},
})
export default class TheState {
// Resolvers
// Mutations
@Mutation('addBook')
addBook(args, context) {
// our new book
const b = {
id: new Date().getTime(),
title: args.title,
__typename: 'Book',
};
return b
}
@Mutation('deleteBook')
deleteBook(args, context) {
context.patchQuery(GET_ALL_BOOKS,
data => {
data.books = data.books.filter((b) => {
return b.id !== args.id
});
},
);
return { id: args.id, __typename: 'Book', }
}
// Updates
@Update('addBook')
addToBooks(mutation, context) {
const book = mutation.result;
// updates the store
context.patchQuery(GET_ALL_BOOKS,
data => {
data.books = data.books.concat([book]);
},
);
}
// Action handlers
}
Insert all of the following code in the src/app/app-state.ts
Pass the state defined above to the LoonaModule in src/app/app.module.ts.
//STEP 8 CODE
import State from "./app-state";
const cache = new InMemoryCache();
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [ApolloModule,
//STEP 8 CODE
LoonaModule.forRoot([State]),
BrowserModule, IonicModule.forRoot(), AppRoutingModule],
providers: [{
provide : LOONA_CACHE,
useValue : cache
},
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent]
})
9. Add a component to test the Loona module implementation.
9.a. Delete the contents of the home directory.
9.b. Create a new component called book.component.ts in the src/app directory and post the following content into it.
9.c. In the src/app/app.component.html, replace the router tag with the <book-list tag> so that it looks like this
9.d. Add the book component to the declaration list of the src/app/app.module.ts file.
import { Component, Input } from '@angular/core';
import { Loona } from '@loona/angular';
import { map, tap, pluck } from 'rxjs/operators';
import { GET_ALL_BOOKS, ADD_NEW_BOOK, DELETE_BOOK } from './queries'
@Component({
selector: 'book-list',
template: `
<ul>
<li *ngFor="let book of (books$ | async)">
{{book.id}} : {{book.title}}
<button (click)="deleteBook(book.id)">DELETE</button>
</li>
</ul>
<div>
<input type="text" placeholder=" enter title..." [(ngModel)]="input.title"/>
<button (click)="addBook()">ADD</button>
</div>
`,
styles: [`h1 { font-family: Lato; }`]
})
export class BookListComponent {
books$;
input = {
title: ""
}
constructor(private loona: Loona) { }
ngOnInit() {
this.books$ = this.loona.query(GET_ALL_BOOKS,
).valueChanges.pipe(pluck('data', 'books'));
}
deleteBook(_id) {
this.loona.mutate(DELETE_BOOK, { id: _id }).subscribe();
}
addBook() {
this.loona.mutate(ADD_NEW_BOOK, { title: this.input.title }).subscribe();
}
}
<ion-app>
<book-list></book-list>
</ion-app>