loading...
Cover image for Vue.js–Getting started in Dart (Part 1)

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

graphicbeacon profile image Jermaine Oppong Updated on ・5 min read

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.

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.


Watch on Youtube


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 lets 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.

As always, I hope this was insightful and you learned something new today.

Subscribe to my YouTube channel for the latest videos on Dart. Thanks!

Like, share and follow me 😍 for more content on Dart.

Further reading

  1. js package
  2. How to Use JavaScript libraries in your Dart applications
  3. Full-stack web development with Dart

Posted on by:

graphicbeacon profile

Jermaine Oppong

@graphicbeacon

Web Developer | Google Dart enthusiast | Egghead.io instructor | YouTuber 📹 | Read my blog ✍🏾 https://www.creativebracket.com

Discussion

pic
Editor guide
 

Nice article! Only one question. Why do you want to use Dart instead of plain JS or TS?
Why do you want to adapt that language instead of one with larger community?

 

Hey Kovács, thanks for your question.

The pull Dart has on me is the fact that as a statically-typed language from the get-go, it also comes with tooling that has made me productive working in it. Dart shines when building applications that can be made sense of while it grows.

I’ve found myself working on large JS projects and I’ve struggled to understand it, due to the fact that JavaScript as a dynamically-typed language means that working out the flow of data and the structure of that data requires more mental effort. I've also struggled to stay ahead of type-related errors on larger projects. The amount of times I’ve right clicked a method in VS Code and used the “Go to definition” command to be met with no feedback, still annoys me today.

TypeScript, however is a good effort by Microsoft to address this concern. My only qualms with this is that TS code I’ve seen looks more like C# rather than JavaScript, especially when you include namespaces and interfaces, so it can be difficult to grasp at times. I would have loved to have seen TS stand on it’s own two feet, rather than be tied to the JS ecosystem. Ryan Dahl seems to be looking into that with his Deno project. This tells me that Dart may have been on the right track by not tying itself too much to the JS ecosystem. I haven't really done much with Flow, so can't comment on that.

This series with Vue.js is an investigation using the js package. I'm not selling any silver bullets. My ideal framework of choice for SPA development would be AngularDart, although I still need to look into it :)

This is beginning to feel like an essay, so I'll leave it at that.