Creating GUI container components in Angular and Web Components

Post on 09-Jan-2017

296 views 1 download

Transcript of Creating GUI container components in Angular and Web Components

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.

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/