Directional Pagination
GraphQLQueryPager
supports pagination in both the forward and reverse direction, as well as both at once.
Forward Pagination
Forward pagination is the most common form of pagination. It is used to fetch the next n
items in a list. While we have many options depending on our requirements -- whether we use one query or two, whether we want to use a cursor or an offset, whether we want to transform the results, etc. -- we will examine using a cursor with a single query.
let initialQuery = MyQuery(first: 10, after: nil)let pager = GraphQLQueryPager(client: client,initialQuery: initialQuery,extractPageInfo: { data inCursorBasedPagination.Forward(hasNext: data.values.pageInfo.hasNextPage ?? false,endCursor: data.values.pageInfo.endCursor)},pageResolver: { page, paginationDirection in// As we only want to support forward pagination, we can return `nil` for reverse paginationswitch paginationDirection {case .next:return MyQuery(first: 10, after: page.endCursor ?? .none)case .previous:return nil}})
Reverse Pagination
Reverse pagination is used to fetch the previous n
items in a list. While we have many options depending on our requirements -- whether we use one query or two, whether we want to use a cursor or an offset, whether we want to transform the results, etc. -- we will examine using a cursor with a single query.
let initialQuery = MyQuery(first: 10, after: nil)let pager = GraphQLQueryPager(client: client,initialQuery: initialQuery,extractPageInfo: { data inCursorBasedPagination.Reverse(hasNext: data.values.pageInfo.hasPrevious ?? false,endCursor: data.values.pageInfo.startCursor)},pageResolver: { page, paginationDirection in// As we only want to support reverse pagination, we can return `nil` for forward paginationswitch paginationDirection {case .next:return nilcase .previous:return MyQuery(first: 10, before: page.startCursor ?? .none)}})
Bi-directional Pagination
Bi-directional pagination is used to fetch the next n
items in a list, as well as the previous n
items in a list. Given that we can fetch in both directions, the implication is that the initial query fetched is at neither the head nor tail of the list of results. For this example, we will examine using a cursor with a single query.
let pager = GraphQLQueryPager(client: client,initialQuery: MyQuery(first: 10, after: nil),extractPageInfo: { data inCursorBasedPagination.Bidirectional(hasNext: data.values.pageInfo.hasNextPage ?? false,endCursor: data.values.pageInfo.endCursor,hasPrevious: data.values.pageInfo.hasPreviousPage ?? false,startCursor: data.values.pageInfo.startCursor)},pageResolver: { page, direction inswitch direction {case .next:return MyQuery(first: 10, after: page?.endCursor ?? .none)case .previous:return MyQuery(first: 10, before: page?.startCursor ?? .none)}})
Custom Configuration
Generally, it's recommended to use a convenience initializer to create a configured GraphQLQueryPager
. However, if you need to customize the configuration, you can use the init
method directly. If your application only uses a specific type of pagination, it is simple to declare a custom GraphQLQueryPager
convenience initializer that can be used throughout your application. This allows you to easily instantiate a pager without worrying about cases that your application does not support. If your pagination scheme only supports forward pagination and offsets, you can declare a custom GraphQLQueryPager
convenience initializer that only supports forward pagination and offsets. In this example, we declare two initializers for forward offset pagination -- one with a transform
, and one without:
extension GraphQLQueryPager {static func makeForwardOffsetQueryPager<InitialQuery: GraphQLQuery>(client: ApolloClientProtocol,watcherDispatchQueue: DispatchQueue = .main,queryProvider: @escaping (OffsetPagination.Forward?) -> InitialQuery,extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Forward) -> GraphQLQueryPager where Model == PaginationOutput<InitialQuery, InitialQuery> {GraphQLQueryPager.init(client: client,initialQuery: queryProvider(nil),watcherDispatchQueue: watcherDispatchQueue,extractPageInfo: pageExtraction(transform: extractPageInfo),pageResolver: { page, direction inguard direction == .next else { return nil }return queryProvider(page)})}static func makeForwardOffsetQueryPager<InitialQuery: GraphQLQuery, T>(client: ApolloClientProtocol,watcherDispatchQueue: DispatchQueue = .main,queryProvider: @escaping (OffsetPagination.Forward?) -> InitialQuery,extractPageInfo: @escaping (InitialQuery.Data) -> OffsetPagination.Forward,transform: @escaping (InitialQuery.Data) throws -> Model) -> GraphQLQueryPager where Model: RangeReplaceableCollection, T == Model.Element {GraphQLQueryPager.init(client: client,initialQuery: queryProvider(nil),watcherDispatchQueue: watcherDispatchQueue,extractPageInfo: pageExtraction(transform: extractPageInfo),pageResolver: { page, direction inguard direction == .next else { return nil }return queryProvider(page)},initialTransform: transform,pageTransform: transform)}}private func pageExtraction<InitialQuery: GraphQLQuery, P: PaginationInfo, T>(transform: @escaping (InitialQuery.Data) -> P) -> (PageExtractionData<InitialQuery, InitialQuery, T>) -> P {{ extractionData inswitch extractionData {case .initial(let value, _), .paginated(let value, _):return transform(value)}}}private func pageExtraction<InitialQuery: GraphQLQuery, PaginatedQuery: GraphQLQuery, P: PaginationInfo, T>(initialTransform: @escaping (InitialQuery.Data) -> P,paginatedTransform: @escaping (PaginatedQuery.Data) -> P) -> (PageExtractionData<InitialQuery, PaginatedQuery, T>) -> P {{ extractionData inswitch extractionData {case .initial(let value, _):return initialTransfom(value)case .paginated(let value, _):return paginatedTransform(value)}}}