Skip to content

Commit

Permalink
docs #2
Browse files Browse the repository at this point in the history
  • Loading branch information
wassim-k committed Jun 18, 2024
1 parent e649f21 commit 083e9ef
Show file tree
Hide file tree
Showing 30 changed files with 948 additions and 80 deletions.
2 changes: 1 addition & 1 deletion docs/docs/angular/fetching/mutations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ In some **edge** cases both `data` and `error` might be undefined, for example w
:::

## `mapMutation`
Similar to `mapQuery`, `Apollo Orbit` provides a `mapMutation` RxJS operator to map the `data` of a mutation result while preserving the other properties.
Similar to `mapQuery`, **Apollo Orbit** provides a `mapMutation` RxJS operator to map the `data` of a mutation result while preserving the other properties.
This is useful when you want to map the mutation result without dealing with the nullability of `data` property.
```typescript
import { mapMutation } from '@apollo-orbit/angular';
Expand Down
35 changes: 26 additions & 9 deletions docs/docs/angular/fetching/queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ import { BooksQuery } from '../graphql/types';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BooksComponent {
protected readonly booksQuery = this.apollo.watchQuery(new BooksQuery({ genre: 'Fiction' }));
protected readonly booksQuery = this.apollo.watchQuery(new BooksQuery());

public constructor(
private readonly apollo: Apollo
Expand All @@ -92,7 +92,7 @@ export class BooksComponent {
```
Then, we subscribe to the query using `async` pipe and handle the different query states:
```html title="books/books.component.html"
<h3>Books (Fiction)</h3>
<h3>Books</h3>
@if (booksQuery | async; as booksResult) {
@if (booksResult.loading) { Loading... }
@if (booksResult.error) { {{ booksResult.error.message }} }
Expand All @@ -104,13 +104,30 @@ Then, we subscribe to the query using `async` pipe and handle the different quer
}
```

## Variables
Variables are passed as constructor parameters to `new BooksQuery()`:

```typescript title="books/books.component.ts"
@Component({
...
})
export class BooksComponent {
// code-add-line
protected readonly booksQuery = this.apollo.watchQuery(new BooksQuery({ genre: 'Fiction' }));

...
}
```

This ensures that a query with required variables generates a class with required constructor parameters as explained in the [Codegen](../codegen/generated-code) section.

## Options
For the complete list of options available to query methods, please refer to <Link to="https://www.apollographql.com/docs/react/data/queries/#options">Apollo Client docs</Link>

In the next example, let's see how we can modify the query from the previous example in order to allow users to manually refetch the data.

First, we will need to pass `notifyOnNetworkStatusChange` option to our `watchQuery` method, this tells Apollo Client to set `loading` property to true whenever a refetch is in flight.
In order to do that, we will need to spread the `query`, `variables` & `context` properties of `new BooksQuery({ genre: 'Fiction' })` and add `notifyOnNetworkStatusChange: true` to the options object.
In order to do that, we will need to spread the `query`, `variables` & `context` properties of `new BooksQuery()` and add `notifyOnNetworkStatusChange: true` to the options object.

Secondly, we define a method in our component that calls `refetch` method on the `QueryObservable` instance.

Expand All @@ -127,9 +144,9 @@ import { BooksQuery } from '../graphql/types';
})
export class BooksComponent {
// code-remove-line
protected readonly booksQuery = this.apollo.watchQuery(new BooksQuery({ genre: 'Fiction' }));
protected readonly booksQuery = this.apollo.watchQuery(new BooksQuery());
// code-add-line
protected readonly booksQuery = this.apollo.watchQuery({ ...new BooksQuery({ genre: 'Fiction' }), notifyOnNetworkStatusChange: true });
protected readonly booksQuery = this.apollo.watchQuery({ ...new BooksQuery(), notifyOnNetworkStatusChange: true });

public constructor(
private readonly apollo: Apollo
Expand All @@ -145,11 +162,11 @@ export class BooksComponent {

Then, we modify the template to include a button that calls `refetch`, and finally, we replace `booksResult.data?.books` with `booksResult.data ?? booksResult.previousData`.
This is because while a refetch is in flight, the `QueryResult` object will have `{ loading: true, data: undefined, ... }` which can provide a jarring user experience as the data disappears off the screen until the new data arrives.
**Apollo Orbit** exposes a `previousData` property which stores the last non-nil value of the `data` property which can be used to provider a smoother user experience.
**Apollo Orbit** exposes a `previousData` property which stores the last non-nil value of the `data` property that can be used to provide a smoother user experience.

```html title="books/books.component.html"
<h3>
Books (Fiction)
Books
// code-add-line
<button type="button" (click)="refetch()">⟳</button>
</h3>
Expand All @@ -168,12 +185,12 @@ This is because while a refetch is in flight, the `QueryResult` object will have
```

## `mapQuery`
`Apollo Orbit` provides a `mapQuery` RxJS operator to map both `data` and `previousData` of a query result while preserving the other properties.
**Apollo Orbit** provides a `mapQuery` RxJS operator to map both `data` and `previousData` of a query result while preserving the other properties.
This is useful when you want to map the query result without dealing with the nullability of `data` and `previousData` properties.
```typescript
import { mapQuery } from '@apollo-orbit/angular';
...
protected readonly bookNames$: Observable<QueryResult<Array<string>>> = this.apollo.watchQuery(new BooksQuery({ genre: 'Fiction' })).pipe(
protected readonly bookNames$: Observable<QueryResult<Array<string>>> = this.apollo.watchQuery(new BooksQuery()).pipe(
// code-add-line
mapQuery(data => data.books.map(book => book.name))
);
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/angular/fetching/subscriptions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Then, we subscribe to the data using `async` pipe:
```

## `mapSubscription`
`Apollo Orbit` provides a `mapSubscription` RxJS operator to map `data` of a subscription result while preserving the other properties.
**Apollo Orbit** provides a `mapSubscription` RxJS operator to map `data` of a subscription result while preserving the other properties.
This is useful when you want to map the subscription result without dealing with the nullability of `data` property.
```typescript
import { mapSubscription } from '@apollo-orbit/angular';
Expand Down
43 changes: 20 additions & 23 deletions docs/docs/angular/get-started/setup-environment.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ sidebar_position: 2.1
---

import Link from '@docusaurus/Link';

import TabItem from '@theme/TabItem';
import Tabs from '@theme/Tabs';
import CodegenTip from '../../shared/_codegen-tip.mdx';

# Setup Environment
The following steps will get you fully setup with `@apollo-orbit/angular` and `@apollo-orbit/codegen`.
Expand All @@ -28,12 +28,7 @@ All the code samples in the docs assume that the following steps were completed


### Setup `@apollo-orbit/codegen`

:::info
While `@apollo-orbit/angular` can work fully without `@apollo-orbit/codegen` it is highly recommended to use codegen in order to unlock its full potential.
:::

More information on `@apollo-orbit/codegen` can be found in the [Codegen guide](../codegen/intro).
<CodegenTip />

#### Install codegen dependencies

Expand Down Expand Up @@ -66,7 +61,6 @@ More information on `@apollo-orbit/codegen` can be found in the [Codegen guide](
`yarn codegen` can also be executed manually to trigger GraphQL codegen.
:::


#### Create codegen.ts

```typescript title="codegen.ts"
Expand Down Expand Up @@ -112,32 +106,35 @@ const config: CodegenConfig = {
export default config;
```
More information on `@apollo-orbit/codegen` can be found in the [Codegen guide](../codegen/intro).
### Install Apollo GraphQL extension
### Setup GraphQL syntax highlighting & auto-complete
#### Install extension
<Link to="https://marketplace.visualstudio.com/items?itemName=apollographql.vscode-apollo">VSCode Marketplace - Apollo GraphQL</Link>
#### Install VSCode extension
<Link to="https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql">GraphQL: Language Feature Support</Link>
#### Create apollo.config.js
#### Create graphql.config.js
Create the following file in the root folder of your project.
For more information, visit <Link to="https://www.apollographql.com/docs/devtools/apollo-config/">Configuring Apollo projects</Link>
```javascript title="apollo.config.js"
```javascript title="graphql.config.js"
module.exports = {
client: {
service: {
name: 'API',
url: 'http://localhost:4000/graphql',
skipSSLValidation: true
},
includes: [
`${__dirname}/src/app/**/*.graphql`,
`${__dirname}/src/app/**/*.state.ts`
schema: [
'http://localhost:4000/graphql',
'src/app/**/*.state.ts'
],
documents: [
'src/app/**/*.graphql'
],
extensions: {
customDirectives: [
'directive @client on FIELD' // required for Apollo Client local schema
]
}
};
```
For more information, visit <Link to="https://github.com/graphql/graphiql/blob/main/packages/graphql-language-service-server/README.md#graphql-configuration-file">configuration documentation</Link>
### Update ESLint
Add the following rule to *.eslintrc.js* to ensure the correct import path is used in your application.
```javascript title=".eslintrc.js "
Expand Down
8 changes: 2 additions & 6 deletions docs/docs/angular/index.md → docs/docs/angular/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ sidebar_position: 1
---

import Link from '@docusaurus/Link'
import StateFeatures from '../shared/_state-features.mdx'

# Introduction
**Apollo Orbit** is a fully-featured **<Link to="https://www.apollographql.com/docs/react/">Apollo Client</Link>** for **<Link to="https://angular.io/">Angular</Link>**.
Expand All @@ -20,12 +21,7 @@ import Link from '@docusaurus/Link'
* **Battle tested and production ready:** used in production environment for 3+ years.

### State Management
* **Comprehensive state management**: **Apollo Orbit** combines the strengths of **Apollo Client** and traditional state management libraries, providing a unified solution for managing both local and remote data.
* **Decoupling**: Separate state management code from component code using modular `state` definitions and `action` handlers.
* **Modular:** Each `state` definition manage its own slice of the cache.
* **Separation of concerns (SoC):** Different `state` slices can handle the same `Mutation` or `Action` independently.
* **Event-driven architecture:** **Apollo Orbit** `@Action`s enable event driven application design.
* **Testability:** `state` logic can be tested in isolation, enhancing testability.
<StateFeatures />

## Docs
The docs are designed to supplement <Link to="https://www.apollographql.com/docs/react/">Apollo Client docs</Link>, with focuses on the **Angular** specific API and features implemented by **Apollo Orbit**.
Expand Down
10 changes: 4 additions & 6 deletions docs/docs/angular/state/action.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@ sidebar_position: 8
title: 'action'
---

import EventCommand from '../../shared/_event-command.mdx';

## Overview
Actions are simple classes that have a unique `type` property and, optionally, additional information.

Actions generally take one of two forms:
* **Command**: A verb. For example, `[Theme] ToggleTheme`
* **Event**: Past tense. For example, `[Library] BookSelected`

:::tip
When unsure whether to use a **command** or an **event** for a certain action, it is advisable to choose **event**.
Events simply relay what has already happened, without making assumptions about how the various states should react.
In contrast, a command could potentially obscure information about what has happened and dictate what states should do.
:::
<EventCommand />

## Defining an action
An action is required to have a unique `type`. By convention, `type` follows the format `[<Context>] <ActionName>` to uniquely identify the action throughout the app.
Expand Down Expand Up @@ -80,7 +78,7 @@ interface ActionType<T> {
## Handling an action
`action` is used in a `state` slice to define an action handler.

An `action` handler can be synchronous or asynchronous and can be used to update the state, call a service, or perform any other logic.
An `action` handler can be synchronous or asynchronous and can be used to update the cache, call a service, or perform any other logic.

Action handlers can be chained together to form a sequence of actions that are executed in order, by using the `dispatch` function which is available in the `ActionContext` parameter.

Expand Down
11 changes: 4 additions & 7 deletions docs/docs/angular/state/intro.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ sidebar_position: 1
pagination_label: State
---

import StateFeatures from '../../shared/_state-features.mdx'

# Introduction
**Apollo Client** is much more than a simple GraphQL client. It offers a comprehensive caching API that leverages GraphQL's rich type information system (schema) to provide features like cache normalization, optimistic UI and more.

**Apollo Orbit** aims to bridge the gap between **Apollo Client** and a full-fledged state management solutions like NGXS, NgRx or Redux.
**Apollo Orbit** aims to bridge the gap between **Apollo Client** and a full-fledged state management solution like NGXS, NgRx or Redux.

It does so by providing the following features:
* **Comprehensive state management**: **Apollo Orbit** combines the strengths of **Apollo Client** and traditional state management libraries, providing a unified solution for managing both local and remote data.
* **Decoupling**: Separate state management code from component code using modular `state` definitions and `action` handlers.
* **Modular:** Each `state` definition manage its own slice of the cache.
* **Separation of concerns (SoC):** Different `state` slices can handle the same `Mutation` or `Action` independently.
* **Event-driven architecture:** **Apollo Orbit** `@Action`s enable event driven application design.
* **Testability:** `state` logic can be tested in isolation, enhancing testability.
<StateFeatures />
15 changes: 7 additions & 8 deletions docs/docs/angular/state/local-state.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ In this example, we'll build a theme management feature that allows users to swi
First, we'll start by defining the local schema for our theme management feature, by creating a theme `state` slice and defining the schema in `typeDefs`.

```typescript title="states/theme/theme.state.ts"
import { state } from '@apollo-orbit/angular';
import gql from 'graphql-tag';
import { gql, state } from '@apollo-orbit/angular';

export const themeState = () => {
return state(descriptor => descriptor
Expand All @@ -38,8 +37,8 @@ export const themeState = () => {
extend type Query {
theme: Theme!
}`
)
}
`)
);
};
```
Expand Down Expand Up @@ -180,7 +179,7 @@ export function provideGraphQL(): EnvironmentProviders {
}
```

## 3. Define theme component
## 3. Create theme component
Finally, we'll bring it all together by defining our theme component which queries the cache and dispatches the relevant actions.

```typescript title="theme/theme.component.ts"
Expand Down Expand Up @@ -226,7 +225,7 @@ There you have it! we've created our first fully local state managed feature wit

<details>
<summary>Here's the complete `theme.state.ts` code for reference</summary>
```typescript title="states/theme/theme.state.ts"/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
```typescript title="states/theme/theme.state.ts"
import { inject } from '@angular/core';
import { gql, state } from '@apollo-orbit/angular';
import { ThemeName, ThemeQuery } from '../../graphql/types';
Expand All @@ -251,8 +250,8 @@ There you have it! we've created our first fully local state managed feature wit
extend type Query {
theme: Theme!
}`
)
}
`)
.typePolicies({
Theme: {
fields: {
Expand Down
9 changes: 7 additions & 2 deletions docs/docs/angular/state/mutation-update.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Link from '@docusaurus/Link'
It is used to update the cache following the execution of a mutation.

## Usage
As stated previously in the [`mutations guid`](../fetching/mutations#caching), unlike `UpdateBookMutation`, updating the cache after executing `AddBookMutation` requires manual intervention.
As stated previously in the [`mutations guid`](../fetching/mutations#cache-normalisation), unlike `UpdateBookMutation`, updating the cache after executing `AddBookMutation` requires manual intervention.

Let's build on the same example used in [fetching guide](../fetching/queries) to demonstrate how `mutationUpdate` can be used to update the cache after executing `AddBookMutation`.

Expand All @@ -36,7 +36,7 @@ export const bookState = state(descriptor => descriptor
.mutationUpdate(AddBookMutation, (cache, info) => {
const addBook = info.data?.addBook;
if (!addBook) return;
cache.updateQuery(new BooksQuery({ genre: 'Fiction' }), query => query ? { books: [...query.books, addBook] } : query);
cache.updateQuery(new BooksQuery(), query => query ? { books: [...query.books, addBook] } : query);
})
);
```
Expand Down Expand Up @@ -68,6 +68,11 @@ The example above demonstrates how the same mutation can be handled independentl

`info` argument is of type `MutationInfo<AddBookMutationData, AddBookMutationVariables>` and all logic in `mutationUpdate` handler is type-safe.

:::note
`mutationUpdate` also accepts the name of the mutation as a `string` argument. This can be used in projects that do not have codegen setup.
So the above code can be updated to `.mutationUpdate('AddBook', (cache, info) =>` and the rest of the code remains the same, except it won't have type-safety.
:::

## API
```typescript
interface MutationInfo<T = any, V = Variables, C = Context> {
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/angular/state/optimistic-response.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ title: 'optimisticResponse'
import Link from '@docusaurus/Link'

## Overview
`optimisticResponse` is analogous to <Link to="https://www.apollographql.com/docs/react/data/mutations/#options">`optimisticResponse`</Link> option passed to [`mutate`](../fetching/mutations#mutate) method in `Apollo`.
`optimisticResponse` is analogous to <Link to="https://www.apollographql.com/docs/react/data/mutations/#mutationhookoptions-interface-optimisticresponse">`optimisticResponse`</Link> option passed to [`mutate`](../fetching/mutations#mutate) method in `Apollo`.
It enables <Link to="https://www.apollographql.com/docs/react/performance/optimistic-ui">optimistic UI</Link>, allowing for immediate UI updates before the server responds.

## Usage
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/angular/state/refetch-queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ title: 'refetchQueries'
import Link from '@docusaurus/Link'

## Overview
`refetchQueries` is analogous to <Link to="https://www.apollographql.com/docs/react/data/mutations/#options">`refetchQueries`</Link> option passed to [`mutate`](../fetching/mutations#mutate) method in `Apollo`.
`refetchQueries` is analogous to <Link to="https://www.apollographql.com/docs/react/data/mutations/#mutationhookoptions-interface-refetchqueries">`refetchQueries`</Link> option passed to [`mutate`](../fetching/mutations#mutate) method in `Apollo`.
It enables <Link to="https://www.apollographql.com/docs/react/data/mutations/#refetching-queries">refetching queries</Link> after a mutation has executed.

## Usage
Expand All @@ -18,12 +18,12 @@ import { AddBookMutation, BooksQuery } from '../../graphql/types';

export const bookState = state(descriptor => descriptor
// code-add-line
.refetchQueries(AddBookMutation, info => [new BooksQuery({ genre: 'Fiction' })])
.refetchQueries(AddBookMutation, info => [new BooksQuery()])
// code-remove-start
.mutationUpdate(AddBookMutation, (cache, info) => {
const addBook = info.data?.addBook;
if (!addBook) return;
cache.updateQuery(new BooksQuery({ genre: 'Fiction' }), query => query ? { books: [...query.books, addBook] } : query);
cache.updateQuery(new BooksQuery(), query => query ? { books: [...query.books, addBook] } : query);
})
// code-remove-end
);
Expand Down
6 changes: 4 additions & 2 deletions docs/docs/angular/state/resolver.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export const bookState = state(descriptor => descriptor
.typeDefs(gql`
extend type Book {
displayName: String!
}`)
}
`)
.resolver(['Book', 'displayName'], (rootValue: Book, args, context, info): Book['displayName'] => {
const { name, genre } = rootValue;
return typeof genre === 'string' ? `${name} (${genre})` : name;
Expand Down Expand Up @@ -65,7 +66,8 @@ export const bookState = state(descriptor => descriptor
.typeDefs(gql`
extend type Book {
displayName: String!
}`)
}
`)
.typePolicies({
Book: {
fields: {
Expand Down
Loading

0 comments on commit 083e9ef

Please sign in to comment.