{"id":131,"date":"2014-08-13T10:00:00","date_gmt":"2014-08-13T10:00:00","guid":{"rendered":"http:\/\/localhost\/?p=131"},"modified":"2017-01-10T16:47:17","modified_gmt":"2017-01-10T16:47:17","slug":"collection-repeat","status":"publish","type":"post","link":"https:\/\/ionic.io\/blog\/collection-repeat","title":{"rendered":"Collection Repeat"},"content":{"rendered":"<p>When developing apps, it\u2019s fair to expect that a large list of data will be needed. Until now, if you were developing a hybrid app and wanted to show the user a list of 100+ items, that was just too bad. The phone\u2019s browser would laugh at you as you scrolled through your list at ten frames per second. <\/p>\n<p>After trying to fix this with every trick you knew, your frustration would slowly mount until you sighed and acknowledged that the web was just too limited for realistic apps. Then you would be forced to learn Objective-C or Java and start building a native app.<\/p>\n<p>Fortunately, developers no longer need to face such dramatic scenarios, because Ionic has a solution.<\/p>\n<p><!--more--><\/p>\n<h3 id=\"howdoesnativedoitanyway\">How Does Native Do It, Anyway?<\/h3>\n<p>Native apps render huge lists quite seamlessly. A contacts app, for example, can display thousands of items with no problem at all. Achieving this is actually very simple: Native APIs are only rendering the specific items that should be currently visible, relative to the user&#8217;s scroll position. <\/p>\n<p>This means if 10 items can fit in the screen\u2019s viewport, and the user has scrolled halfway down a list of 100 items, only items 50-60 should be rendered.<\/p>\n<p>This <em>is<\/em> possible to do with web technologies, we just have to game the system a little bit&#8230;<\/p>\n<h3 id=\"thebasicconcept\">The Basic Concept<\/h3>\n<p>We know our end goal: Limit the rendering of items in a large list. So how do we get there?<\/p>\n<p>When figuring this out for Ionic, we were heavily inspired by iOS\u2019s UICollectionView. In our implementation, there are three main components:<\/p>\n<ol>\n<li>The <code>collection-repeat<\/code> directive: The developer adds this directive to the element he or she wants to repeat, supplies an array of data, and gives the size (width and height) of each element in that array using a simple expression.<\/p>\n<\/li>\n<li>\n<p>The <code>CollectionRepeatManager<\/code>: This takes the size of the scroll view containing the list, the current scroll value, and the size of each item to figure out exactly which items need to be rendered. <\/p>\n<\/li>\n<li>\n<p>The <code>CollectionDataSource<\/code>: The Manager will ask the DataSource for an item at a certain index, and the DataSource will give the Manager a complete DOM element representing that item.<\/p>\n<\/li>\n<\/ol>\n<h3 id=\"thesecretsauce\">The Secret Sauce<\/h3>\n<p>The biggest piece of the puzzle is how to performantly render new elements as the user scrolls through the list.<\/p>\n<p>While scrolling down, elements that were just at the top of the screen are no longer visible. What should be done with these elements? Our first thought was to remove these from the DOM.<\/p>\n<div style=\"float: right; margin-right: -160px\">\n  {% include codepen.html id=&#8221;mFygh&#8221; %}\n<\/div>\n<p>The solution we found to be the most performant, though, is to hide these no longer visible elements and mark them as \u201cavailable for rendering.\u201d If we were to remove no-longer-visible elements, we would have to append them to the DOM again later, as the user scrolled down. And appending new elements to the DOM while the user scrolls causes noticeable jankiness.<\/p>\n<p>Later, the Manager asks the DataSource, \u201cGive me an element matching data at index 16, so I can render it.\u201d The DataSource then finds an element that\u2019s already in the DOM and marked as available. It assigns item 16\u2019s data to that element\u2019s scope and gives the element to the Manager. <\/p>\n<p>The Manager then transforms the element into its proper position in the view, using the width and height given by the developer at the start to calculate its position. We use Javascript to transform the elements into their proper positions&#8211;again, so that we don\u2019t have to worry about ordering elements in the DOM as the user scrolls. The actual order of elements in the list doesn\u2019t matter: They are transformed into the proper position to look like they\u2019re in the proper order, and the end user can\u2019t tell the difference. <\/p>\n<h3 id=\"problemsolvednowwhat\">Problem Solved. Now What?<\/h3>\n<p>What does this mean for developers? It means we are no longer limited by the weaknesses of the DOM and the mobile webview. It means we can finally have as much data as needed, keep scrolling smooth and performant, <em>and<\/em> keep our users happy, all at the same time.<\/p>\n<p>Crazy, huh?<\/p>\n<p>Check out <a href=\"http:\/\/ionicframework.com\/docs\/api\/directive\/collectionRepeat\">Collection Repeat\u2019s documentation<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When developing apps, it\u2019s fair to expect that a large list of data will be needed. Until now, if you were developing a hybrid app and wanted to show the user a list of 100+ items, that was just too bad. The phone\u2019s browser would laugh at you as you scrolled through your list at [&hellip;]<\/p>\n","protected":false},"author":5,"featured_media":1652,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"publish_to_discourse":"","publish_post_category":"","wpdc_auto_publish_overridden":"","wpdc_topic_tags":"","wpdc_pin_topic":"","wpdc_pin_until":"","discourse_post_id":"","discourse_permalink":"","wpdc_publishing_response":"","wpdc_publishing_error":"","_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[3],"class_list":["post-131","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-all","tag-ionic"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v23.0 (Yoast SEO v23.0) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Collection Repeat - Ionic Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/ionic.io\/blog\/collection-repeat\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Collection Repeat\" \/>\n<meta property=\"og:description\" content=\"When developing apps, it\u2019s fair to expect that a large list of data will be needed. Until now, if you were developing a hybrid app and wanted to show the user a list of 100+ items, that was just too bad. The phone\u2019s browser would laugh at you as you scrolled through your list at [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/ionic.io\/blog\/collection-repeat\" \/>\n<meta property=\"og:site_name\" content=\"Ionic Blog\" \/>\n<meta property=\"article:published_time\" content=\"2014-08-13T10:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2017-01-10T16:47:17+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2014\/08\/collectionrepeat-header.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1400\" \/>\n\t<meta property=\"og:image:height\" content=\"514\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Mike Hartington\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@mhartington\" \/>\n<meta name=\"twitter:site\" content=\"@ionicframework\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Mike Hartington\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"3 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/ionic.io\/blog\/collection-repeat#article\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/collection-repeat\"},\"author\":{\"name\":\"Mike Hartington\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/c8c92b04d526adb925ea514c619a267b\"},\"headline\":\"Collection Repeat\",\"datePublished\":\"2014-08-13T10:00:00+00:00\",\"dateModified\":\"2017-01-10T16:47:17+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/ionic.io\/blog\/collection-repeat\"},\"wordCount\":697,\"commentCount\":3,\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/collection-repeat#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2014\/08\/collectionrepeat-header.png\",\"keywords\":[\"Ionic\"],\"articleSection\":[\"All\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/ionic.io\/blog\/collection-repeat#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/ionic.io\/blog\/collection-repeat\",\"url\":\"https:\/\/ionic.io\/blog\/collection-repeat\",\"name\":\"Collection Repeat - Ionic Blog\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/ionic.io\/blog\/collection-repeat#primaryimage\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/collection-repeat#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2014\/08\/collectionrepeat-header.png\",\"datePublished\":\"2014-08-13T10:00:00+00:00\",\"dateModified\":\"2017-01-10T16:47:17+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/ionic.io\/blog\/collection-repeat#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/ionic.io\/blog\/collection-repeat\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/collection-repeat#primaryimage\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2014\/08\/collectionrepeat-header.png\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2014\/08\/collectionrepeat-header.png\",\"width\":1400,\"height\":514},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/ionic.io\/blog\/collection-repeat#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/ionic.io\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Collection Repeat\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/ionic.io\/blog\/#website\",\"url\":\"https:\/\/ionic.io\/blog\/\",\"name\":\"ionic.io\/blog\",\"description\":\"Build amazing native and progressive web apps with the web\",\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/ionic.io\/blog\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/ionic.io\/blog\/#organization\",\"name\":\"Ionic\",\"url\":\"https:\/\/ionic.io\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png\",\"width\":1920,\"height\":854,\"caption\":\"Ionic\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/x.com\/ionicframework\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/c8c92b04d526adb925ea514c619a267b\",\"name\":\"Mike Hartington\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2018\/08\/mike-headshot-2-smaller-150x150.png\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2018\/08\/mike-headshot-2-smaller-150x150.png\",\"caption\":\"Mike Hartington\"},\"description\":\"Director of Developer Relations\",\"sameAs\":[\"https:\/\/twitter.com\/mhartington\",\"https:\/\/x.com\/mhartington\"],\"url\":\"https:\/\/ionic.io\/blog\/author\/mike\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Collection Repeat - Ionic Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/ionic.io\/blog\/collection-repeat","og_locale":"en_US","og_type":"article","og_title":"Collection Repeat","og_description":"When developing apps, it\u2019s fair to expect that a large list of data will be needed. Until now, if you were developing a hybrid app and wanted to show the user a list of 100+ items, that was just too bad. The phone\u2019s browser would laugh at you as you scrolled through your list at [&hellip;]","og_url":"https:\/\/ionic.io\/blog\/collection-repeat","og_site_name":"Ionic Blog","article_published_time":"2014-08-13T10:00:00+00:00","article_modified_time":"2017-01-10T16:47:17+00:00","og_image":[{"width":1400,"height":514,"url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2014\/08\/collectionrepeat-header.png","type":"image\/png"}],"author":"Mike Hartington","twitter_card":"summary_large_image","twitter_creator":"@mhartington","twitter_site":"@ionicframework","twitter_misc":{"Written by":"Mike Hartington","Est. reading time":"3 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/ionic.io\/blog\/collection-repeat#article","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/collection-repeat"},"author":{"name":"Mike Hartington","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/c8c92b04d526adb925ea514c619a267b"},"headline":"Collection Repeat","datePublished":"2014-08-13T10:00:00+00:00","dateModified":"2017-01-10T16:47:17+00:00","mainEntityOfPage":{"@id":"https:\/\/ionic.io\/blog\/collection-repeat"},"wordCount":697,"commentCount":3,"publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"image":{"@id":"https:\/\/ionic.io\/blog\/collection-repeat#primaryimage"},"thumbnailUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2014\/08\/collectionrepeat-header.png","keywords":["Ionic"],"articleSection":["All"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/ionic.io\/blog\/collection-repeat#respond"]}]},{"@type":"WebPage","@id":"https:\/\/ionic.io\/blog\/collection-repeat","url":"https:\/\/ionic.io\/blog\/collection-repeat","name":"Collection Repeat - Ionic Blog","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/ionic.io\/blog\/collection-repeat#primaryimage"},"image":{"@id":"https:\/\/ionic.io\/blog\/collection-repeat#primaryimage"},"thumbnailUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2014\/08\/collectionrepeat-header.png","datePublished":"2014-08-13T10:00:00+00:00","dateModified":"2017-01-10T16:47:17+00:00","breadcrumb":{"@id":"https:\/\/ionic.io\/blog\/collection-repeat#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/ionic.io\/blog\/collection-repeat"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/collection-repeat#primaryimage","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2014\/08\/collectionrepeat-header.png","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2014\/08\/collectionrepeat-header.png","width":1400,"height":514},{"@type":"BreadcrumbList","@id":"https:\/\/ionic.io\/blog\/collection-repeat#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/ionic.io\/blog"},{"@type":"ListItem","position":2,"name":"Collection Repeat"}]},{"@type":"WebSite","@id":"https:\/\/ionic.io\/blog\/#website","url":"https:\/\/ionic.io\/blog\/","name":"ionic.io\/blog","description":"Build amazing native and progressive web apps with the web","publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/ionic.io\/blog\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/ionic.io\/blog\/#organization","name":"Ionic","url":"https:\/\/ionic.io\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png","width":1920,"height":854,"caption":"Ionic"},"image":{"@id":"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/ionicframework"]},{"@type":"Person","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/c8c92b04d526adb925ea514c619a267b","name":"Mike Hartington","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2018\/08\/mike-headshot-2-smaller-150x150.png","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2018\/08\/mike-headshot-2-smaller-150x150.png","caption":"Mike Hartington"},"description":"Director of Developer Relations","sameAs":["https:\/\/twitter.com\/mhartington","https:\/\/x.com\/mhartington"],"url":"https:\/\/ionic.io\/blog\/author\/mike"}]}},"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2014\/08\/collectionrepeat-header.png","_links":{"self":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/131","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/comments?post=131"}],"version-history":[{"count":0,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/131\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/media\/1652"}],"wp:attachment":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/media?parent=131"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/categories?post=131"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/tags?post=131"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}