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, in this case being `window`

library main; // required library declaration import 'package:js/js.dart'; // Pull in our dependency @JS('console.log') // annotates `log` function to call `console.log`

external void log(dynamic str); void main() {

log('Hello world!'); // invokes console.log() in JavaScript land

}

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 any argument, it sets the context of the other annotations relative to the window global 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; // library name can be whatever you want import 'package:js/js.dart'; @JS('log') // Setting the proxy to the `console.log` method

external void log(dynamic str); void main() {

log('Hello world!');

}

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);

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>

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 jQuery instance

}

Let’s refactor main.dart to test this:

import './jquery.dart'; void main() {

print(jQuery('#output')); // [object Object]

print(jQuery('#output').length); // 1

}

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);

}

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 // needed along with 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;

}

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

external jQuery css(CssOptions options);

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;

}

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

external jQuery animate(AnimateOptions options);

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));

}

And expect the result below:

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.

Further reading