Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: templ.Join method renders multiple components into a single component #929

Merged
merged 7 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions docs/docs/03-syntax-and-usage/10-template-composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,43 @@ func main() {
</div>
```

## Joining Components

Components can be aggregated into a single Component using `templ.Join`.

```templ
a-h marked this conversation as resolved.
Show resolved Hide resolved
package main

templ hello() {
<span>hello</span>
}

templ world() {
<span>world</span>
}

templ helloWorld() {
@templ.Join(hello(), world())
}
```

```go title="main.go"
package main

import (
"context"
"os"
)

func main() {
helloWorld().Render(context.Background(), os.Stdout)
}
```

```html title="output"
<span>hello</span><span>world</span>
```

## Sharing and re-using components

Since templ components are compiled into Go functions by the `go generate` command, templ components follow the rules of Go, and are shared in exactly the same way as Go code.
Expand Down
19 changes: 19 additions & 0 deletions join.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package templ

import (
"context"
"io"
)

// Join returns a single `templ.Component` that will render provided components in order.
// If any of the components return an error the Join component will immediately return with the error.
func Join(components ...Component) Component {
return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
for _, c := range components {
if err = c.Render(ctx, w); err != nil {
return err
}
}
return nil
})
}
81 changes: 81 additions & 0 deletions join_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package templ_test

import (
"bytes"
"context"
"errors"
"io"
"testing"

"github.com/a-h/templ"
"github.com/google/go-cmp/cmp"
)

func TestJoin(t *testing.T) {
compErr := errors.New("component error")

hello := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
if _, err := io.WriteString(w, "Hello"); err != nil {
t.Fatalf("failed to write string: %v", err)
}
return nil
})
world := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
if _, err := io.WriteString(w, "World"); err != nil {
t.Fatalf("failed to write string: %v", err)
}
return nil
})
err := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
return compErr
})

tests := []struct {
name string
input []templ.Component
expectedOutput string
expectedErr error
}{
{
name: "a nil slice of components produces no output",
input: nil,
expectedOutput: "",
},
{
name: "an empty list of components produces no output",
input: []templ.Component{},
expectedOutput: "",
},
{
name: "components are rendered in order",
input: []templ.Component{hello, world},
expectedOutput: "HelloWorld",
},
{
name: "components are rendered in order, and errors returned",
input: []templ.Component{hello, err},
expectedOutput: "Hello",
expectedErr: compErr,
},
{
name: "no further components are rendered after an error",
input: []templ.Component{err, hello},
expectedOutput: "",
expectedErr: compErr,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := templ.Join(tt.input...)
b := new(bytes.Buffer)
err := got.Render(context.Background(), b)
if err != tt.expectedErr {
t.Fatalf("failed to render component: %v", err)
}
if diff := cmp.Diff(tt.expectedOutput, b.String()); diff != "" {
t.Error(diff)
}
})
}
}