IonIon
Packages
  • next
  • current
Changelog
GitHub
Packages
  • next
  • current
Changelog
GitHub
  • Version 0.x

    • Release Notes
    • Upgrade Guide
    • Contribution Guide
    • Security Policy
    • Code of Conduct
    • Origin
  • Packages

    • Introduction
    • Container

      • Introduction
      • Prerequisites
      • How to install
      • Container Instance
      • Bindings
      • Dependencies
      • Resolving
      • Contextual Bindings
    • Contracts

      • Introduction
      • How to install
    • Support

      • Introduction
      • How to install
      • Arrays

        • About Arrays
        • Includes All
        • Includes Any
        • Is Array Like
        • Is Concat Spreadable
        • Is Safe Array Like
        • Is Typed Array
        • Merge
      • Concerns

        • About Concerns
        • Prerequisites
        • Concern Class
        • Using Concerns
        • Aliases
        • Conflict Resolution
        • Booting
        • Hooks
        • Edge Cases
        • JSDoc
      • Exceptions

        • About Exceptions
        • Configure Custom Error
        • Configure Stack Trace
        • Get Error Message
        • Custom Errors
      • Facades

        • About Facades
      • Meta

        • About Meta
        • Prerequisites
        • Supported Elements
        • Set & Get
        • Inheritance
        • Outside Changes
        • TC39 Proposal
        • Target Meta
      • Mixins

        • About Mixins
        • New Mixin
        • Apply Mixins
        • Instanceof
        • Inheritance
        • Onward
      • Object

        • About Objects
        • Forget
        • Forget All
        • Get
        • Has
        • Has All
        • Has Any
        • Has Unique ID
        • Is Cloneable
        • Is Populatable
        • Isset
        • Merge
        • Populate
        • Set
        • Unique ID
      • Reflections

        • About reflections
        • Assert Has Prototype Prop.
        • Class Looks Like
        • Class Own Keys
        • Get All Parents Of Class
        • Get Class Prop. Descriptor
        • Get Class Prop. Descriptors
        • Get Constructor Name
        • Get Name Or Desc. Tag
        • Get Parent Of Class
        • Has All Methods
        • Has Method
        • Has Prototype Property
        • Is Callable
        • Is Class Constructor
        • Is Class Method Reference
        • Is Constructor
        • Is Key Safe
        • Is Key Unsafe
        • Is Method
        • Is Subclass
        • Is Subclass Or Looks Like
        • Is WeakKind
      • Misc

        • About Misc.
        • Desc. Tag
        • Empty
        • Is Key
        • Is Primitive
        • Is Property Key
        • Isset
        • Merge Keys
        • To Weak Ref.
      • Callback Wrapper
    • Vuepress Utils

      • Introduction
      • How to install
      • Navigation

        • Archive
      • Plugins

        • Last Updated
      • Components

        • Version Disclaimer
    • XYZ (test package)

Introduction Available since v0.11Browser

The @aedart/support/facades package is an adaptation of Laravel's Facades (originally licensed under MIT). In this context, a Facade acts as an interface (or gateway) to an underlying object instance, resolved from the Service Container.

import { Container } from "@aedart/support/facades";

const service = Container.obtain().make('api_service');
  • Setup Facade's Service Container instance
  • Define a Facade
  • The obtain() method
  • Testing
  • Onward

Setup Facade's Service Container instance

Before you can make use of facades, you must ensure that the Facade abstraction has a service container instance set. This can be done via the static setContainer() method.

import { Container } from "@aedart/container";
import { Facade } from "@aedart/support/facades";

// Somewhere in your application's setup or boot logic...
Facade.setContainer(Container.getInstance());

Consequently, if you need to unset the Service Container instance and make sure that the Facade abstraction is cleared of any previously resolved object instances, invoke the static destroy() method.

Facade.destroy();

Define a Facade

To define your own Facade, extend the abstract Facade class, and specify the target binding identifier.

import { Facade } from "@aedart/support/facades";

export default class ApiFacade extends Facade
{
    static getIdentifier()
    {
        return 'api_client';
    }
}

If you are using TypeScript, then you can also specify the return type of the obtain() method, by declaring the underlying resolved object's type, for the internal type property (type property is not used for any other purpose).

import type { Identifier } from "@aedart/contracts/container";
import { Facade } from "@aedart/support/facades";
import type { AcmeApiClient } from "@acme/contracts/api";

export default class ApiFacade extends Facade
{
    protected static type: AcmeApiClient;
    
    public static getIdentifier(): Identifier
    {
        return 'api_client';
    }
}

The obtain() method

The obtain() is used to obtain the Facade's underlying object instance. Typically, you do not need to do anything more than to implement the getIdentifier() method in your concrete facade class. But, in some situations you might need to resolve a binding differently. Or, perhaps perform some kind of additional post-resolve logic, in order to make easier / simpler to work with the resolved object.

export default class LimitedApiFacade extends Facade
{
    static getIdentifier()
    {
        return 'api_client';
    }

    /**
     * @return {import('@acme/contracts/api').AcmeApiClient}
     */
    static obtain()
    {
        const client = this.resolve(this.getIdentifier());
        client.error_response_thresshold = 3;
        client.ttl = 350;
        
        return client;
    }
}
const promise = LimitedApiFacade.obtain().fetch('https://acme.com/api/users');

Testing

When you need to test components that rely on Facades, you can register a "spy" (mocked object), via the static method spy(). Consider, for instance, that you have a users repository component that relies on a custom Api facade.

import { ApiFacade } from "@acme/facades";

class UsersRepository {
    
    fetch() {
        return ApiFacade.obtain().fetch('https://acme.com/api/users');
    }
    
    // ...remaining not shown...
}

In your testing environment, you can specify a callback that can be used to create a fake object (mocked object) that must behave in a certain way, via the spy() method. The callback must return either of the following:

  • The Facade's underlying resolved object.
  • Or, a fake object that behaves as desired (in the context of your test).
ApiFacade.spy((container, identifier) => {
    // ...mocking not shown ...

    return myResolvedObject; // resolved or mocked object
});

All subsequent calls to the facade's underlying object will be made to the registered "spy" object instead.

The following example uses Jasmine as testing framework. However, the spy() method is not tied to any specific testing or object mocking framework. Feel free to use whatever testing tools or frameworks fits your purpose best.

import { ApiFacade } from "@acme/facades";
import { UsersRepository } from "@app";

// E.g. testing via Jasmine Framework...
describe('@acme/api', () => {

    // Test setup not shown in this example...
    
    afterEach(() => {
        Facade.destroy();
    });
    
    it('can obtain users', () => {

        let mocked = null;
        ApiFacade.spy((container, identifier) => {
            const apiClient = container.get(identifier);

            mocked = spyOn(apiClient, 'fetch')
                .and
                .returnValue([
                    { id: 12, name: 'Jackie' },
                    { id: 14, name: 'Lana' },
                    // ...etc
                ]);

            // return the resolved api client...
            return apiClient;
        });

        const repo = new UsersRepository();
        const users = repo.fetch();
        
        expect(users)
            .not
            .toBeUndefined();

        expect(mocked)
            .toHaveBeenCalled();
    });
});

Onward

Please consider reading Laravel's "When to Utilize Facades", to gain an idea of when using Facades can be good, and when not.

Edit page
Last Updated:
Contributors: alin