DEV Community

Cover image for How to use JavaScript libraries in your Dart applications
Jermaine
Jermaine

Posted on • Updated on • Originally published at creativebracket.com

How to use JavaScript libraries in your Dart applications

Ever experienced the fear of leaving your favourite JS libraries behind should you include Dart with your dev stack? Well fear no more, because the Dart team have provided a means of incorporating your favourite JavaScript libraries!

The solution comes in the form of the js package, which provides a proxy to invoke JavaScript API methods. In this article, we will explore some examples with the window object, and a real-world example with jQuery.

Here's the source code containing the solution.

Prerequisite

Before we start lets use stagehand to scaffold our web project:

stagehand web-simple
Enter fullscreen mode Exit fullscreen mode

Add the following in pubspec.yaml under dependencies:

dependencies:
  js: ^0.6.0
Enter fullscreen mode Exit fullscreen mode

And run pub get. Also ensure you have webdev installed (pub global activate webdev)

Now we're ready to jump into the examples!


Example with the window object

Installing the js package exposes the @JS() annotation as a means of accessing JavaScript APIs on the window host object.

Here's a snippet to use in web/main.dart to invoke window.console.log():

@JS() // Sets the context, which in this case is `window`
library main; // required library declaration called main, or whatever name you wish

import 'package:js/js.dart'; // Pull in our dependency

@JS('console.log') // This marks the annotated function as a call to `console.log`
external void log(dynamic str);

void main() {
  log('Hello world!'); // calling console.log() in JavaScript land
}
Enter fullscreen mode Exit fullscreen mode

Run webdev serve and visit the localhost url to see the output. To see updates just save the file and reload the page!

The annotated file must begin with a library declaration that also has the @JS() annotation, which we see at lines 1–2. Since the first annotation does not have an argument, it sets the context of the other annotations relative to the window object. So getting to this line @JS('console.log') details a traversal from window to the console property which has the log method.

Here's another example setting the context to window.console:

@JS('console') // Our `console` namespace
library main;

import 'package:js/js.dart';

@JS('log') // Setting the proxy to the `console.log` method
external void log(dynamic str);

void main() {
  log('Hello worlddd!');
}
Enter fullscreen mode Exit fullscreen mode

Since the file starts with the console namespace, the next annotation for the log method excludes the console prefix. The external keyword for the log method is used to mark this method outside of Dart, or else a function body is expected. Furthermore, because our function is named with the same name as the method on console, we can remove the annotation above entirely.

// @JS('log') // remove
external void log(dynamic str); 
Enter fullscreen mode Exit fullscreen mode

Please note: You do not need this interop package if you only need to access inbuilt properties on window. Use Dart's dart:html library to do this. The snippet above is just for illustration purposes therefore the js package comes into play when using external libraries.

Real-world example with jQuery

In order to use jQuery, let's import it in web/index.html before the script tag requesting main.dart.js:

<script defer src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Enter fullscreen mode Exit fullscreen mode

Now, create a file named web/jquery.dart containing the snippet below:

@JS()
library jquery;

import 'package:js/js.dart';

// new jQuery() invokes JavaScript `new jQuery()`
@JS()
class jQuery {
  external factory jQuery(String selector);
  external int get length; // We get this from the jQuery instance
}
Enter fullscreen mode Exit fullscreen mode

Let's refactor main.dart to test this:

import './jquery.dart';

void main() {
  print(jQuery('#output')); // [object Object]
  print(jQuery('#output').length); // 1
}
Enter fullscreen mode Exit fullscreen mode

Let's do something a bit more intriguing by using the css() and animate() methods:

@JS()
class jQuery {
  external factory jQuery(String selector);
  external int get length;
  external jQuery css(Map options);
  external jQuery animate(Map options);
}
Enter fullscreen mode Exit fullscreen mode

Calling both methods will return the jQuery instance the same as the JS-based API does.

Now this won't work as expected because the options parameters are expecting a Map type. We cannot pass a Dart Map object because they are "opaque" in JavaScript. In other words you'll get an object not containing what you expect it to contain.

To get this working, we need to define a factory constructor with the keys we'll need:

@JS()
@anonymous // This annotation is needed along with the unnamed factory constructor
class CssOptions {
  external factory CssOptions({ backgroundColor, height, position, width });
  external String get backgroundColor;
  external String get position;
  external num get height;
  external num get width;
}
Enter fullscreen mode Exit fullscreen mode

And amend the css() external method declaration as follows:

external jQuery css(CssOptions options);
Enter fullscreen mode Exit fullscreen mode

Let's do the same for the animate method:

@JS()
@anonymous
class AnimateOptions {
  external factory AnimateOptions({left, top});
  external dynamic get left;
  external dynamic get top;
}
Enter fullscreen mode Exit fullscreen mode

And amend the animate() external method declaration as follows:

external jQuery animate(AnimateOptions options);
Enter fullscreen mode Exit fullscreen mode

Now we can invoke our methods in web/main.dart as such:

import './jquery.dart';

void main() {
  jQuery('#output')
      .css(CssOptions(
          backgroundColor: 'green',
          height: 100,
          position: 'relative',
          width: 100))
      .animate(AnimateOptions(left: 100, top: 100));
}
Enter fullscreen mode Exit fullscreen mode

And expect the result below:

Recording of Dart+jQuery interop example

Conclusion

Knowing that you can remain productive as a Dart developer while maintaining access to the JavaScript library ecosystem, changes things for the better as any new library that pops out is still within your grasp.

This solution works for any JavaScript library that has a namespace under the window object, which covers 99% of cases.

As always, I hope this was insightful and you learnt something new today. And here's the gist containing the full solution.

Below is an extended example with Vue.js:

Further reading

  1. js package on Pub
  2. Free Dart screencasts on Egghead.io

Sharing is caring 🤗

If you enjoyed reading this post, please share this through the various social channels. Also, check out and subscribe to my YouTube channel (hit the bell icon too) for videos on Dart.

Subscribe to my email newsletter to download my Free 35-page Get started with Dart eBook and to be notified when new content is released.

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

Top comments (23)

Collapse
 
silvapheeha profile image
Silva

Hi, How can i implement this in angularDart, Tried following your approach but nothing is happening in my component.

Collapse
 
creativ_bracket profile image
Jermaine

Could you shed a bit more light on what you wish to achieve? You don't need interop if using AngularDart itself.

Collapse
 
silvapheeha profile image
Silva

I am trying to use jQuery With AngularDart, to animate some components. Angular has a BrowserAnimationsModule but couldn't find something similar in AngularDart so decided to use jQuery.

Thread Thread
 
creativ_bracket profile image
Jermaine

May investigate this as part of my video series on AngularDart. In the meantime, have you tried with CSS animations?

Thread Thread
 
silvapheeha profile image
Silva

Css animations was my last resort, i will try and use css. Thank you.

Thread Thread
 
creativ_bracket profile image
Jermaine

You're welcome. Just released a video today demonstrating interop with Toastr.js in an AngularDart web app → youtu.be/xegpncRwqGw Let me know what you think.

Thread Thread
 
silvapheeha profile image
Silva

Thanks, your tutorial helped me lot.

Thread Thread
 
hoanganhhhhh profile image
HoangAnhhhhh

but I couldn't build the app when using external js library. :(

Thread Thread
 
creativ_bracket profile image
Jermaine

Any errors? Could you share the code you have in a GitHub gist?

Collapse
 
thinkdigitalsoftware profile image
ThinkDigitalSoftware

Do you, by chance, know if this works with Flutter?

Collapse
 
creativ_bracket profile image
Jermaine

This only works with browser-based packages. The only way I see this useful in a Flutter app is if rendering content in a web view.

Collapse
 
thinkdigitalsoftware profile image
ThinkDigitalSoftware

Well there are tons of JS libraries that aren't ui related where we wouldn't have to reinvent the wheel

Thread Thread
 
creativ_bracket profile image
Jermaine

Sorry, not sure I follow what you mean here. Could you expand on this?

Thread Thread
 
thinkdigitalsoftware profile image

Let's say I want to use the moments.js library in my flutter application. Since it's already written, could I include this in my Flutter application and then call the functions in the moments library, or does it only work for interacting with the JS Window object?

Thread Thread
 
creativ_bracket profile image
Jermaine

Ahhh, thanks for clarifying. Only works with packages that are accessible via the window object, which will cover most browser-based JS packages. This is by design.

Out of interest, do you have a particular use case that warrants using a JS package in Flutter?

Thread Thread
 
thinkdigitalsoftware profile image
ThinkDigitalSoftware

well there was an IMEI/credit card validator that was written in JS that I needed in one of my apps. I ended up converting it to dart code and then used it, but it would have been nice if I didn't need to. JS is so mature already I don't want to reinvent wheels I don't need to.

Thread Thread
 
creativ_bracket profile image
Jermaine

Oh right, got you. I tried a quick search on Pub that brought me across these two:

Haven't tried it myself, but would those have worked for your use case?

Thread Thread
 
thinkdigitalsoftware profile image
ThinkDigitalSoftware

imei_validator is the one I ported over :D

Thread Thread
 
creativ_bracket profile image
Jermaine

Awesome Jonathan! I'm glad you contributed in this way. I count this a victory. This also has the added benefit of not needing any JS interop, which helps with performance. Now I know where to look should I need credit card validation ;)

Collapse
 
alfinbi profile image
Alfin Bakhtiar Ilhami

could you explain how to user nodejs 'require' in dart js interop ?

Collapse
 
creativ_bracket profile image
Jermaine

Hey Alfin, have you seen the node_interop package?

Collapse
 
mandui profile image
mandui

Hi, I'm new in web dev, and your aqueduct videos really help me, thx:)

I wanna ask is there a way to use javascript file in aqueduct project? I mean there's no index.html in project...

Collapse
 
creativ_bracket profile image
Jermaine

Hi Mandui, not in an Aqueduct project. Aqueduct is used to build serverside backends. You could serve index.html and js files with Aqueduct, but I'm assuming that ain't what you're asking.

Could you expand on what you wish to do?