Upload
rachael-l-moore
View
300
Download
1
Embed Size (px)
Citation preview
Creating containercomponentsin Web Components and Angularng-conf: March 5, 2015
Kara EricksonWeb Engineer kara karaforthewin
Rachael L MooreUI Engineer morewry morewry
Roadmap
Web Components
Angular 1.3
Angular 2.0
<!-- #include virtual="head.html" -->
<!-- #include virtual="menu.html" -->
I render in body.
<!-- #include virtual="foot.html" -->
Server Side Includes
{{> head}}
{{> menu}}
I render in body.
{{> foot}}
mustache
UI Components
<ot-site>
I render in body.
</ot-site>
Native Elements
<input type="range" />
<input type="range" min="1"
max="8" />
<div id="site">
</div>
Component Development
<div id="site">
<header></header>
</div>
Head
Component Development
<div id="site">
<header></header>
<nav></nav>>
</div>
Component Development
Menu
<div id="site">
<header></header>
<nav></nav>
<main></main>
</div>
Component Development
Body
<div id="site">
<header></header>
<nav></nav>
<main></main>
</div>
Component Development
<div id="site">
<header>
<svg id="logo"></svg>
</header>
<nav></nav>
<main></main>
</div>
Component Development
Body
<div id="site">
<header>
<svg id="logo"></svg>
</header>
<nav></nav>
<main></main>
<footer>
© 2015 OpenTable, Inc.
</footer>
</div>
Component Development
<div id="site">
<header>
<svg id="logo"></svg>
</header>
<nav></nav>
<main></main>
<footer>
© 2015 OpenTable, Inc.
</footer>
</div>
Component Development
<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
Component Use
<ot-site>
</ot-site>
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.
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.
<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
Head
Menu
Body
Web Components
Shadow DOM
<ot-site>
Native Elements
<input type="range" />
Secrets and Shadows
Shadow DOM
Light DOM
Shadow DOM
Light DOM
<input type="range" />
<input type="range" />
#shadow-root (user-agent)
<div pseudo="track" id="track">
<div pseudo="thumb" id="thumb">
</div>
</div>
UA Shadow DOM
...had flourished.
<span id="myspan">
Long ago there was something in me, but now that thing is gone.
</span>
I cannot...
Shadow Host
var $ = document.querySelector.bind(document)
var span = $("#myspan")
span.createShadowRoot()
Shadow Root
...had flourished.
<span id="myspan">
Long ago there was something in me, but now that thing is gone.
</span>
I cannot...
Shadow Host
...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
Before
...had flourished. Long
ago there was something
in me, but now that thing
is gone. I cannot...
After
...had flourished. I
cannot...
var host = $("ot-site")
host.createShadowRoot()
host.shadowRoot.innerHTML = ``
ot-site.js
host.shadowRoot.innerHTML = .``.
ot-site.js
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
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
host.shadowRoot.innerHTML += `
<style>
/* use your imagination */
</style>
`
ot-site.js
<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
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
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
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
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
<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
<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
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
Light DOMShadow 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>
<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 DOMShadow 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>
I render in head.
I render in menu.
I render in body.
I render in head.
I render in menu.
I render in body.
Code Demo: Content Projection
Angular 1.3
Transcluding Directive
<ot-site>
.directive("otSite", function() { return { template: `` };});
ot-site.js
.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
Transclusion
<ot-site>
</ot-site>
index.html
<ot-site>
<div>
I render in head.
</div>
<div>
I render in menu.
</div>
<div>
I render in body.
</div>
</ot-site>
index.html
<ot-site>
<div>
I render in head.
</div>
<div>
I render in menu.
</div>
<div>
I render in body.
</div>
</ot-site>
index.html
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>`
};
});
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>`
};
});
<ot-site>
<div>
I render in head.
</div>
<div>
I render in menu.
</div>
<div>
I render in body.
</div>
</ot-site>
DOM
<ot-site>
<div>
I render in head.
</div>
<div>
I render in menu.
</div>
<div>
I render in body.
</div>
</ot-site>
DOM
DOM
<ot-site>
<div id="site">
<header>
<svg id="logo"></svg>
</header>
<nav></nav>
<main></main>
<footer>©</footer>
</div>
</ot-site>
<ot-site>
<div>
I render in head.
</div>
<div>
I render in menu.
</div>
<div>
I render in body.
</div>
</ot-site>
DOM
<ot-site>
<div>
I render in head.
</div>
<div>
I render in menu.
</div>
<div>
I render in body.
</div>
</ot-site>
Clone DOM
<ot-site>
<div>
I render in head.
</div>
<div>
I render in menu.
</div>
<div>
I render in body.
</div>
</ot-site>
Clone DOM
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>
<div>
I render in head.
</div>
<div>
I render in menu.
</div>
<div>
I render in body.
</div>
<ot-site>
</ot-site>
Clone DOM
<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
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>`
};
});
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>`
};
});
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>`
};
});
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>`
};
});
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>`
};
});
<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
<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
<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>
DOMClone
<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>
DOMClone
angular.forEach(clone, function(cloneEl) {});
<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>
DOMClone
angular.forEach(clone, function(cloneEl) {});
<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>
DOMClone
angular.forEach(clone, function(cloneEl) {});
<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>
DOMClone
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>
DOMClone
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>
<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>
DOMClone
target.append(clone);
<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>
DOMClone
<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);
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
Transclude functiontransclude(function(clone) {
# DOM manipulation
});
Transclude function access pointscompile: function(tElem, tAttrs, transclude)
controller: function($scope, $element, $transclude)
link: function(scope, iElem, iAttrs, ctrl, transclude)
compile: function(tElem, tAttrs, transclude)
controller: function($scope, $element, $transclude)
link: function(scope, iElem, iAttrs, ctrl, transclude)
Transclude function access points
compile: function(tElem, tAttrs, transclude)
controller: function($scope, $element, $transclude)
link: function(scope, iElem, iAttrs, ctrl, transclude)
Transclude function access points
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
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
Transclude function availabilitycompile: function(tElem, tAttrs, transclude)
controller: function($scope, $element, $transclude)
link: function(scope, iElem, iAttrs, ctrl, transclude)
ot-site.js
.directive("otSite", function() { return {
transclude: true,
template: ...,
link: function(scope, elem, attr, ctrl, transclude) {
}
};
});
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 {...}
});
});
}
};
});
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 {...}
});
});
}
};
});
transcluding directive with isolate scope
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 {...}
});
});
}
};
});
Angular 2.0
Component Directive
<ot-site>
Angular 1.3
Transclusion
Angular 2.0
Shadow DOM
Angular 2.0Angular 1.3
Transclusion
<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
<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
<div id="site"><header>
<svg id="logo"></svg>
</header>
<nav></nav>
<main></main>
<footer> © 2015 OpenTable, Inc. </footer>
</div>
ot-site template
<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
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 {...}
});
});
}
};
});
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 {...}
});
});
}
};
});
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 {...}
});
});
}
};
});
Shadow DOM
Angular 2.0Angular 1.3
Transclusion
Manual scope
proper transclusion scope
Transclusion
Manual scope
Shadow DOM
Sensible default context
Angular 2.0Angular 1.3
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 {...}
});
});
}
};
});
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 {...}
});
});
}
};
});
ot-site.js
.directive("otSite", function() { return {
template: ...
};
});
.directive(."otSite"., function() { return {
template: ...
};
});
ot-site.js
ot-site.js
.directive(."otSite"., function() { return {
.template: ....
};
});
Shadow DOM
Sensible default context
Angular 2.0Angular 1.3
Transclusion
Manual scope
One DDO
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 {...}
});
});
}
};
});
Shadow DOM
Sensible default context
Class & annotation
Angular 2.0Angular 1.3
Transclusion
Manual scope
One DDO
class OtSite() { constructor () {
}
// public methods here
}
ot-site.js
ot-site.js
class OtSite() { constructor () {
}
// public methods here
}
OtSite.annotations = [
];
ot-site.js
class OtSite() { constructor () {
}
// public methods here
}
OtSite.annotations = [
new Component({
})
];
ot-site.js
class OtSite() { constructor () {
}
// public methods here
}
OtSite.annotations = [
new Component({
selector: "ot-site"
})
];
ot-site.js
class OtSite() { constructor () {
}
// public methods here
}
OtSite.annotations = [
new Component({
selector: "ot-site"
}),
new Template({
url: "ot-site.html"
})
];
ot-site.js
class OtSite() { constructor () {
}
// public methods here
}
OtSite.annotations = [
new Component({
selector: "ot-site"
}),
new Template({
url: "ot-site.html"
})
];
ot-site.js
class OtSite() { constructor () {
}
// public methods here
}
@Component({
selector: "ot-site"
})
@Template({
url: "ot-site.html"
})
class OtSite() { constructor () {
}
// public methods here
}
@Component({
selector: "ot-site"
})
@Template({
url: "ot-site.html"
})
ot-site.js
@Component({
selector: "ot-site"
})
@Template({
url: "ot-site.html"
})
class OtSite() { constructor () {
}
// public methods here
}
ot-site.js
ot-site.js
@Component({
selector: "ot-site"
})
@Template({
url: "ot-site.html"
})
class OtSite() { constructor () {
}
// public methods here
}
<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>
Thanks, everyone!
Kara EricksonWeb Engineer kara karaforthewin
Rachael L MooreUI Engineer morewry morewry
We’re hiring!Visit our careers page atopentable.com/careers/
We’re hiring!Visit our careers page atopentable.com/careers/