$whoami

Michał Staśkiewicz

@mikoscz

@staskiewiczmiko

@mikoscz

Building extendable boxes

Higher order components

Agenda

  1. Higher order functions/components
  2. Extending component in a lame way
  3. Higher Order pattern to the rescue

Higher order functions

const createAdder = (incBy) => {
  return (number) => number + incBy;
};


const incByFour = createAdder(4);
incByFour(10); // => 14

const incBySix = createAdder(6);
incByFour(4); // => 10

Higher Order Function

Function (takes some args) -> Function

Higher Order Component

Component (takes some args) -> Component

Can component return value?

{{yield args}}

almost like functions

// components/custom-button.hbs

<button class="my-custm-button">
  {{yield "Click me"}}
</button>

// Usage

{{#custom-button as |value|}}
  {{value}}
{{/custom-button}}


Example return by yielding

"I want new feature"

Users profile box - everywhere

// user-profile/template.hbs

{{#if showAvatar}}
  {{user-avatar img=user.avatarUrl size=avatarSize}}
{{/if}}

{{#if showName}}
  {{user-name name=user.name format=nameFormat}}
{{/if}}

// Usage

{{user-profile
  user=user
  showAvatar=true
  avatarSize="big"
  showName=true
  nameFormat="long}}

Mess in the air

// user-profile/template.hbs

{{#if showAvatar}}
  {{user-avatar img=user.avatarUrl size=avatarSize}}
{{/if}}

{{#if showName}}
  {{user-name name=user.name format=nameFormat}}
{{/if}}

// user-profile/component.js

export default Component.extend({
  showName: true,
  showAvatar: true,
  avatarSize: 'big',
  nameFormat: 'long'
});

Trust me I'll refactor this

{{user-profile user=user}}
{{#if showAvatar}}
  {{user-avatar img=user.avatarUrl size=avatarSize}}
{{/if}}

{{yield}}

{{#if showName}}
  {{user-name name=user.name format=nameFormat}}
{{/if}}

export default Component.extend({
  showName: true,
  showAvatar: true,
  avatarSize: 'big',
  nameFormat: 'long'
});

{{#user-profile user=user}}
  Some additional info
{{/user-profile}}

Infinity crap

How about nope

Let's use Higher Order

{{component}}

{{component 'custom-button'}}
// user-profile/template.hbs

{{yield (component 'user-avatar' img=user.avatarUrl size="big")
        (component 'user-name' name=user.name format="long")}}

// Place where we're using component

{{#user-profile user=user as |avatar fullName|}}
  {{component fullName}}
  {{component avatar}}
{{/user-profile}}

More complex example

// user-profile/template.hbs

{{yield (component 'user-avatar' img=user.avatarUrl size="big")
        (component 'user-name' name=user.name format="long")}}

// Place where we're using component

{{#user-profile user=user as |avatar fullName|}}
  {{component fullName format="short"}}
  {{component avatar size="small"}}
{{/user-profile}}

Overwriting defaults

// user-profile/template.hbs

{{yield (hash
  avatar=(component 'user-avatar' img=user.avatarUrl size="big")
  name=(component 'user-name' name=user.name format="long"))}}

// Place where we're using component

{{#user-profile user=user as |profile|}}
  {{component profile.name}}
  {{component profile.avatar}}
{{/user-profile}}

Using hash helper

// user-profile/template.hbs

{{yield (hash
  avatar=(component 'user-avatar' img=user.avatarUrl size="big")
  name=(component 'user-name' name=user.name format="long"))}}

// Place where we're using component

{{#user-profile user=user as |profile|}}
  {{profile.name}}
  {{profile.avatar}}
{{/user-profile}}

Shorthand syntax

// user-profile/template.hbs

{{yield (hash
  avatar=(component 'user-avatar' img=user.avatarUrl size="big")
  name=(component 'user-name' name=user.name format="long"))}}

// Place where we're using component

{{#user-profile user=user as |profile|}}
  {{#profile.name as |name|}}
    {{name}}
    <p>Some additional information</p>
  {{/profile.name}}
  {{profile.avatar}}
{{/user-profile}}

Flexibility

Do I need to remember the API of a component?

// user-profile/template.hbs

{{#if hasBlock}}
  {{yield (hash
    avatar=(component 'user-avatar' img=user.avatarUrl size="big")
    name=(component 'user-name' name=user.name format="long"))}}
{{else}}
  {{user-avatar img=user.avatarUrl size="big"}}
  {{user-name name=user.name format="long"}}
{{/if}}

// Place where we're using component

{{user-profile user=user}}

Default behaviour

More examples

  • https://github.com/cibernox/ember-power-select
  • https://github.com/cibernox/ember-power-datepicker
  • https://github.com/kaliber5/ember-bootstrap
  • https://github.com/miguelcobain/ember-paper

Thanks!

@mikoscz

@staskiewiczmiko

@mikoscz

Higher order components

By Michał Staśkiewicz

Higher order components

  • 669