Playing Next Lesson In
seconds

Let's Learn AdonisJS 7 #2.1

View State & Passing Data to Views

In This Lesson

Learn how to pass data from controllers to views using state in AdonisJS. We'll also discuss the three different types of state in EdgeJS: render, local, and global.

Created by
@tomgobich
Published

Every view comes with stateful information housed on the renderer instance for us to utilize and display. State is how we'll pass information from our route handlers into the page itself. By default, AdonisJS provides many utilities via this state, and we can inspect it in a couple of different ways.

Inspecting State

First, one of these utilities is an inspect method which will plop the contents of its argument on our page. To access it, we can use EdgeJS's interpolation, initiated with double-curly braces.

@layout()

  <h1>Challenges</h1>

  {{ inspect(state) }}

@end
Copied!
  • resources
  • views
  • pages
  • challenges
  • index.edge

Our state is housed in a variable called state. With the above, if we visit our page now, we'll see a list of all existing state utilities.

Although we can access state using the state variable, it's also directly accessible as well. So, as you can see inspect is listed as an item in our state, but we didn't need to do state.inspect() to use it.

Dumping State

Now, inspect is fantastic for simple things, but when inspecting a lot, it can be a bit much to weed through. An alternative option is to dump state. This, like inspect will plop what we're inspecting on the page, but this will instead make it collapsible so we can dig into specific properties as needed, making it easier to digest large items.

@layout()

  <h1>Challenges</h1>

  @dump(state)

@end
Copied!
  • resources
  • views
  • pages
  • challenges
  • index.edge

Dump is what is referred to as a tag in EdgeJS, similar to how we're using our layout. Tags don't require interpolation to be accessed like traditional state; instead, they can be accessed by prefixing them with an at symbol (@). They must also be on a line of their own.

Passing Data to Views

Great, now when rendering inside our route handlers, we can pass additional state as the second argument to the render method. So, if we wanted to pass a list of challenges into the view, we can define the list and pass it in via an object as the second argument. This object will then get merged with the pre-existing state and be made accessible within our view.


router.get('/challenges', async (ctx) => {
  const challenges = [
    { id: 1, text: 'Learn AdonisJS', points: 10 },
    { id: 2, text: 'Learn EdgeJS', points: 5 },
    { id: 3, text: 'Build an AdonisJS app', points: 20 },
  ]

  return ctx.view.render('pages/challenges/index', { challenges })
})
Copied!
  • start
  • routes.ts

Now, within our dumped output, we should see challenges as one of the items in our state!

The 3 Types of State in EdgeJS

Within EdgeJS, there are three different levels of state.

  • Render state - which we're using above, and is only available on the direct page being rendered

  • Local state - shared data accessible throughout the individual request's view instance

  • Global state - shared data accessible across all request view instances

To demonstrate this, let's switch our dump to an inspection of just our challenges. Additionally, let's also inspect our challenges from our layout component.

@layout()

  <h1>Challenges</h1>

  {{ inspect({ view: challenges }) }}

@end
Copied!
  • resources
  • views
  • pages
  • challenges
  • index.edge
<!DOCTYPE html>
<html lang="en-us">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>
      AdonisJS - A fully featured web framework for Node.js
    </title>
    @vite(['resources/css/app.css', 'resources/js/app.js'])
    @stack('dumper')
  </head>
  <body>
    @include('partials/header')
    
    {{ inspect({ layout: challenges }) }}

    <main>
      @include('partials/flash_alerts')
      {{{ await $slots.main() }}}
    </main>
  </body>
</html>
Copied!
  • resources
  • views
  • components
  • layout.edge

With this, we can see our render state in action. It's accessible only in the direct page being rendered, not within our component's state. If, however, we switched this from render state to local state, we'll see that it's available in both.

router.get('/challenges', async (ctx) => {
  const challenges = [
    { id: 1, text: 'Learn AdonisJS', points: 10 },
    { id: 2, text: 'Learn EdgeJS', points: 5 },
    { id: 3, text: 'Build an AdonisJS app', points: 20 },
  ]

  ctx.view.share({ challenges })

  return ctx.view.render('pages/challenges/index')
})
Copied!
  • start
  • routes.ts

The share method on our HttpContext's view is how we can add local state. This is helpful for:

  • Adding request-specific state in or outside of the route handler

  • Adding request-specific globals that are deeply accessible within our views

Global State

Unlike render and local state, global state is not request-specific. It's great for adding helpers or general things we'll need throughout EdgeJS. To add a global, we'll first want to create a new preload file so we can register the global prior to our application starting.

node ace make:preload globals
# ❯ Do you want to register the preload file in .adonisrc.ts file? (y/N) › true
# DONE:    create start/globals.ts
# DONE:    update adonisrc.ts file
Copied!

When asked if we want to register the preload file, hit yes! This hooks the preload file into our app's lifecycle by adding it to the adonisrc.ts file's preloads array.

In addition to updating our adonisrc.ts file this will also give us a start/globals.ts file to define our globals within.

import edge from 'edge.js'

edge.global('appName', 'Lets Learn AdonisJS 7')
Copied!
  • start
  • globals.ts

To define a global, we just need to import edge from 'edge.js' and call the global method. The first argument is the property name, and the second is the value. The value could be a simple primitive, a function, or anything we need. Once set, the value is globally accessible across all rendered views in our application, so definitely don't share user-specific info this way! This is great for helpers and the like.

@layout()

  <h1>Challenges</h1>

  {{ inspect({ view: challenges, appName }) }}

@end
Copied!
  • resources
  • views
  • pages
  • challenges
  • index.edge
<!DOCTYPE html>
<html lang="en-us">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>
      AdonisJS - A fully featured web framework for Node.js
    </title>
    @vite(['resources/css/app.css', 'resources/js/app.js'])
    @stack('dumper')
  </head>
  <body>
    @include('partials/header')
    
    {{ inspect({ layout: challenges, appName }) }}

    <main>
      @include('partials/flash_alerts')
      {{{ await $slots.main() }}}
    </main>
  </body>
</html>
Copied!
  • resources
  • views
  • components
  • layout.edge

Okay, I'm going to remove these inspects and change our challenges back to render state.

router.get('/challenges', async (ctx) => {
  const challenges = [
    { id: 1, text: 'Learn AdonisJS', points: 10 },
    { id: 2, text: 'Learn EdgeJS', points: 5 },
    { id: 3, text: 'Build an AdonisJS app', points: 20 },
  ]

  return ctx.view.render('pages/challenges/index', { challenges })
})
Copied!
  • start
  • routes.ts

Join the Discussion 0 comments

Create a free account to join in on the discussion
robot comment bubble

Be the first to comment!