Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Creating GUI container components in Angular and Web Components

Creating GUI container components in Angular and Web Components

So you've embraced architecting your Angular application with reusable components--cheers to you! But you have UI components that need multiple entry points for user markup, and regular ng-transclude left you hanging. In this talk, we'll cover how new web component standards, like the Shadow DOM, handle this. Next, we'll walk through how to accomplish it today in Angular 1.3 -- and also give you a brief glimpse into what a solution will look like in upcoming Angular 2. Afterwards, you'll know how to make layout scaffold components with custom elements that serve as containers for arbitrary user-provided HTML content.

Talk presented at ng-conf in March 2015.

Rachael L Moore

March 10, 2015
Tweet

More Decks by Rachael L Moore

Other Decks in Technology

Transcript

  1. Creating container components in Web Components and Angular ng-conf: March

    5, 2015 Kara Erickson Web Engineer kara karaforthewin Rachael L Moore UI Engineer morewry morewry
  2. <!-- #include virtual="head.html" --> <!-- #include virtual="menu.html" --> I render

    in body. <!-- #include virtual="foot.html" --> Server Side Includes
  3. <div id="site"> <header> <svg id="logo"></svg> <!-- point-1 --> </header> <nav>

    <!-- point-2 --> </nav> <main> <!-- point-3 --> </main> <footer> © 2015 OpenTable, Inc. </footer> </div> Component Development
  4. Component Use <ot-site> <div> I render in head. </div> <div>

    I render in menu. </div> <div> I render in body. </div> </ot-site> I render in head. I render in menu. I render in body.
  5. Component Use <ot-site> <div> <!-- insert-1 --> I render in

    head. </div> <div> <!-- insert-2 --> I render in menu. </div> <div> <!-- insert-3 --> I render in body. </div> </ot-site> I render in head. I render in menu. I render in body.
  6. <div id="site"> <header> <svg id="logo"></svg> <!-- point-1 --> </header> <nav>

    <!-- point-2 --> </nav> <main> <!-- point-3 --> </main> <footer> © 2015 OpenTable, Inc. </footer> </div> <ot-site> <div> <!-- insert-1 --> I render in head. </div> <div> <!-- insert-2 --> I render in menu. </div> <div> <!-- insert-3 --> I render in body. </div> </ot-site> Match Content → ← Match Component
  7. ...had flourished. <span id="myspan"> Long ago there was something in

    me, but now that thing is gone. </span> I cannot... Shadow Host
  8. ...had flourished. <span id="myspan"> Long ago there was something in

    me, but now that thing is gone. </span> I cannot... Shadow Host
  9. ...had flourished. <span id="myspan"> #shadow-root Long ago there was something

    in me, but now that thing is gone. </span> I cannot... Shadow Root
  10. Before ...had flourished. Long ago there was something in me,

    but now that thing is gone. I cannot... After ...had flourished. I cannot...
  11. host.shadowRoot.innerHTML = ` <div id="site"> <header> <svg id="logo"></svg> <!-- point-1

    --> </header> <nav> <!-- point-2 --> </nav> <main> <!-- point-3 --> </main> <footer> © 2015 OpenTable, Inc. </footer> </div> ` ot-site.js
  12. host.shadowRoot.innerHTML = ` <div id="site"> <header> <svg id="logo"></svg> <!-- point-1

    --> </header> <nav> <!-- point-2 --> </nav> <main> <!-- point-3 --> </main> <footer> © 2015 OpenTable, Inc. </footer> </div> ` ot-site.js
  13. <ot-site> #shadow-root <div id="site"> <header> <svg id="logo"></svg> </header> <nav></nav> <main></main>

    <footer>©</footer> </div> <style>/**/</style> </ot-site> Composed DOM
  14. host.shadowRoot.innerHTML = ` <div id="site"> <header> <svg id="logo"></svg> <!-- point-1

    --> </header> <nav> <!-- point-2 --> </nav> <main> <!-- point-3 --> </main> <footer> © 2015 OpenTable, Inc. </footer> </div> ` ot-site.js
  15. host.shadowRoot.innerHTML = ` <div id="site"> <header> <svg id="logo"></svg> <content />

    </header> <nav> <content /> </nav> <main> <content /> </main> <footer> © 2015 OpenTable, Inc. </footer> </div> ` ot-site.js
  16. host.shadowRoot.innerHTML = ` <div id="site"> <header> <svg id="logo"></svg> <content select=""

    /> </header> <nav> <content select="" /> </nav> <main> <content select="" /> </main> <footer> © 2015 OpenTable, Inc. </footer> </div> ` ot-site.js
  17. host.shadowRoot.innerHTML = ` <div id="site"> <header> <svg id="logo"></svg> <content select="[head]"

    /> </header> <nav> <content select="[menu]" /> </nav> <main> <content select="[body]" /> </main> <footer> © 2015 OpenTable, Inc. </footer> </div> ` ot-site.js
  18. <ot-site> <div> <!-- insert-1 --> I render in head. </div>

    <div> <!-- insert-2 --> I render in menu. </div> <div> <!-- insert-3 --> I render in body. </div> </ot-site> index.html
  19. <ot-site> <div head> I render in head. </div> <div menu>

    I render in menu. </div> <div body> I render in body. </div> </ot-site> index.html
  20. Match Content → <div id="site"> <header> <svg id="logo"></svg> <content select="[head]"/>

    </header> <nav> <content select="[menu]"/> </nav> <main> <content select="[body]"/> </main> <footer> © 2015 OpenTable, Inc. </footer> </div> <ot-site> <div head> I render in head. </div> <div menu> I render in menu. </div> <div body> I render in body. </div> </ot-site> ← Match Component
  21. Light DOM Shadow DOM <div id="site"> <header> <svg id="logo"></svg> <content

    select="[head]"/> </header> <nav> <content select="[menu]"/> </nav> <main> <content select="[body]"/> </main> <footer> © 2015 OpenTable, Inc. </footer> </div> <ot-site> <div head> I render in head. </div> <div menu> I render in menu. </div> <div body> I render in body. </div> </ot-site>
  22. <div id="site"> <header> <svg id="logo"></svg> <content select="[head]"/> </header> <nav> <content

    select="[menu]"/> </nav> <main> <content select="[body]"/> </main> <footer> © 2015 OpenTable, Inc. </footer> </div> Light DOM Shadow DOM <ot-site> <div head> I render in head. </div> <div menu> I render in menu. </div> <div body> I render in body. </div> </ot-site>
  23. .directive("otSite", function() { return { template: ` <div id="site"> <header>

    <!-- point-1 --> <svg id="logo"></svg> </header> <nav></nav> <!-- point-2 --> <main></main> <!-- point-3 --> <footer> © 2015 OpenTable, Inc. </footer> </div>` }; }); ot-site.js
  24. <ot-site> <div> I render in head. </div> <div> I render

    in menu. </div> <div> I render in body. </div> </ot-site> index.html
  25. <ot-site> <div> I render in head. </div> <div> I render

    in menu. </div> <div> I render in body. </div> </ot-site> index.html
  26. ot-site.js .directive("otSite", function() { return { template: ` <div id="site">

    <header> <svg id="logo"></svg> </header> <nav></nav> <main></main> <footer> © 2015 OpenTable, Inc. </footer> </div>` }; });
  27. ot-site.js .directive("otSite", function() { return { transclude: true, template: `

    <div id="site"> <header> <svg id="logo"></svg> </header> <nav></nav> <main></main> <footer> © 2015 OpenTable, Inc. </footer> </div>` }; });
  28. <ot-site> <div> I render in head. </div> <div> I render

    in menu. </div> <div> I render in body. </div> </ot-site> DOM
  29. <ot-site> <div> I render in head. </div> <div> I render

    in menu. </div> <div> I render in body. </div> </ot-site> DOM
  30. <ot-site> <div> I render in head. </div> <div> I render

    in menu. </div> <div> I render in body. </div> </ot-site> DOM
  31. <ot-site> <div> I render in head. </div> <div> I render

    in menu. </div> <div> I render in body. </div> </ot-site> Clone DOM
  32. <ot-site> <div> I render in head. </div> <div> I render

    in menu. </div> <div> I render in body. </div> </ot-site> Clone DOM
  33. Clone DOM <div> I render in head. </div> <div> I

    render in menu. </div> <div> I render in body. </div> <ot-site> <div> I render in head. </div> <div> I render in menu. </div> <div> I render in body. </div> </ot-site>
  34. <div> I render in head. </div> <div> I render in

    menu. </div> <div> I render in body. </div> <ot-site> </ot-site> Clone DOM
  35. <ot-site> <div id="site"> <header> <svg id="logo"></svg> </header> <nav></nav> <main></main> <footer>©</footer>

    </div> </ot-site> Clone <div> I render in head. </div> <div> I render in menu. </div> <div> I render in body. </div> DOM
  36. ot-site.js .directive("otSite", function() { return { transclude: true, template: `

    <div id="site"> <header> <svg id="logo"></svg> </header> <nav></nav> <main></main> <footer> © 2015 OpenTable, Inc. </footer> </div>` }; });
  37. ot-site.js .directive("otSite", function() { return { transclude: true, template: `

    <div id="site"> <header ng-transclude> <svg id="logo"></svg> </header> <nav ng-transclude></nav> <main ng-transclude></main> <footer> © 2015 OpenTable, Inc. </footer> </div>` }; });
  38. ot-site.js .directive("otSite", function() { return { transclude: true, template: `

    <div id="site"> <header ng-transclude> <svg id="logo"></svg> </header> <nav ng-transclude></nav> <main ng-transclude></main> <footer> © 2015 OpenTable, Inc. </footer> </div>` }; });
  39. ot-site.js .directive("otSite", function() { return { transclude: true, template: `

    <div id="site"> <header> <svg id="logo"></svg> </header> <nav></nav> <main></main> <footer> © 2015 OpenTable, Inc. </footer> </div>` }; });
  40. ot-site.js .directive("otSite", function() { return { transclude: true, template: `

    <div id="site"> <header t-id="head"> <svg id="logo"></svg> </header> <nav t-id="menu"></nav> <main t-id="body"></main> <footer> © 2015 OpenTable, Inc. </footer> </div>` }; });
  41. <ot-site> <div> <!-- insert-1 --> I render in head. </div>

    <div> <!-- insert-2 --> I render in menu. </div> <div> <!-- insert-3 --> I render in body. </div> </ot-site> index.html
  42. <ot-site> <div t-to="head"> I render in head. </div> <div t-to="menu">

    I render in menu. </div> <div t-to="body"> I render in body. </div> </ot-site> index.html
  43. <div t-to="head"> I render in head. </div> <div t-to="menu"> I

    render in menu. </div> <div t-to="body"> I render in body. </div> <ot-site> <div id="site"> <header t-id="head"> <svg id="logo"></svg> </header> <nav t-id="menu"></nav> <main t-id="body"></main> <footer>©</footer> </div> </ot-site> DOM Clone
  44. <div t-to="head"> I render in head. </div> <div t-to="menu"> I

    render in menu. </div> <div t-to="body"> I render in body. </div> <ot-site> <div id="site"> <header t-id="head"> <svg id="logo"></svg> </header> <nav t-id="menu"></nav> <main t-id="body"></main> <footer>©</footer> </div> </ot-site> DOM Clone angular.forEach(clone, function(cloneEl) {});
  45. <div t-to="head"> I render in head. </div> <div t-to="menu"> I

    render in menu. </div> <div t-to="body"> I render in body. </div> <ot-site> <div id="site"> <header t-id="head"> <svg id="logo"></svg> </header> <nav t-id="menu"></nav> <main t-id="body"></main> <footer>©</footer> </div> </ot-site> DOM Clone angular.forEach(clone, function(cloneEl) {});
  46. <div t-to="head"> I render in head. </div> <div t-to="menu"> I

    render in menu. </div> <div t-to="body"> I render in body. </div> <ot-site> <div id="site"> <header t-id="head"> <svg id="logo"></svg> </header> <nav t-id="menu"></nav> <main t-id="body"></main> <footer>©</footer> </div> </ot-site> DOM Clone angular.forEach(clone, function(cloneEl) {});
  47. <ot-site> <div id="site"> <header t-id="head"> <svg id="logo"></svg> </header> <nav t-id="menu"></nav>

    <main t-id="body"></main> <footer>©</footer> </div> </ot-site> DOM Clone var tId = cloneEl.attributes["t-to"].value; <div t-to="head"> I render in head. </div> <div t-to="menu"> I render in menu. </div> <div t-to="body"> I render in body. </div>
  48. DOM Clone var target = temp.find('[t-id="'+tId+'"]'); <div t-to="head"> I render

    in head. </div> <div t-to="menu"> I render in menu. </div> <div t-to="body"> I render in body. </div> <ot-site> <div id="site"> <header t-id="head"> <svg id="logo"></svg> </header> <nav t-id="menu"></nav> <main t-id="body"></main> <footer>©</footer> </div> </ot-site>
  49. <ot-site> <div id="site"> <header t-id="head"> <svg id="logo"></svg> </header> <nav t-id="menu"></nav>

    <main t-id="body"></main> <footer>©</footer> </div> </ot-site> <div t-to="head"> I render in head. </div> <div t-to="menu"> I render in menu. </div> <div t-to="body"> I render in body. </div> DOM Clone target.append(clone);
  50. <div t-to="head"> I render in head. </div> <div t-to="menu"> I

    render in menu. </div> <div t-to="body"> I render in body. </div> DOM Clone <ot-site> <div id="site"> <header t-id="head"> <svg id="logo"></svg> <div t-to="head"> I render in head. </div> </header> <nav t-id="menu"></nav> <main t-id="body"></main> <footer>©</footer> </div> </ot-site> </ot-site> target.append(clone);
  51. angular.forEach(clone, function(cloneEl) { // get desired target ID var tId

    = cloneEl.attributes["t-to"].value; // find target element with that ID var target = temp.find('[t-id="'+tId+'"]'); // append element to target target.append(cloneEl); }); custom-transclude.js
  52. Transclude function access points compile: function(tElem, tAttrs, transclude) controller: function($scope,

    $element, $transclude) link: function(scope, iElem, iAttrs, ctrl, transclude)
  53. Directive Life Cycle 1 2 3 4 5 6 7

    8 Child compile Child controller Child pre-link Child post-link Parent compile Parent controller Parent pre-link Parent post-link
  54. Directive Life Cycle 1 2 3 4 5 6 7

    8 Child compile Child controller Child pre-link Child post-link Parent compile Parent controller Parent pre-link Parent post-link
  55. ot-site.js .directive("otSite", function() { return { transclude: true, template: ...,

    link: function(scope, elem, attr, ctrl, transclude) { } }; });
  56. ot-site.js .directive("otSite", function() { return { transclude: true, template: ...,

    link: function(scope, elem, attr, ctrl, transclude) { transclude(function(clone) { angular.forEach(clone, function(cloneEl) { var tId = ... var target = ... if (target.length) {...} else {...} }); }); } }; });
  57. ot-site.js .directive("otSite", function() { return { scope: {}, transclude: true,

    template: ..., link: function(scope, elem, attr, ctrl, transclude) { transclude(function(clone) { angular.forEach(clone, function(cloneEl) { var tId = ... var target = ... if (target.length) {...} else {...} }); }); } }; });
  58. ot-site.js .directive("otSite", function() { return { scope: {}, transclude: true,

    template: ..., link: function(scope, elem, attr, ctrl, transclude) { transclude(function(clone) { angular.forEach(clone, function(cloneEl) { var tId = ... var target = ... if (target.length) {...} else {...} }); }); } }; });
  59. <div id="site"> <header t-id="head"> <svg id="logo"></svg> </header> <nav t-id="menu"></nav> <main

    t-id="body"></main> <footer> © 2015 OpenTable, Inc. </footer> </div> ot-site template
  60. <div id="site"> <header t-id="head"> <svg id="logo"></svg> </header> <nav t-id="menu"></nav> <main

    t-id="body"></main> <footer> © 2015 OpenTable, Inc. </footer> </div> ot-site template
  61. <div id="site"> <header> <svg id="logo"></svg> <content select="[head]"></content> </header> <nav> <content

    select="[menu]"></content> </nav> <main> <content select="[body]"></content> </main> <footer> © 2015 OpenTable, Inc. </footer> </div> ot-site template
  62. ot-site.js .directive("otSite", function() { return { scope: {}, transclude: true,

    template: ..., link: function(scope, elem, attr, ctrl, transclude) { transclude(function(clone) { angular.forEach(clone, function(cloneEl) { var tId = ... var target = ... if (target.length) {...} else {...} }); }); } }; });
  63. ot-site.js .directive("otSite", function() { return { scope: {}, transclude: true,

    template: ..., link: function(scope, elem, attr, ctrl, transclude) { transclude(function(clone) { angular.forEach(clone, function(cloneEl) { var tId = ... var target = ... if (target.length) {...} else {...} }); }); } }; });
  64. ot-site.js .directive("otSite", function() { return { scope: {}, transclude: true,

    template: ..., link: function(scope, elem, attr, ctrl, transclude) { transclude(function(clone) { angular.forEach(clone, function(cloneEl) { var tId = ... var target = ... if (target.length) {...} else {...} }); }); } }; });
  65. ot-site.js .directive("otSite", function() { return { scope: {}, transclude: true,

    template: ..., link: function(scope, elem, attr, ctrl, transclude) { transclude(function(clone) { angular.forEach(clone, function(cloneEl) { var tId = ... var target = ... if (target.length) {...} else {...} }); }); } }; });
  66. ot-site.js .directive("otSite", function() { return { scope: {}, transclude: true,

    template: ..., link: function(scope, elem, attr, ctrl, transclude) { transclude(function(clone) { angular.forEach(clone, function(cloneEl) { var tId = ... var target = ... if (target.length) {...} else {...} }); }); } }; });
  67. ot-site.js .directive("otSite", function() { return { scope: {}, transclude: true,

    template: ..., link: function(scope, elem, attr, ctrl, transclude) { transclude(function(clone) { angular.forEach(clone, function(cloneEl) { var tId = ... var target = ... if (target.length) {...} else {...} }); }); } }; });
  68. Shadow DOM Sensible default context Class & annotation Angular 2.0

    Angular 1.3 Transclusion Manual scope One DDO
  69. ot-site.js class OtSite() { constructor () { } // public

    methods here } OtSite.annotations = [ ];
  70. ot-site.js class OtSite() { constructor () { } // public

    methods here } OtSite.annotations = [ new Component({ }) ];
  71. ot-site.js class OtSite() { constructor () { } // public

    methods here } OtSite.annotations = [ new Component({ selector: "ot-site" }) ];
  72. ot-site.js class OtSite() { constructor () { } // public

    methods here } OtSite.annotations = [ new Component({ selector: "ot-site" }), new Template({ url: "ot-site.html" }) ];
  73. ot-site.js class OtSite() { constructor () { } // public

    methods here } OtSite.annotations = [ new Component({ selector: "ot-site" }), new Template({ url: "ot-site.html" }) ];
  74. ot-site.js class OtSite() { constructor () { } // public

    methods here } @Component({ selector: "ot-site" }) @Template({ url: "ot-site.html" })
  75. class OtSite() { constructor () { } // public methods

    here } @Component({ selector: "ot-site" }) @Template({ url: "ot-site.html" }) ot-site.js
  76. @Component({ selector: "ot-site" }) @Template({ url: "ot-site.html" }) class OtSite()

    { constructor () { } // public methods here } ot-site.js
  77. <ot-site> <div head> I render in head. </div> <div menu>

    I render in menu. </div> <div body> I render in body. </div> </ot-site>