Reputation: 1
Task is to create a side toolbar for a painting web-application. I'm using Angular2+ for the first time and I thought it would be a good idea to implement the command pattern right off the bat so that I can avoid any refactoring later on.
Problem is I'm not sure how to go about it:
So far I put my "tools" (i.e: pens, pencils, paintbrush etc.) in separate classes (representing the concrete commands):
import { ToolService } from './tool.service';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class PencilToolService implements ToolService {
private imgClass:string = "fas fa-pencil-alt";
constructor() {
}
execute():void {
//TODO: add pencil logic
}
getImgClass():string{
return this.imgClass;
}
}
all the tools implement the interface Tool-Service, which is supposed to represent the "Command" interface
export interface ToolService {
execute():void;
}
then I made an Invoker which takes all
import { PaintBrushToolService } from './../tool-selector/tools/paintbrush-tool.service';
import { PencilToolService } from './../tool-selector/tools/pencil-tool.service';
import { ToolService } from './tools/tool.service';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ToolInvokerService {
selectedTool:ToolService;
tools: ToolService[];
constructor(private toolService:ToolService) {
this.selectedTool = toolService;
//Maybe remove from constructor
}
setToolService(toolService: ToolService):void {
this.selectedTool = toolService;
}
getSelectedTool():ToolService {
return this.selectedTool;
}
executeToolService(){
this.selectedTool.execute();
}
getToolList():ToolService[] {
return this.tools;
}
}
Now this is where I get stuck. I'm supposed to add a receiver, which in this case I'm not exactly sure is what (the toolbar itself?).
for reference, here is the toolbar code:
import { ToolService } from './../../../services/toolbar/tool-selector/tools/tool.service';
import { PaintBrushToolService } from './../../../services/toolbar/tool-selector/tools/paintbrush-tool.service';
import { PencilToolService } from './../../../services/toolbar/tool-selector/tools/pencil-tool.service';
import { ToolInvokerService } from './../../../services/toolbar/tool-selector/tool-invoker.service';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-tool-selector',
templateUrl: './tool-selector.component.html',
styleUrls: ['./tool-selector.component.scss']
})
export class ToolSelectorComponent implements OnInit {
tools:ToolService[];
constructor(private toolInvoker:ToolInvokerService,
private pencil:PencilToolService,
private paintBrush:PaintBrushToolService) {
this.tools = [this.pencil, this.paintBrush];
}
ngOnInit() {
}
}
Also, in this case, what is my Editor class?
Note: I'm basing myself on this class diagram: https://refactoring.guru/design-patterns/command
Note2: A very similar web-app is sketch.io
Upvotes: 0
Views: 771
Reputation: 3489
I've got a similar scenario where I have a context sensitive menu bar. Depending on what you have selected, the available operations on the menu bar change. And to make matters worse, some of the operations that exist in the menu bar can be done by right clicking on the object.
The thing is, the business logic to execute commands will need to use DI to resolve services etc, but I also need the command to store a state that is specific to this operation at this very point in time (which could also be used later to undo the operation).
To achieve this, take a look at the command bus pattern. In which case your angular services would be your handlers. It means that the actual logic that does the execution can still make use of DI while the state of you commands can be constructed on the fly, passed around and even serialized/stored if you wish.
So "PencilComponent" would issue a "DrawLineCommand" command with only a state, to a CommandBusService/CommandHandlerService. CommandHandlerService would look through its handlers for one mapped to the DrawLineCommand, find PencilService and call whatever action on pencil service.
Then in the initialization logic of PencilService you can register it as a command handler for all of its commands.
Upvotes: 0