Vue.js–Get started in Dart (Part 1)

Vue.js–Get started in Dart (Part 1)

I’ve been impressed with the mark that Vue.js has been making lately. Having used it in the past and wanting to use it again, I got curious about what it looks like working with Vue in Dart.

Prefer a video?

Having demonstrated that it’s possible to use JavaScript libraries in Dart web apps, we will go through the Vue.js “Getting started” page and rewrite the examples in Dart, using the js interop package.

Before we begin:

1. Set up your project

Set-up your web project quickly with Stagehand:

$ mkdir vue_dart && cd vue_dart
$ stagehand web-simple

2. Install the js interop package

Ensure that the js dependency is added to your pubspec.yaml file:

dependencies:
  js: ^0.6.1+1

Save and run pub get to update your dependencies.

3. Import the Vue.js library

In web/index.html in the <head> before <script defer src="main.dart.js"></script> import the dev version of the library:

<!-- development version, includes helpful console warnings -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

Now we can go through the examples!

Getting started

Create a web/app.dart file with our library declaration and imports:

@JS()
library vue_interop;

import 'package:js/js.dart';

// TODO: The rest of the code to go here

Declarative rendering

Here’s the first example of a template with the message property placeholder:

<div id="app">
  {{ message }}
</div>

Create an annotated factory constructor for Vue:

@JS()
class Vue {
  external factory Vue(VueOptions options);
}

The JavaScript api takes an object literal when a new Vue instance is declared. Notice the declaration of the VueOptions type instead of Map? We cannot use Dart Maps here since they are opaque in JavaScript.

Therefore, we need to create a factory constructor to house our options:

@JS()
@anonymous
class VueOptions {
  external factory VueOptions({ String el, VueDataOptions data });
  external String get el;
  external VueDataOptions get data;
}

And the data prop is a VueDataOptions object:

@JS()
@anonymous
class VueDataOptions {
  external factory VueDataOptions({
    String message = '', // Set to empty string as default
  });
  external String get message;
}

Return to web/main.dart and let's use these factories:

// Relative imports
import './app.dart';

void main() {
  Vue(VueOptions(
    el: '#app',
    data: VueDataOptions(
      message: 'Hello Vue!',
    ),
  ));
}

You should now see the text “Hello Vue!” on the screen:

In addition to string interpolation we can also bind element attributes.

<div id="app-2">
  <span v-bind:title="message">
    Hover your mouse over me for a few seconds
    to see my dynamically bound title!
  </span>
</div>

No changes needed on our factory, just declare a call:

// web/main.dart
...
void main() {
  ...

  // App 2 example
  Vue(VueOptions(
    el: '#app-2',
    data: VueDataOptions(
      message: 'You loaded this page on ${DateTime(2018).toLocal()}',
    ),
  ));
}

Conditionals

Use the v-if attribute to toggle the presence of an element:

<div id="app-3">
  <span v-if="seen">Now you see me</span>
</div>

Since we are watching a new property (seen), let’s add a getter for this in our factory:

@JS()
@anonymous
class VueDataOptions {
  external factory VueDataOptions({
    String message = '',
    bool seen = null, // <-- Added this
  });
  external String get message;
  external bool get seen; // <-- Added this
}

And in web/main.dart:

...
void main() {
  ...
  // App 3 example
  var app3 = Vue(VueOptions(
    el: '#app-3',
    data: VueDataOptions(seen: true),
  ));
}

In the snippet above we have assigned the result of calling Vue() to an app3 variable. The docs demonstrate doing app3.seen = false, which means we have to add a getter of type boolean to Vue class in web/app.dart:

@JS()
class Vue {
  external factory Vue(VueOptions options);
  external void set seen(bool val); // <-- Added this
}

And in web/main.dart, we will do:

import 'dart:async'; // <-- Added this line to use `Future.delayed`

// Relative imports
import './todo.dart'; // <-- Added this line
import './app.dart';

void main() {
  ...
  ...

  // App 3 example
  var app3 = Vue(VueOptions(
    el: '#app-3',
    data: VueDataOptions(seen: true),
  ));

  // Added a delay to see disappearing text
  Future.delayed(Duration(seconds: 2), () async {
    app3.seen = false;

    // Added a delay and then restored text visibility
    await Future.delayed(Duration(seconds: 2));
    app3.seen = true;
  });
}

Loops

The v:for attribute is used when iterating over arrays:

<div id="app-4">
  <ol>
    <li v-for="todo in todos">
      {{ todo.text }}
    </li>
  </ol>
</div>

This introduces a new factory constructor, which we’ll call Todo.

Create web/todo.dart with our factory class:

@JS()
library todo;

import 'package:js/js.dart';

@JS()
@anonymous
class Todo {
  external factory Todo({String text});
  external String get text;
}

And in web/app.dart, let’s define a list of Todos:

@JS()
class Vue {
  external factory Vue(VueOptions options);
  external void set seen(bool val);
  external List<Todo> get todos; // <-- Added this line
}

...

@JS()
@anonymous
class VueDataOptions {
  external factory VueDataOptions({
    String message = '',
    bool seen = null,
    List<Todo> todos = const [],
  });
  external String get message;
  external bool get seen;
  external List<Todo> get todos; // <-- Added this line
}

And in web/main.dart we’ll use it:

...
...
void main() {
  ...
  ...

  // App 4 example
  var app4 = Vue(VueOptions(
    el: '#app-4',
    data: VueDataOptions(todos: [
      Todo(text: 'Learn Dart'),
      Todo(text: 'Learn Aqueduct'),
      Todo(text: 'Build something awesome!'),
    ]),
  ));
}

In order to add a new item to the todos list like the docs show:

app4.todos.push({ text: 'New item' });

We’ll need to add a getter for todos on Vue:

// web/app.dart
...
...

@JS()
class Vue {
  external factory Vue(VueOptions options);
  external void set seen(bool val);
  external List<Todo> get todos; // <-- Added this line
}

And in web/main.dart:

...
...
  // App 4 example
  var app4 = Vue(VueOptions(
    el: '#app-4',
    data: VueDataOptions(todos: [
      Todo(text: 'Learn Dart'),
      Todo(text: 'Learn Aqueduct'),
      Todo(text: 'Build something awesome!'),
    ]),
  ));

  app4.todos.add(Todo(text: 'New item')); // <-- Added this line

Conclusion

And this brings us to the end of Part 1. In part 2, we will look at handling user input and composition with Vue components.

Further reading

  1. js package

  2. How to Use JavaScript libraries in your Dart applications

  3. Full-stack web development with Dart