{"id":1438,"date":"2021-04-01T12:01:57","date_gmt":"2021-04-01T19:01:57","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/surface-duo\/?p=1438"},"modified":"2021-04-01T12:01:57","modified_gmt":"2021-04-01T19:01:57","slug":"dual-screen-web-angular","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/surface-duo\/dual-screen-web-angular\/","title":{"rendered":"Adapting your Angular web app for dual-screen devices"},"content":{"rendered":"<p>\n  Hello web developers!\n<\/p>\n<p>\n  Foldable and dual-screen devices are becoming increasingly more common over time, but you may wonder if investing development time to support these devices might be worth it, especially when creating fully responsive web apps is already a challenge. Using the new <a href=\"https:\/\/devblogs.microsoft.com\/surface-duo\/dual-screen-web-experiences-preview?WT.mc_id=javascript-12159-yolasors\">CSS and JavaScript primitives<\/a> is a fun way to discover and learn about the new possibilities offered by devices like the Microsoft Surface Duo, yet you might be looking for a more efficient way to adapt existing apps without having to make drastic changes and dive too much into custom CSS. That&#8217;s what we&#8217;re going to explore below.\n<\/p>\n<p>\n  Earlier this year, Aaron posted how to build a <a href=\"https:\/\/devblogs.microsoft.com\/surface-duo\/dual-screen-react-web?WT.mc_id=javascript-12159-yolasors\">dual-screen web app using React<\/a>, so today I\u2019m going to show how to build for dual-screen devices using Angular.\n<\/p>\n<p>\n  In this post, we&#8217;ll take a look at how you can use <a href=\"https:\/\/angular.io\/\">Angular<\/a> to create a foldable web experience, with minimal changes to an existing code base. We&#8217;ll start from the <a href=\"https:\/\/github.com\/foldable-devices\/demos\/tree\/master\/photo-gallery\">photo gallery demo<\/a>, create an Angular version of it, and then see how using an Angular library makes the foldable adaptation much easier to approach.\n<\/p>\n<p>\n  <img decoding=\"async\" width=\"978\" height=\"908\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/04\/word-image.gif\" class=\"wp-image-1439\" alt=\"Animation of Surface Duo simulator showing Angular web demo\" \/><br\/><em>Figure 1: demonstration of dual-screen Angular web app<\/em>\n<\/p>\n<h2>TL;DR key takeaway<\/h2>\n<p>\n  Adapting existing apps to foldable devices does not mean that you have to rethink your design and code entirely. With the <a href=\"https:\/\/github.com\/sinedied\/ngx-foldable\">ngx-foldable<\/a> library you can adapt existing Angular apps to support dual-screen devices with minimal changes to your app (and no CSS!). <a href=\"https:\/\/github.com\/aaronpowell\/react-foldable\">react-foldable<\/a> is also an alternative if you&#8217;re working with React, and I&#8217;m sure that similar libraries will eventually become available for other frameworks.\n<\/p>\n<h2>Re-creating the Photo Gallery demo with Angular<\/h2>\n<p>\n  I wanted to keep the demo app as simple as possible to understand, so I used the <a href=\"https:\/\/cli.angular.io\/\">Angular CLI<\/a> to generate the project using the minimal template:\n<\/p>\n<pre>ng new photo-gallery --minimal --prefix=pg --style=css --routing=false --strict<\/pre>\n<p>\n  It gives us a nice working base with <a href=\"https:\/\/www.typescriptlang.org\/tsconfig#strict\">strict type checking<\/a> and single file components, which looked perfect for building this demo. I won&#8217;t cover here all the details about what I did to recreate the demo, as I mostly took the existing JavaScript and CSS code from the original photo gallery app and put it in Angular components.\n<\/p>\n<p>\n  You can find the complete application <a href=\"https:\/\/github.com\/sinedied\/surface-duo-photo-gallery\">source code on GitHub<\/a>, but let&#8217;s have a closer look at the most interesting parts here.\n<\/p>\n<h3>App component<\/h3>\n<p>\n  The file <code>app.component.ts<\/code> is the root component of our application. It contains the state, as which image is currently selected, and all the components composing our app. By looking at its template you can glimpse at how our application works:\n<\/p>\n<\/p>\n<pre>&lt;pg-gallery [images]=\"images\" (select)=\"setImage($event)\">&lt;\/pg-gallery>\r\n&lt;pg-fold>&lt;\/pg-fold>\r\n&lt;pg-details [image]=\"currentImage\"><\/pg-details>\r\n&lt;pg-fullview\r\n  [image]=\"currentImage\"\r\n  (close)=\"closeImage()\"\r\n  (previous)=\"previousImage($event)\"\r\n  (next)=\"nextImage($event)\"\r\n>&lt;\/pg-fullview><\/pre>\n<p>\n   From there you can see that our app is made of four main components:\n<\/p>\n<ul>\n<li><strong>Gallery<\/strong> &#8211; a scrollable list of thumbnails\n  <\/li>\n<li><strong>Fold<\/strong> &#8211; a placeholder for the space taken by the fold area on dual-screen devices\n  <\/li>\n<li><strong>Details<\/strong> &#8211; show the zoomed in version of the selected image with its description on dual-screen devices\n  <\/li>\n<li><strong>Fullview<\/strong> &#8211; show the selected image in full screen on single-screen devices\n  <\/li>\n<\/ul>\n<p>\n   The <strong>App<\/strong> component also includes some styling to lay out these components depending on the device configuration:\n<\/p>\n<pre>:host {\r\n  width: 100vw;\r\n  height: 100vh;\r\n  display: flex;\r\n  flex-direction: row;\r\n}\r\n@media (screen-spanning: single-fold-vertical) {\r\n  :host { flex-direction: row; }\r\n}\r\n@media (screen-spanning: single-fold-horizontal) {\r\n  :host { flex-direction: column-reverse; }\r\n}\r\n@media (screen-spanning: none) {\r\n  :host { flex-direction: row; }\r\n}<\/pre>\n<p>\n  Here we&#8217;re using specific media queries to adapt the layout on a dual-screen configuration. You&#8217;ll see these media queries also used in the other components to hide or show them and adapt their design for every configuration, so let&#8217;s have a closer look.\n<\/p>\n<h3>Fold, Details and Fullview components<\/h3>\n<p>\n  These three components are used to display different things depending what device it&#8217;s running on. The <strong>Fullscreen<\/strong> component is used only on single-screen devices, whereas the <strong>Fold<\/strong> and <strong>Details<\/strong> components are used on dual-screen devices.\n<\/p>\n<pre>import { Component } from '@angular\/core';\r\n \r\n@Component({\r\n  selector: 'pg-fold',\r\n  template: `<div class=\"fold\"><\/div>`,\r\n  styles: [\r\n    `\r\n      .fold {\r\n        height: 0;\r\n        width: 0;\r\n        background-size: 40px 40px;\r\n        background-color: #737373;\r\n        background-image: linear-gradient(\r\n          45deg,\r\n          rgba(255, 255, 255, 0.2) 25%,\r\n          transparent 25%,\r\n          transparent 50%,\r\n          rgba(255, 255, 255, 0.2) 50%,\r\n          rgba(255, 255, 255, 0.2) 75%,\r\n          transparent 75%,\r\n          transparent\r\n        );\r\n      }\r\n \r\n      @media (screen-spanning: single-fold-vertical) {\r\n        .fold {\r\n          height: env(fold-height);\r\n          width: env(fold-width);\r\n        }\r\n      }\r\n \r\n      @media (screen-spanning: single-fold-horizontal) {\r\n        .fold {\r\n          height: env(fold-height);\r\n          width: env(fold-width);\r\n        }\r\n      }\r\n    `,\r\n  ],\r\n})\r\nexport class FoldComponent {}<\/pre>\n<p>\n  You can see here that by default the <strong>Fold<\/strong> component is hidden (height and width set to 0), and it&#8217;s made visible with different sizes when a dual-screen device is used. The <strong>Details<\/strong> component uses a similar approach. The <strong>Fullview<\/strong> component does the opposite by hiding itself when a dual-screen device is detected, with this media query:\n<\/p>\n<pre>@media (screen-spanning: single-fold-horizontal),\r\n       (screen-spanning: single-fold-vertical) {\r\n  .container {\r\n    display: none;\r\n  }\r\n}<\/pre>\n<p>\n  With that, we&#8217;ve covered the main principles behind the original photo gallery adaptation. You can see the full source code for this version <a href=\"https:\/\/github.com\/sinedied\/surface-duo-photo-gallery\/tree\/css\">here<\/a>.\n<\/p>\n<p>\n  But we&#8217;ve not really made good usage of Angular features here, as we are including all components whether they&#8217;re needed or not, and use CSS to show or hide them. We also had to use extra CSS with specific media queries, meaning more work was needed to make this demo. It might not be an issue here as our demo remains quite simple, but in more complex applications, this could result in reduced performance due to unnecessary component rendering, and maintenance issues due to the scattered CSS approach.\n<\/p>\n<\/p>\n<h2>Introducing ngx-foldable<\/h2>\n<p>\n  The Angular library <a href=\"https:\/\/www.npmjs.com\/package\/ngx-foldable\">ngx-foldable<\/a> was specifically designed to allow adapting Angular applications while making minimal changes to your code. It provides directives and services to access the screen context information and react to changes automatically.\n<\/p>\n<p>\n  We install it with <code>npm install ngx-foldable<\/code> and then import the <code>FoldableModule<\/code> into our app:\n<\/p>\n<pre>import { FoldableModule } from 'ngx-foldable';\r\n \r\n@NgModule({\r\n  imports: [\r\n    FoldableModule\r\n    ...\r\n  ],\r\n  ...\r\n})\r\nexport class AppModule {}<\/pre>\n<h2>Revisiting the App component<\/h2>\n<p>\n  With the library set up, we can now use the provided <code>fdSplitLayout<\/code>, <code>fdWindow<\/code> and <code>fdIfSpan<\/code> directives to rebuild our App component template:\n<\/p>\n<pre>&lt;div fdSplitLayout=\"flex reverse\">\r\n  &lt;pg-gallery fdWindow=\"0\" [images]=\"images\" (select)=\"setImage($event)\">&lt;\/pg-gallery>\r\n  &lt;pg-details fdWindow=\"1\" *fdIfSpan=\"'multi'\" [image]=\"currentImage\">&lt;\/pg-details>\r\n  &lt;pg-fullview\r\n    *fdIfSpan=\"'none'\"\r\n    [image]=\"currentImage\"\r\n    (close)=\"closeImage()\"\r\n    (previous)=\"previousImage($event)\"\r\n    (next)=\"nextImage($event)\"\r\n  >&lt;\/pg-fullview>\r\n&lt;\/div><\/pre>\n<p>\n  First, you will notice that we added a top <code>&lt;div&gt;<\/code> container with the directive <code>fdSplitLayout<\/code>. This directive enables us to build a split layout on dual-screen devices without the need for extra CSS. The first parameter allows you to choose which kind of CSS layout you want to use, so we&#8217;re using <code>flex<\/code> here.  Other possible options are <code>grid<\/code> or <code>absolute<\/code>, to better fit your existing app layout. The second parameter allow you to choose whether you want to <code>reverse<\/code> the window segments order when the spanning (ie the orientation) changes, or keep the <code>normal<\/code> order.\n<\/p>\n<p>\n  <img decoding=\"async\" width=\"1322\" height=\"719\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/04\/word-image.png\" class=\"wp-image-1440\" alt=\"Three Surface Duo devices showing which screen shows content based on window order values\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/04\/word-image.png 1322w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/04\/word-image-300x163.png 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/04\/word-image-1024x557.png 1024w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/04\/word-image-768x418.png 768w\" sizes=\"(max-width: 1322px) 100vw, 1322px\" \/><br\/><em>Figure 2: Illustration of impact of window order values<\/em>\n<\/p>\n<p>\n  Next you will notice that we added the <code>fdWindow<\/code> directive to the <strong>Gallery<\/strong> and <strong>Details<\/strong> components. This one allows you to assign a particular component to a window segment in dual-screen mode and works only within a <code>fdSplitLayout<\/code> container element.\n<\/p>\n<p>\n  The best part of the <code>fdSplitLayout<\/code> and <code>fdWindow<\/code> directives are that they&#8217;re only activated on dual-screen devices, so absolutely no CSS is added when the app is running on a single-screen device.\n<\/p>\n<p>\n  Notice that we also got rid of the <strong>Fold<\/strong> component, as it&#8217;s no longer needed.\n<\/p>\n<p>\n  Finally, we used the <code>fdIfSpan<\/code> structural directive to show\/hide the <strong>Details<\/strong> and <strong>Fullview<\/strong> components depending on the context. This directive works the same as <code><a href=\"https:\/\/angular.io\/api\/common\/NgIf\">ngIf<\/a><\/code>, except that it&#8217;s wired to pre-defined conditions related to the current screen context.\n<\/p>\n<p>\n  <code>*fdIfSpan=\"'multi'\"<\/code> means that the <strong>Details<\/strong> component will only be attached to the DOM in a multi-screen context, no matter what the orientation of the device is. The <strong>Fullview<\/strong> component uses the opposite value of <code>'none'<\/code>, meaning that it will be present only on single-screen devices. Note that we could have also used the <code><a href=\"https:\/\/angular.io\/api\/common\/NgIf#showing-an-alternative-template-using-else\">else<\/a><\/code> syntax, exactly like a regular <code>ngIf<\/code>.\n<\/p>\n<p>\n  Other possible conditions are <code>'fold-vertical'<\/code> and <code>'fold-horizontal'<\/code> if you need to target a specific orientation.\n<\/p>\n<p>\n  Using these three directives, we can now remove ALL the specific CSS related to single\/dual screen adaptation. Yup, you read that right. With that, the new CSS for our App component simply becomes:\n<\/p>\n<pre>:host {\r\n  width: 100vw;\r\n  height: 100vh;\r\n}<\/pre>\n<p>\n  Less code in the end, better performance, and no need for specific CSS; sounds like a win here? \ud83d\ude42\n<\/p>\n<p>\n   You can see and test the <a href=\"https:\/\/sinedied.github.io\/surface-duo-photo-gallery\/\">final web app online<\/a>.\n<\/p>\n<p>\n  <img decoding=\"async\" width=\"800\" height=\"626\" src=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/04\/graphical-user-interface-application-description.png\" class=\"wp-image-1441\" alt=\"Screenshot of the final photo gallery app on a Surface Duo\" srcset=\"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/04\/graphical-user-interface-application-description.png 800w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/04\/graphical-user-interface-application-description-300x235.png 300w, https:\/\/devblogs.microsoft.com\/surface-duo\/wp-content\/uploads\/sites\/53\/2021\/04\/graphical-user-interface-application-description-768x601.png 768w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><br\/><em>Figure 3: Screenshot of the final photo gallery app<\/em>\n<\/p>\n<h2>Going further<\/h2>\n<p>\n  We&#8217;ve seen how we can abstract the handling of device adaptation and provide a higher-level API using Angular. While it&#8217;s always interesting to have a look at the <a href=\"https:\/\/github.com\/MicrosoftEdge\/MSEdgeExplainers\/blob\/main\/Foldables\/explainer.md#proposal-css-primitives-for-building-dual-screen-layouts\">CSS primitives<\/a> behind it, sometimes we just want a more straightforward way of achieving our intent. That&#8217;s also why CSS libraries like <a href=\"https:\/\/getbootstrap.com\/\">Bootstrap<\/a> and <a href=\"https:\/\/tailwindcss.com\/\">Tailwind CSS<\/a> are so popular for quickly creating responsive designs.\n<\/p>\n<p>\n  You can take a look at the app <a href=\"https:\/\/github.com\/sinedied\/surface-duo-photo-gallery\/\">final code<\/a> and the details of <a href=\"https:\/\/github.com\/sinedied\/surface-duo-photo-gallery\/compare\/css...main\">the changes<\/a> when using the ngx-foldable library.\n<\/p>\n<p>\n  If you&#8217;re curious, you can also dive into the code behind <a href=\"https:\/\/github.com\/sinedied\/ngx-foldable\">ngx-foldable<\/a> and see how it works. Contributions are welcomed too \ud83d\ude09.\n<\/p>\n<h2>Resources and feedback<\/h2>\n<p>\n  Check out the <a href=\"https:\/\/docs.microsoft.com\/dual-screen\/web\/?WT.mc_id=javascript-12159-yolasors\">Surface Duo web developer documentation<\/a> for more details on the CSS and JavaScript APIs for enhancing your web apps for the Surface Duo and other dual-screen devices.\n<\/p>\n<p>\n  If you have any questions, or would like to tell us about your apps, use the <a href=\"http:\/\/aka.ms\/SurfaceDuoSDK-Feedback\">feedback forum<\/a> or message us on Twitter <a href=\"https:\/\/twitter.com\/surfaceduodev\">@surfaceduodev<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hello web developers! Foldable and dual-screen devices are becoming increasingly more common over time, but you may wonder if investing development time to support these devices might be worth it, especially when creating fully responsive web apps is already a challenge. Using the new CSS and JavaScript primitives is a fun way to discover and [&hellip;]<\/p>\n","protected":false},"author":56297,"featured_media":1450,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[715,531,412,530],"class_list":["post-1438","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-surface-duo-sdk","tag-angular","tag-css","tag-javascript","tag-web"],"acf":[],"blog_post_summary":"<p>Hello web developers! Foldable and dual-screen devices are becoming increasingly more common over time, but you may wonder if investing development time to support these devices might be worth it, especially when creating fully responsive web apps is already a challenge. Using the new CSS and JavaScript primitives is a fun way to discover and [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts\/1438","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/users\/56297"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/comments?post=1438"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/posts\/1438\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/media\/1450"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/media?parent=1438"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/categories?post=1438"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/surface-duo\/wp-json\/wp\/v2\/tags?post=1438"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}