Title: Reusable and recyclable NativeScript views · Issue #7469 · NativeScript/NativeScript · GitHub
Open Graph Title: Reusable and recyclable NativeScript views · Issue #7469 · NativeScript/NativeScript
X Title: Reusable and recyclable NativeScript views · Issue #7469 · NativeScript/NativeScript
Description: Is your feature request related to a problem? Please describe. Removing a NativeScript view from the visualtree results in all native views being disposed on Android. On iOS, some native views are not disposed, but their delegates are. T...
Open Graph Description: Is your feature request related to a problem? Please describe. Removing a NativeScript view from the visualtree results in all native views being disposed on Android. On iOS, some native views are ...
X Description: Is your feature request related to a problem? Please describe. Removing a NativeScript view from the visualtree results in all native views being disposed on Android. On iOS, some native views are ...
Opengraph URL: https://github.com/NativeScript/NativeScript/issues/7469
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Reusable and recyclable NativeScript views","articleBody":"**Is your feature request related to a problem? Please describe.**\r\nRemoving a NativeScript view from the visualtree results in all native views being disposed on Android. On iOS, some native views are not disposed, but their delegates are. This behavior seems to come from https://github.com/NativeScript/NativeScript/pull/3912 but I haven't found an explanation for why it is that way.\r\n\r\nSince the view is destroyed, there is no way to do, for example:\r\n\r\n```typescript\r\nconst parent1 = stack1;\r\nconst parent2 = stack2;\r\nconst st = new StackLayout();\r\nconst bt = new Button()\r\nst.addChild(bt);\r\nparent1.addChild(st);\r\n// st.android != null\r\nparent1.removeChild(st);\r\n// st.android == null\r\nparent2.addChild(st);\r\n// st.android != null\r\n```\r\n\r\nWithout the view being destroyed and created again.\r\n\r\nThis also makes hard to use some plugins like `nativescript-popup`. We've tried creating an angular template and passing the NS views to the plugin, but once we detached the view from it's parent, the native views became null and the popup was empty. Our workaround was to manually use the native views that were created and set `view.parent = null` otherwise the app would crash when closed.\r\n\r\n**Describe the solution you'd like**\r\nNativeScript views could be reusable:\r\n\r\n```typescript\r\nconst parent1 = stack1;\r\nconst parent2 = stack2;\r\nconst st = new StackLayout();\r\nst.reusable = true;\r\nconst bt = new Button()\r\nst.addChild(bt);\r\nparent1.addChild(st);\r\nconst native = st.android;\r\nparent1.removeChild(st);\r\nnative === st.android // true\r\nparent2.addChild(st);\r\nnative === st.android // true\r\n```\r\n\r\nThis is different from https://github.com/NativeScript/NativeScript/issues/4189, since we're not just reusing the native views, but the whole NS view, so no properties have to be reset. Maybe an additional method (like `view.inflate()`) could be provided to inflate views without the need to attach to the \"dom\".\r\n\r\n**Additional context**\r\nI've only used core and angular flavors, so I'll contextualize it using them:\r\n\r\nAngular/Vue/React/etc flavors of nativescript would create all their views as `reusable`. The angular renderer has [destroyNode](https://angular.io/api/core/Renderer2#destroyNode) method that is unimplemented by `nativescript-angular` because the core already destroys the view when it's detached. [This method is called when the view is not being used anymore](https://github.com/angular/angular/blob/7ca611cd1266ede26a26b17cb31748988ff8d98c/packages/core/src/render3/node_manipulation.ts#L90-L97), so the view can be safely detached and reused. I'm sure the other frameworks have similar approaches.\r\n\r\nThis necessity came up when I was starting to develop a recyclerview plugin with shared pools (which is possible natively, no need for this PR) and general use of `nativescript-popup` and I stumbled upon my views being destroyed whenever I tried to detach them.\r\n\r\nHere's a playground of the current behavior: https://play.nativescript.org/?template=play-tsc\u0026id=eorFFE\u0026v=6\r\n\r\nHere's what happens when we try detaching and attaching: https://play.nativescript.org/?template=play-ng\u0026id=mXwdnl\r\n\r\nIdeally, \"create native view\" should only output once for each label created by \"Create Views\" and none for \"Move Views\".\r\n\r\nPartially inspired by: https://hackernoon.com/react-native-listview-performance-revisited-recycling-without-the-bridge-c4f62d18c7dd?gi=87e16d79933e\r\n\r\n### Use cases\r\n\r\n#### Custom reusable heavy views/view tree in the core\r\n\r\nAllows you to reuse a view or view tree anywhere. Examples: footer that is used in almost every screen only needs to instanced once. Same view pool for multiple ListViews. A conditional view that may be added or not to a ListView item (the current best practice is instantiate all views and set the `visibility` property, or using multiple templates, potentially increasing memory usage).\r\n\r\n```typescript\r\n// viewrecyler.ts\r\nexport class ViewRecycler {\r\n viewPool = [];\r\n availableViews = [];\r\n prefetch = 5;\r\n\r\n constructor() {\r\n for (let i = 0; i \u003c this.prefetch; i++) {\r\n this.generateReusableView();\r\n }\r\n }\r\n\r\n generateReusableView() {\r\n const view = new HeavyView(); // HeavyView could be a whole view tree\r\n view.reusable = true;\r\n view.inflate(); // force creation, we're prefetching.\r\n this.viewPool.push(view);\r\n this.availableViews.push(view);\r\n return view;\r\n }\r\n\r\n getView() {\r\n if (this.availableViews.length === 0) {\r\n this.generateReusableView(); // if none available, generate a new one\r\n }\r\n return this.availableViews.pop();\r\n }\r\n\r\n storeView(detachedView: HeavyView) {\r\n this.availableViews.push(detachedView);\r\n }\r\n}\r\n\r\nexport const viewRecycler = new ViewRecycler();\r\n\r\n// page.ts\r\n\r\nlet recyclableView;\r\nfunction navigatedTo() {\r\n const container = getViewById(page, \"viewcontainer\");\r\n recyclableView = viewRecycler.getView();\r\n container.addChild(recyclableView);\r\n}\r\n\r\nfunction navigatingFrom() {\r\n if (recyclableView) {\r\n recyclableView.parent.removeChild(recyclableView);\r\n viewRecycler.storeView(recyclableView);\r\n recyclableView = null;\r\n }\r\n}\r\n```\r\n\r\nIt's up to the developer to set/reset view properties. In the example above, all properties must be set on `recyclableView = viewRecycler.getView();`, or reset on `viewRecycler.storeView(recyclableView);`. When too many properties are being reset each time (defeating the purpose of recycling), it's recommended to add a new view pool.\r\n\r\n\r\n#### ngFor/Repeater\r\n\r\nWhat could be accomplished with this:\r\n```\r\n\u003cButton *ngFor=\"let item of item;\"\u003e\u003c/Button\u003e\r\n```\r\nThis is already possible, but every time you swap something, angular uses `detach` and `insert`, meaning you get lag. Workaround: use `trackBy` using `index`.\r\n```\r\n\u003cButton *ngReusableFor=\"let item of items; uniqueKey:'myForKey'\"\u003e\u003c/Button\u003e\r\n\u003cButton *ngReusableFor=\"let item of items2; uniqueKey:'myForKey'\"\u003e\u003c/Button\u003e\r\n```\r\n\r\n`ngReusableFor` would have a service that stores views in `myForKey`. No matter how many items you add/remove from `items` and `item2`, the same views would be reused in both of them. **caution: the same template has to be used for both fors**.\r\n\r\n#### Global templates\r\nMy favorite use case.\r\n\r\n```\r\n\u003cGlobalTemplates\u003e\r\n \u003cng-template templateKey=\"reusablecard\" let-item=\"item\"\u003e\r\n \u003cCardView\u003e\r\n \u003cStackLayout\u003e\r\n \u003cLabel text=\"item.title\"\u003e\u003c/Label\u003e\r\n \u003cLabel text=\"item.content\"\u003e\u003c/Label\u003e\r\n \u003c/StackLayout\u003e\r\n \u003c/CardView\u003e\r\n \u003c/ng-template\u003e\r\n\u003c/GlobalTemplates\u003e\r\n\r\n...\r\n\u003c!-- use inside a listview, ngfor, or any html --\u003e\r\n\u003cReusableTemplate key=\"reusablecard\" context=\"item\"\u003e\u003c/ReusableTemplate\u003e\r\n```\r\n\r\nEvery time you'd use that template, it'd be a recycled instance from somewhere in the app. You could have horizontal listviews inside a vertical listview and they all share the same pool of recycled components. You could also configure some prefetching (create 5 cards when the app starts and just use them over and over).\r\n\r\n\r\n\r\n\u003cbountysource-plugin\u003e\r\n\r\n---\r\nWant to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/76522545-reusable-and-recyclable-nativescript-views?utm_campaign=plugin\u0026utm_content=tracker%2F12908224\u0026utm_medium=issues\u0026utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin\u0026utm_content=tracker%2F12908224\u0026utm_medium=issues\u0026utm_source=github).\r\n\u003c/bountysource-plugin\u003e","author":{"url":"https://github.com/edusperoni","@type":"Person","name":"edusperoni"},"datePublished":"2019-07-05T14:29:33.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":1},"url":"https://github.com/7469/NativeScript/issues/7469"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:22003466-bf15-77e6-551d-8501296eb4d8 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | AAA0:D6F18:3D51542:54EE5C3:6A60F2BF |
| html-safe-nonce | d963394b6305eccc227513170573db541302ab0debbda1cde8b911997c335151 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJBQUEwOkQ2RjE4OjNENTE1NDI6NTRFRTVDMzo2QTYwRjJCRiIsInZpc2l0b3JfaWQiOiI3NDczNDYyMjczMzAwODIwNjcxIiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | 66306a9286b56257b55359ce5e815f4d2b77b7baeda3fde908f6ca499b6ea130 |
| hovercard-subject-tag | issue:464666183 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/NativeScript/NativeScript/7469/issue_layout |
| twitter:image | https://opengraph.githubassets.com/7caa0e1567410c86fe70834fd326333308ebfbcd6a75165f0e03244d0f8b4b92/NativeScript/NativeScript/issues/7469 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/7caa0e1567410c86fe70834fd326333308ebfbcd6a75165f0e03244d0f8b4b92/NativeScript/NativeScript/issues/7469 |
| og:image:alt | Is your feature request related to a problem? Please describe. Removing a NativeScript view from the visualtree results in all native views being disposed on Android. On iOS, some native views are ... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | edusperoni |
| hostname | github.com |
| expected-hostname | github.com |
| None | 01a0f3379195d313175de239776b09dd4a079d5b2ea29dd9c37e85cd4cd5e990 |
| turbo-cache-control | no-preview |
| go-import | github.com/NativeScript/NativeScript git https://github.com/NativeScript/NativeScript.git |
| octolytics-dimension-user_id | 7392261 |
| octolytics-dimension-user_login | NativeScript |
| octolytics-dimension-repository_id | 31492490 |
| octolytics-dimension-repository_nwo | NativeScript/NativeScript |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 31492490 |
| octolytics-dimension-repository_network_root_nwo | NativeScript/NativeScript |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 00ca1a9089c8f2453e5d118d0554c8a26883d159 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width