Design patterns to know in Angular
To develop quality applications it is necessary to first have a good knowledge of the existing design patterns in order to recognize as early as possible when it is possible to use them in the project.
The MVC design pattern corresponds to the architecture of a project consisting of three distinct parts. Models are used to represent transit data used in the application (interface or class without logic). The views correspond to the user interface (the HTML template). Finally controllers allow to link the two previous layers.
Dependency Injection Pattern
Dependency Injection (DI) is an important design pattern for developing large-scale applications. Angular has its own DI system, which is used in the design of Angular applications to increase efficiency and scalability. Indeed in the constructor of the classes (components, directives, services) one asks for dependencies (services or objects). It is an external system (the injector) which is responsible for creating the instance according to the configuration. This facilitates the development but also the tests.
The SOLID design pattern is a set of five principles suitable for object-oriented computer programming. It aims to make software design more understandable, flexible and maintainable. These principles make it possible to highlight the good practices to be followed but they are abstract and there are therefore several concrete patterns in order to develop while respecting these principles.
- Unique responsibility: Each file (class / function / module / section) should have only one responsibility, that is to say it must allow to do a single thing. We must therefore split the logic into as many classes and files as necessary. In addition there must be a place for all types of files and each file must be in the right place.
- Open-closed: Each class must be open for extension but closed for modification. It should be possible to extend functionality by adding code only but without changing existing code. That is to say, the input and output parameters of the functions and the typings must not change. This way there is less risk of breaking the logic.
- Liskov substitution: If B and C are implementations of A, then B and C must be able to be interchanged without affecting program execution. B and C implementations must have the same functions, signatures and types in order to be able to interchange them. (Ex: strategy pattern)
- Principle of interface segregation: If B and C are implementations of A, then B and C must really be able to implement the functions described in interface A. When executing our program we should not check if the implementation can trigger this method. If this is the case then the solution is to split into several interfaces.
- Dependency inversion principle: High level modules should not depend on low level modules but both should depend on abstractions. (Ex: dependency injection pattern, adapter pattern)
The strategy design pattern involves separating the execution of a business logic from the entity that uses it and giving that entity a way to switch between different forms of logic. For instance, to be able to perform different types of sorting on an entity, you must create an abstract SortingStrategy class or function and then create different sorting implementations. Now you can select the strategy by switching between the different implementations based on your needs.
The visitor design pattern is useful if you have multiple concrete classes that inherit from the same base class or implement the same interface. In this case the visitor pattern helps to avoid if-else block or switch/case and typecasting. Indeed, the goal is to add new behaviors to the existing class hierarchy without modifying any existing code. For instance, the CustomMarker abstract class declares a method that should take the visitor interface as argument and then each custom MarkerClass implementation will call the adequate method of the visitor with itself as argument. The visitor will therefore have X methods, which corresponds to X implementation of CustomMarker.
The inheritance offered by object-oriented programming can create tightly coupled hierarchical objects that are difficult to refactor because of the object dependencies. Instead you can use a composition pattern to allow flexibility in what the container object can do.
The lazy design pattern makes it possible to develop scalable and high-performance applications because the principle of this pattern is to provide an application divided into several independent parts. This makes it possible to reduce the size of the main application and then have different modules which will be downloaded by the user only if he wishes to access this specific part of the application.
This pattern ensures that there is only one instance of your class. Thanks to this singleton, you can control the scope of the variables inside. The singleton is handled using a public getInstance method which guarantees the one and only way to access the class. In Angular the dependency injection mechanism manage the pattern for us. For instance the services provided at the root of the application are singleton instances, on the contrary the services provided in the components are not singleton they will be instantiated for each instance of the component.
This pattern is very simple but very useful when you need to instantiate different child objects of the same parent class according to certain conditions. The factory defines an object creation interface with the creation conditions as input and the instance of the object as output. This interface usually contains a single public method. Then, it is possible to have different implementations of this factory interface, each with their own logic to instantiate the objects. Please note that this factory pattern is necessary only in case of special logic to instantiate the child objects, if there is no variance to be made during instantiation then it is not necessary to apply this pattern.
The purpose of this model is to encapsulate a command inside an object. The command can be of any type, for example a synchronous action or even an asynchronous request. Thanks to this, you can have a list of commands which you can easily invoke, reuse, combine and maintain in your application.
The decorator design pattern is an alternative to subclasses for extending an object using composition instead of inheritance. In fact, the decorator attaches additional responsibilities to an object. The main concept is to have an object that wrap another object. The one wrapping the object is the decorator and it is the same type of the original object but it has also an object of the same type of the object.