Factory Method a Design pattern

Discover the world of IT

Factory Method a Design pattern

The factory method is a design pattern that are used to construct a method allowing one to create different objects that shares an interface, and is therefore found as an creational design pattern.

That makes the pattern very scalable, allowing one to add layers of abstraction to your code, and create objects that serves different purposes with similar functional structure.

This pattern becomes very usefull when the creation of objects needs to be done according to context. An simple example is an ice cream machine, that produces different kinds of icecream depending on the flavours that you add to the machine. Adding vanilla the machine produces vanilla flavourd icream, adding strawberries the machine produces straberry flavoured ice cream. Both use cases the machine or factory is producing a similar product (Object), but with different taste.

A usecase for this pattern that could be useful in the terms of UI, is to create a factory that produces different UI elements depending on the context. The example below defines two input components that a created dynamically depending on a selectable value using the factory method principles. Of course, such an example could be hardcoded and it often is, but there are cases where dynamic inputs are required and need to expand and maintained, and this is a good way to simplify the code.

Angular’s use

Angular offers its own factory method out of the box, applying the function found within our code is a component factory that offers a method for creating components that can be applied to a reference point. This is a fine example of this design pattern in use. 

Example

Below you’ll find an example the factory method pattern that allows a selectable field to set an input of the factory where the factory method is applied to fetch the correct component and render it. 

Input Type

This defines an enum, that will be used within this example as a selector, allowing different types to be selected between, and thereby configure the factory to show either a number input or a password input.

export enum InputType {
  'Number' = 'Number',
  'Password' = 'Password',
}

Factory Component

The Factory Component in this example is used as an main component for a input factory that changes the UI of the application dynamicly accoring to the selection defined within this component.

This component consumes the factory component that are used as a factory component in our UI. It enables the factory to change accordingly depending on the selector within this component

@Component({
  selector: 'factory-component',
  template: `
    <select #t  (change)="selectedType = t.value">
        <option *ngFor="let inputType of inputTypes" value="{{inputType}}">{{inputType}}</option>
    </select>
  <input-factory [inputType]="selectedType"></input-factory>
  `
})
export class FactoryComponent implements OnInit{
  @ViewChild('container', {static: true, read: ViewContainerRef }) container;
  inputTypes: string[] = [];
  selectedType: string;

  ngOnInit(): void {
    const inputType: typeof InputType = InputType;
    this.inputTypes = Object.keys(inputType);
    this.selectedType = InputType.Number;
  }
}

Input Component

This is an abstract component class that are used in the following two components to define their two properties, with an generic structure, allowing it to be implmented for multi purpose use. The that inherits this Input Component has a value and an event emitter for when its value changes.

@Component({selector:'input-component'})
abstract class InputComponent<T> {
  value?: T;
  @Output() valueChanged: EventEmitter<T> = new EventEmitter<T>();
}

Password Input Component

This is an Input Component that only allows for text to be passed as in and output. This is a simple component with it’s own template that can differe completely from all implmenetations fo the Input Component. This implementation offers a text input that a shown as a password.

@Component({
  template: `<input type="password" #password [value]="value" (input)="passwordChanged(password)">`
})
export class PasswordInputComponent extends InputComponent<string>{
  passwordChanged(input: HTMLInputElement):void{
    this.value = input.value;
    this.valueChanged.emit(this.value);
  }
}

Number Input Component

This is an Input Component that only allows for numbers to be passed as in and output. This is a simple component with it’s own template that can differe completely from all implmenetations fo the Input Component. This implementation offers a number input.

@Component({
  template: `<input type="number" #password [value]="value" (input)="numberChanged(password)">`
})
export class NumberInputComponent extends InputComponent<number>{
  numberChanged(input: HTMLInputElement):void{
    this.value = +input.value;
    this.valueChanged.emit(this.value);
  }
}

Input Factory Component

This is the factory component allowing it’s content to change according to the inputType. Essentially creating different implementations of the InputComponent. It uses the Angular ComponentFactory which is an Factory method pattern provided by Angular

@Component({
  selector: 'input-factory',
  template: '<ng-template #container></ng-template>'
})
export class InputFactoryComponent{
  @ViewChild('container', {static: true, read: ViewContainerRef }) container;
  @Input() set inputType(inputType: InputType){
    switch (inputType) {
      case InputType.Number:
        this.createComponent<number>(NumberInputComponent, 2000)
        break;
      case InputType.Password:
        this.createComponent<string>(PasswordInputComponent, 'Secret password')
        break;
    }
  }
  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  private createComponent<T>(inputComponent: Type<InputComponent<T>>, value: T):void{
    const componentFactory: ComponentFactory<InputComponent<T>> = this.componentFactoryResolver.resolveComponentFactory(inputComponent);
    this.container.clear();
    const componentRef: ComponentRef<InputComponent<T>> = this.container.createComponent(componentFactory);
    const componentInstance: InputComponent<T> = componentRef.instance;
    componentInstance.value = value;
    componentInstance.valueChanged.subscribe((value: T) => console.log(value))
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *