<commit_before># Components

<aside>

New to Ember or the Octane edition specifically? You may want to read [the Ember Guides’ material on `Component`s][guides-component] first!

</aside>


Glimmer Components are defined in one of three ways: with templates only, with a template and a backing class, or with only a backing class (i.e. a `yield`-only component). When using a backing class, you get a first-class experience using TypeScript! Unfortunately, we don’t yet support type-checking for templates, but we hope to build that out eventually. Don’t let that stop you, though: types in your component classes make for a great experience, so let’s dig in and see how it works in practice.

## A simple component

A *very* simple Glimmer component which lets you change the count of a value might look like this:

<DocsSnippet @name='up-and-down.hbs' @title='my-app/components/up-and-down.hbs' @showCopy={{true}} @language='hbs'>
  <button {{on "click" this.minus}}>&minus;</button>
  {{this.count}}
  <button {{on "click" this.plus}}>+</button>
</DocsSnippet>

<DocsSnippet @name='up-and-down.ts' @title='my-app/components/up-and-down.ts' @showCopy={{true}} @language='ts'>
  import Component from '@glimmer/component';
  import { tracked } from '@glimmer/tracking'
  import { action } from '@ember/object';

  export default class Counter extends Component {
    @tracked count = 0;
    
    @action plus() {
      this.count += 1;
    }
    
    @action minus() {
      this.count -= 1;
    }
  }
</DocsSnippet>

Notice that there are no type declarations here – but this *is* actually a well-typed component. The type of `count` is `number`, and if we accidentally wrote something like `this.count = "hello"` the compiler would give us an error.

## A component with arguments

So far so good, but of course most components aren’t quite this simple! Instead, they’re invoked by other templates and they can invoke other components themselves in their own templates.

Glimmer components can receive both *arguments* and *attributes* when they are invoked. When you are working with a component’s backing class, you have access to the arguments but *not* to the attributes. The arguments are passed to the constructor, and then available as `this.args` on the component instance afterward. Let’s imagine a component which just logs the names of its arguments when it is first constructed:

<DocsSnippet @name='args-display.ts' @title='my-app/components/args-display.ts' @showCopy={{true}} @language='ts'>
  import Component from '@glimmer/component';

  export default class ArgsDisplay extends Component {
    constructor(owner: unknown, args: {}) {
      super(owner, args);

      Object.keys(args).forEach(argName => {
        console.log(argName);
      });
    }
  }
</DocsSnippet>

<aside>

If you’re used to the classic Ember Object model, there are two important differences in the constructor itself:

- we use `super` instead of `this._super`
- we *must* call `super` before we do anything else with `this`

</aside>

Notice that we have to start by calling `super` with `owner` and `args`. This may be a bit different from what you’re used to in Ember or other frameworks, but is normal for sub-classes in TypeScript. If the compiler just accepted any `...arguments`, a lot of potentially *very* unsafe invocations would go through. So, instead of using `...arguments`, we explicitly pass the *specific* arguments and make sure their types match up with what the super-class expects.

The types for `owner` here and `args` line up with what the `constructor` for Glimmer components expect. The `owner` is specified as `unknown` because this is a detail we explicitly *don’t* need to know about. The `args` are `{}` because a Glimmer component *always* receives an object containing its arguments, even if the caller didn’t pass anything: then it would just be an empty object.

<aside><commit_msg>docs: clarify `...arguments` unsafety<commit_after># Components

<aside>

New to Ember or the Octane edition specifically? You may want to read [the Ember Guides’ material on `Component`s][guides-component] first!

</aside>


Glimmer Components are defined in one of three ways: with templates only, with a template and a backing class, or with only a backing class (i.e. a `yield`-only component). When using a backing class, you get a first-class experience using TypeScript! Unfortunately, we don’t yet support type-checking for templates, but we hope to build that out eventually. Don’t let that stop you, though: types in your component classes make for a great experience, so let’s dig in and see how it works in practice.

## A simple component

A *very* simple Glimmer component which lets you change the count of a value might look like this:

<DocsSnippet @name='up-and-down.hbs' @title='my-app/components/up-and-down.hbs' @showCopy={{true}} @language='hbs'>
  <button {{on "click" this.minus}}>&minus;</button>
  {{this.count}}
  <button {{on "click" this.plus}}>+</button>
</DocsSnippet>

<DocsSnippet @name='up-and-down.ts' @title='my-app/components/up-and-down.ts' @showCopy={{true}} @language='ts'>
  import Component from '@glimmer/component';
  import { tracked } from '@glimmer/tracking'
  import { action } from '@ember/object';

  export default class Counter extends Component {
    @tracked count = 0;
    
    @action plus() {
      this.count += 1;
    }
    
    @action minus() {
      this.count -= 1;
    }
  }
</DocsSnippet>

Notice that there are no type declarations here – but this *is* actually a well-typed component. The type of `count` is `number`, and if we accidentally wrote something like `this.count = "hello"` the compiler would give us an error.

## A component with arguments

So far so good, but of course most components aren’t quite this simple! Instead, they’re invoked by other templates and they can invoke other components themselves in their own templates.

Glimmer components can receive both *arguments* and *attributes* when they are invoked. When you are working with a component’s backing class, you have access to the arguments but *not* to the attributes. The arguments are passed to the constructor, and then available as `this.args` on the component instance afterward. Let’s imagine a component which just logs the names of its arguments when it is first constructed:

<DocsSnippet @name='args-display.ts' @title='my-app/components/args-display.ts' @showCopy={{true}} @language='ts'>
  import Component from '@glimmer/component';

  export default class ArgsDisplay extends Component {
    constructor(owner: unknown, args: {}) {
      super(owner, args);

      Object.keys(args).forEach(argName => {
        console.log(argName);
      });
    }
  }
</DocsSnippet>

<aside>

If you’re used to the classic Ember Object model, there are two important differences in the constructor itself:

- we use `super` instead of `this._super`
- we *must* call `super` before we do anything else with `this`

</aside>

Notice that we have to start by calling `super` with `owner` and `args`. This may be a bit different from what you’re used to in Ember or other frameworks, but is normal for sub-classes in TypeScript today. If the compiler just accepted any `...arguments`, a lot of potentially *very* unsafe invocations would go through. So, instead of using `...arguments`, we explicitly pass the *specific* arguments and make sure their types match up with what the super-class expects.

<aside>

This might change in the future! If TypeScript eventually adds [support for “variadic kinds”](https://github.com/Microsoft/TypeScript/issues/5453), using `...arguments` could become safe.

</aside>

The types for `owner` here and `args` line up with what the `constructor` for Glimmer components expect. The `owner` is specified as `unknown` because this is a detail we explicitly *don’t* need to know about. The `args` are `{}` because a Glimmer component *always* receives an object containing its arguments, even if the caller didn’t pass anything: then it would just be an empty object.

<aside>
{'fraction_non_alphanumeric': 0.09682618398214729, 'fraction_numerical': 0.001239771881973717, 'mean_word_length': 4.245123537061119, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 37, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5557590', 'n_tokens_mistral': 2234, 'n_tokens_neox': 2196, 'n_words': 1060}