In a Go template range loop, are variables declared outside the loop reset on each iteration?

Note: Go 1.11 will support modifying template variables via assignment. This will be valid code:

{{ $v := "init" }}
{{ if true }}
  {{ $v = "changed" }}
{{ end }}
v: {{ $v }} {{/* "changed" */}}

Original answer pre-dating Go 1.11 follows:


Variables are not reset. Basically what happens is that you redeclare the $prevDate variable inside the loop. But it is only in scope after the redeclaration and before the closing {{end}} tag of the {{range}}. So when the next iteraiton of the loop comes, you only see the “outer” variable which you haven’t changed (because you created a new).

You can’t change the values of the template variables you create.

What you can do is for example use the following range form:

{{ range $index, $post := .Posts }}

And…

Solution #1: with a registered Function

And you can register a function for the template (see template.Funcs()) to which you can pass the $index and it would return the date field of the previous element (at $index -1).

It would look something like this:

func PrevDate(i int) string {
    if i == 0 {
        return ""
    }
    return posts[i-1].Date
}

// Registering it:
var yourTempl = template.Must(template.New("").
    Funcs(map[string]interface{}{"PrevDate": PrevDate}).
    Parse(yourStringTemplate))

And from your template you can call it like:

{{range $index, $post := .Posts}}
    {{$prevDate := PrevDate $index}}
{{end}}

Solution #2: with a Method of Posts

This solution is analog but is even simpler: add a method to your Posts and you can call it directly. No need to register a function.

For example:

type Post struct {
    // Your Post type
    Date string
}

type Posts []Post

func (p *Posts) PrevDate(i int) string {
    if i == 0 {
        return ""
    }
    return (*p)[i-1].Date
}

And from your template you can call it like:

{{range $index, $post := .Posts}}
    {{$prevDate := $.Posts.PrevDate $index}}
{{end}}

Leave a Comment