graycoreio/daffodil

View on GitHub
libs/cart/guides/extensions.md

Summary

Maintainability
Test Coverage
# Extensions
`@daffodil/cart` provides a number of extension mechanisms so that it can be customized to fit specific needs.

## Custom drivers
If the packaged Daffodil drivers don't satisfy the required use cases, they can be overriden by providing custom drivers. Create a service that implements the interface corresponding to the driver in question.

If custom behavior is not needed for all driver methods, unimplemented methods can be delegated to the original driver. The following example demonstrates overriding the `create` method of the `DaffCartDriver` while using Magento.

```ts
import {
  provideDaffCartDriver,
  DaffMagentoCartService
} from '@daffodil/cart';

@Injectable({
  providedIn: 'root'
})

export class CustomMagentoCartService implements DaffCartServiceInterface {
  constructor(
    private cartDriver: DaffMagentoCartService,
  ) {}

  get(cartId: string): Observable<DaffCart> {
    return this.cartDriver.get(cartId);
  }

  create(): Observable<{id: string}> {
    // custom behavior
  }

  addToCart(productId: string, qty: number): Observable<DaffCart> {
    return this.cartDriver.addToCart(productId, qty);
  }

  clear(cartId: string): Observable<Partial<DaffCart>> {
    return this.cartDriver.clear(cartId);
    }
}

@NgModule({
  ...,
  providers: [
    provideDaffCartDriver(CustomMagentoCartService)
  ]
})
class AppModule {}
```

## Generic models
All Daffodil layers can operate on generic extensions of vanilla Daffodil models. Custom models can therefore be used while retaining type safety. The following example illustrates customizing the cart model with the cart facade.

```ts
import {
  DaffCartFacade,
  DaffCart
} from '@daffodil/cart';

interface CustomCart extends DaffCart {
  customField: string;
}

@Component({})
class CartComponent implements OnInit {
  cart$: Observable<CustomCart>;

  constructor(private cartFacade: DaffCartFacade<CustomCart>) {}

  ngOnInit() {
    // this.cartFacade.cart$ is of type CustomCart
    this.cart$ = this.cartFacade.cart$;
  }
}
```

## Extensible GraphQL fragments
Arbitrary additional fields can be requested on the cart object. Inject a GraphQL document node containing fragments on the platform's cart type to define extra fields.

Only drivers that use GraphQL support extensible fragments because fragments are specific to GraphQL. The following cart drivers support extensible fragments:

- Magento

### Magento
Use `provideDaffCartMagentoExtraCartFragments` to query additional fields on a Magento cart query. This applies to all of the driver calls that return a `DaffCart`, which is most of them.

The additional fields are present on the untyped `extra_attributes` field.

The following example demonstrates providing a GraphQL document using the `graphql-tag` library.

```ts
import gql from 'graphql-tag';
import {
  provideDaffCartMagentoExtraCartFragments,
  DaffCartFacade,
  DaffCartLoad,
  DaffCart
} from '@daffodil/cart';

const extraCartFragment = gql`
  fragment ExtraCartFields on Cart {
    field1 {
      amount
    }
    shipping_addresses {
      field2 {
        value
      }
    }
  }
`;

@NgModule({
  ...,
  providers: [
    provideDaffCartMagentoExtraCartFragments(extraCartFragment)
  ]
})
class AppModule {}

@Component({})
class CartComponent implements OnInit {
  cart$: Observable<DaffCart>;
  field1$: Observable<{amount: number}>;
  field2$: Observable<{value: string}>;

  constructor(private cartFacade: DaffCartFacade) {}

  ngOnInit() {
    this.loadCart();
    this.cart$ = this.cartFacade.cart$;
    this.field1$ = this.cart$.pipe(
      map(cart => cart.extra_attributes?.field1)
    );
    this.field2$ = this.cart$.pipe(
      map(cart => cart.extra_attributes?.shipping_addresses?.[0]?.field2)
    );
  }

  private loadCart() {
    this.cartFacade.dispatch(new DaffCartLoad());
  }
}
```

> An extra cart fragment is defined and provided for the `DAFF_CART_MAGENTO_EXTRA_CART_FRAGMENTS` injection token. The `CartComponent` then loads the cart on initialization, which will query the cart and include the extra injected cart fields in the request. The component maps the extra fields from `cart.extra_attributes` to local fields.