#How to structure an abstract base class with @Input/@Output for split UI variants?

15 messages · Page 1 of 1 (latest)

dark canopy
#

I'm working on an Angular component that needs to support A/B testing (split variants) where:

Business logic is shared between both variants (A and B)
Template and minor styling differ between variants

I want to extract the shared business logic into a base abstract class that contains all @Input() and @Output() bindings, and then extend it in variant A and B components using different templates.

basically i want to have something

export abstract class BaseProductSummaryClass { public readonly meta = input<IMeta>(undefined); etc... }

would be nice to provide your suggestions to organize this code better and manitance easy

I am thinking about to use *ngComponentOutlet directive to load component variant based on split condition, this is also challenging how to pass inputs to variant components

#

How to structure an abstract base class with @Input/@Output for split UI variants?

graceful torrent
#

If i was tasked to do A/ testing , i would:

  • create a custom structural directive to displays the content based on a condition (who is the A/B target)
  • isolate the parts which should change in small components, then duplicate them and customize the B one

Trying to make some 'base' will just complicate all your code

dark canopy
#

also thinking this way where base class holds busines logic only

`export abstract class BaseProductSummaryClass {
meta!: IMeta;

// business logic methods here...
}`

variants

`@Component({
selector: 'product-summary-a',
templateUrl: './product-summary-a.component.html',
})
export class ProductSummaryVariantAComponent extends BaseProductSummaryClass {
// just uses inherited business logic
}

@Component({
selector: 'product-summary-b',
templateUrl: './product-summary-b.component.html',
})
export class ProductSummaryVariantBComponent extends BaseProductSummaryClass {
// just uses inherited business logic
}`

wrapper component

@Component({ selector: 'product-summary', template: <ng-container #container></ng-container>`
})
export class ProductSummaryWrapperComponent implements AfterViewInit {
@ViewChild('container', { read: ViewContainerRef }) container!: ViewContainerRef;

@Input() context!: {
meta: IMeta;
};

@Output() goToStep = new EventEmitter<string>();

ngAfterViewInit() {
const comp = isSplitB()
? ProductSummaryVariantBComponent
: ProductSummaryVariantAComponent;

const ref = this.container.createComponent(comp);

// assign manually instead of using Inputs
Object.assign(ref.instance, this.context);

ref.instance.goToStep.subscribe(e => this.goToStep.emit(e));

}

}`

usage

`<vx-product-summary
[context]="{
meta: checkoutConfig.meta,
}"
(goToStep)="goToStepById($event)"

</vx-product-summary>`

dark canopy
graceful torrent
dark canopy
#

ya, i mean lot of UI changes and just want to create two templates for two variants and keeps business logic same which shares logic

graceful torrent
#

canMatch would be a solution to, to act on the route path for 2 different components, with the idea of splitting content once again.

A base component has always been a bad idea and that's a bigger cost, for now and when you'll need to remove it

graceful torrent
dark canopy
coral kettle
#

You can't abstract input/outputs

#

The best alternative is to provide the data via a service that could be abstracted

dark canopy
#

yes

dark canopy
#

is this valid syntax?

@Directive() export abstract class BaseValueComponent { // inputs, outputs }

coral kettle
#

Yes