AngularJS - wrapping 3rd party asynchronic loaded library as a service
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In AngularJS, third-party libraries work best when the rest of the application does not touch global state directly. That becomes more important when the library is loaded asynchronously, because consumers must wait until the script is available. The usual fix is to hide the loading and readiness logic inside a service that returns a promise.
Why a Service Wrapper Helps
If controllers or directives call window.SomeLibrary directly, two problems appear immediately. First, load order becomes fragile: the controller may run before the script has finished loading. Second, testing becomes harder because every test now depends on a real browser global.
A service wrapper solves both problems:
- it loads the script once
- it exposes a promise-based API
- it gives AngularJS code a stable dependency to inject
- it makes mocking simple in unit tests
The goal is not just to "make it work". The goal is to make script loading predictable and keep the rest of the application unaware of how that script arrived.
Load the Script Once
The first service should be responsible only for loading the external file. A small loader built around $q, $document, and $window is enough.
This pattern matters for two reasons. It caches the promise so repeated injections do not insert the same script several times, and it rejects cleanly if the file fails to load.
Wrap the Library API, Not the Global
The second service should express the parts of the library your application actually uses. Suppose the external file creates a global called AwesomeLibrary with an init method and a search method. The AngularJS wrapper can expose those operations without leaking the global object to the rest of the app.
Consumers now have a stable contract. They call awesomeService.search('term') and do not care whether the script was already loaded, still loading, or just initialized.
Consume It from AngularJS Code
The controller can remain small and predictable because the async work is centralized in the service.
This is the right separation of concerns. The controller manages view state. The wrapper manages loading, initialization, and communication with the third-party code.
Keep AngularJS in the Loop
One subtle issue with third-party libraries is that callbacks may run outside AngularJS. If the library accepts callbacks instead of returning promises, wrap those callbacks in $q or trigger a digest safely with $rootScope.$evalAsync. Otherwise the data may change without the view updating.
A callback-based wrapper looks like this:
That extra step is often the difference between an apparently correct wrapper and one that randomly leaves the screen stale.
Common Pitfalls
The most common mistake is resolving the promise as soon as the script file loads, even though the library still needs initialization. If the library has a separate startup step, the promise returned to AngularJS code should resolve only after initialization succeeds.
Another mistake is exposing the raw global object everywhere. That defeats the purpose of the service and spreads third-party details through the codebase.
Duplicate script insertion is another frequent bug. If two controllers request the library at the same time, a missing cache can append two identical script tags and create inconsistent state.
Finally, do not ignore digest integration. When callbacks happen outside AngularJS, the UI may not update until some unrelated event triggers a digest cycle.
Summary
- Wrap async third-party libraries behind an AngularJS service
- Use one service to load the script and cache the promise
- Use another service to expose the library operations your app needs
- Resolve readiness only after both loading and initialization complete
- Keep controllers unaware of browser globals
- Use
$qand $evalAsyncwhen callbacks run outside AngularJS

