# 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**](https://pub.dartlang.org/packages/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](https://gist.github.com/graphicbeacon/0a23381809fcddaffd553c9ad845b96f) containing the solution.

## **Prerequisite**

Before we start lets use [stagehand](https://pub.dartlang.org/packages/stagehand) to scaffold our web project:

```bash
$ stagehand web-simple
```

Add the following in pubspec.yaml under dependencies:

```yaml
dependencies:
  js: ^0.6.0
```

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()`:

```dart
@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
}
```

Run `webdev serve` and visit the [localhost](http://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`:

```dart
@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 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.

```dart
// @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`:

```xml
<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:

```dart
@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
}
```

Let’s refactor `main.dart` to test this:

```dart
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:

```dart
@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:

```dart
@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;
}
```

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

```dart
external jQuery css(CssOptions options);
```

Let’s do the same for the animate method:

```dart
@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:

```dart
external jQuery animate(AnimateOptions options);
```

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

```dart
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:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1725704824887/2619c81c-ab9f-499a-98c8-4c0a8614342f.gif align="center")

## **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](https://gist.github.com/graphicbeacon/0a23381809fcddaffd553c9ad845b96f) containing the full solution.

## **Further reading**

1. [**js package** on Pub](https://pub.dartlang.org/packages/js)
    
2. [**Free Dart screencasts on Egghead.io**](https://egghead.io/instructors/jermaine-oppong)
