I recently was looking for a solution to generate and manage pagination buttons related to a large collection inside a table with Angular2. I would use it in a modal page such that the table doesn’t override it. I first try to attach a scroll bar to the tbody of the table to limit the size. This solution used css tricks and was not consistent according to the navigators.

Finally, I start implementing my own Angular2 component. This component could be attached to any collections. I start developing incrementally and I was quickly face to poor quality code and mutable state to determine the context. I stopped and meditate in a more functional way. I isolate the main functionality. This last can be formalized as:

  • Given a collection and the current page
  • Provide the list of Strings showing maximum 10 elements and comprising page number or ‘…’ if the number of pages is greater than 10. (Previous and Next buttons are not counted)

This illustrates the objective of the paginated buttons-group: Pagination buttons

Considering I like functional programming even if I mainly use imperative paradigm, I try a first version in Haskell before reimplementing in JS.

Haskell Version:

{-| page is the number of pages and activep is the number of the active page -}
paginate :: Int -> Int -> [String]
paginate page activep
    | page == 0                 = []
    | page <= 10                = map show [1..page]
    | page > 10 && activep <= 5 = map show [1..7] ++ [".."] ++ map show [page-1, page]
    | activep >= page -5        = map show [1..2] ++ [".."] ++ map show [page-6..page]
    | otherwise                 = map show [1..2] ++ [".."] ++ map show [activep-1..activep+2] ++ [".."] ++ map show [page-1, page]

Then, I think “great”. Let’s keep this version and look at how integrating it with a TypeScript-Angular2 project. I looked for some ways to compile Haskell to JS with ghcjs but for now, the project is not very robust. Other solutions: PureScript or ELM. I chose Purescript instead of ELM because this last focus on view.

I change the syntax a bit and arrive to this version:

mapToString :: forall a. ( Show a ) => Array a -> Array String
mapToString = map show

{-| page is the number of pages and activep is the number of the active page -}
paginate :: Int -> Int -> Array String
paginate page activep
    | page == 0                 = []
    | page <= 10                = mapToString $ 1..page
    | page > 10 && activep <= 5 = (mapToString $ 1..7) <> [".."] <> (mapToString [page-1, page])
    | activep >= page -5        = (mapToString $ 1..2) <> [".."] <> (mapToString $ (page-6)..page)
    | otherwise                 = (mapToString $ 1..2)
                                    <> [".."]
                                    <> (mapToString $ (activep-1)..(activep+2))
                                    <> [".."]
                                    <> (mapToString [page-1, page])

Nice. It works perfectly with my Angular2 project but if I look at the generated JS compressed code: ~32000 characters and 113 Kio. It includes lots of PuresScript standard libraries.

Ok, I suggest using PureScript in projects including lots of PureScript code. But just for a couple of functionalities, it’s not wise. So I confess the defeat and write a TypeScript + Lodash version. Finally, this is not so bad:

/* page is the number of pages and activep is the number of the active page */
private paginate(size, activePage) : string[] {
  if (size == 0){
    return [];
  } else if (size <= 10) {
    return _.range(1, size+1).map( i => i.toString() );
  } else if (size > 10 && activePage <= 5) {
    return _.range(1, 8)
      .map( i => i.toString() )
      .concat("..")
      .concat( _.range(size -1, size+1).map( i => i.toString() ));
  } else if (activePage >= size -5) {
    return _.range(1, 3)
      .map( i => i.toString() )
      .concat("..")
      .concat( _.range(size -6, size+1).map( i => i.toString() ));
  } else {
    return _.range(1, 3)
      .map( i => i.toString() )
      .concat("..")
      .concat( _.range(activePage-1, activePage+3).map( i => i.toString() ))
      .concat("..")
      .concat( _.range(size-1, size+1) );
  }
}

You observed that I transform number to string with the map method. Js is weakly typed, so it is not mandatory but it remains coherent.

Check out the final Angular2 version on Plunker and the Gist repository for Haskell, PureScript and TypeScript version of the function paginate.