What are fragments?
A fragment is an independent block of logic. In simpler terms, a fragment is just a method. Just like the service allows for the injection of dependencies, can maintain its own state, and has access to the executing context — such as other fragments and methods defined within that context. Let’s compare the standard approach using services with the use of fragments.
In picture above you can notice that approach with fragments is more granular, allowing us to split our code into smaller blocks for flexible use.
What are the benefits of using fragments?
Example 1
Let’s assume we have 3 services: Service A, Service B, and Service C as on picture below:
We received a new requirement to use our method C1 in the app component. Initially, the thought was to inject Service C into the app component. However, this would lead to a larger main bundle containing unnecessary code (such as method A1 and method B1), which is not desirable.
Another option is to move method C1 to new service and let’s assume that method C1 relies on method B2 from Service B. So this approach still require injecting Service B and would consequently include unnecessary code (specifically method B1) in the main bundle. Additionally, we cannot move method B2 to this new service, because we also use it in a different place.
Finally we decided to create 2 new services: Service D with method C1, Service E with method B2 (see picture below). We ended up with services, each having only a single method and subsequently, our next step is to properly register all services in the providers and ensure their correct injection.
This rebuilding process consumed a significant amount of time and posed a risk of introducing bugs.
We ended up with services, each having only a single method, so a more effective strategy would be to use fragments. This approach should prevents situations like the one mentioned above. When new logic is required, we simply add our new fragment to a specific context, leaving the remaining code untouched. If there’s a need for different behavior (logic) within a context, you have the option to replace fragments with others.
Example 2
Let’s consider the scenario where we have three services: Service A, Service B, and Service C, each with some repetitive code and we decided to introduce inheritance for them. We created base class with one method X and also we have few fields there, which every service use. However, we have to add new feature which requires the addition of two services: Service D and Service E. These services need a different method than method X, but also use the same fields from base class. What options are available to us? We’re unable to introduce a new base class due to the limitation of inheriting from only one class. An alternative possibility is introducing an additional base parent class (as shown in the picture below):
Let’s say we stick with this solution, but during our app’s development, we need to add two more services: F and G. These services operate with different logic and do not use all fields from the base class. We realize that inheritance wasn’t the most suitable choice and end up rewriting everything for example into separate base services, each with a single method. This process consumed again a significant amount of time and posed a risk of introducing bugs.
In the fragment-based approach, we’re able to define three distinct fragments and effortlessly include them in the Context. This eliminates the need to spend time contemplating a better solution such as inheritance or composition.
Example 3
Let’s assume that we wrote apiService as on picture below:
The code repeats, particularly in result mapping and it can be identical across all upcoming API services. Although a base class could resolve this, it might lead to a scenario similar to Example 2. Therefore, opting to use Fragments is a more preferable solution. The image below shows code using fragments:
Each new API file will contain only a few lines of code, and we can easily modify its implementation if needed.
You’ve seen the advantages of using fragments in the examples above, but there’s more to explore. Let’s take a deeper dive into fragments.
Fragment templates
Each fragment has to be created from a template. In Example 3, we created our fragment using the apiFragment template. Templates help us efficiently handle repetitive behaviors and activities. Currently, the fragments library contains a few predefined templates:
- pureFragment — it executes the fragment function, if there’s a change in the fragment input
- memoFragment — executes the fragment function once, remembers the result, and returns the memoized result for another executions
- apiFragment — contains built-in tools for handling API communication
- storeFragment — contains built-in tools for managing state, it shares similar behavior with memoFragment
- formFragment — contains built-in tools for managing forms, it shares similar behavior with memoFragment
Currently, we have only a few predefined templates, but new ones, like debounceFragment, will be added in the future. The library also allows us to create custom templates using a dedicated builder API, providing the flexibility to construct templates tailored to our specific requirements.
In this example below, you can see how we can use the formFragment template to create our fragment
As mentioned before, the formFragment template comes with built-in tools necessary for creating and managing forms. This eliminates the need to create a service and inject all dependencies into it (such as formBuilder in this case). Additionally, it includes the _observe method allowing for subscription to form value changes, and it automatically unsubscribes it when the fragment is destroyed.
Context
The context is also a crucial element in fragments, as it acts as the connection between them. Let’s explore code below to see how context can be used:
As you can see above Context connects fragments. When different behavior is needed, it’s easy to replace fragments. Creating and using a Context is similar to services: adding it to providers, injecting it into components, and call actions.
In fragments, they can access the context where they’re executed. See the picture below:
In the draw$ fragment, we have access to the store$ fragment, compare method. The compare method may have different implementations based on the context it’s used within. Therefore, in fragments, we’re not limited to one specific type of context, we can use abstraction, as shown in the image above. For instance, within the draw$ fragment, we can use either PeopleComponentContext or StarshipComponentContext
Every context needs to be registered in providers. This allows us to manage the lifespan of the context and all the fragments included within it. By adding the context to component providers, both the context and its fragments will remain alive as long as the component exists.
Fragments is an open-source library that will soon be available in beta version on npm. You can find the entire code at https://github.com/webfragments. I encourage leaving feedback, opinions, or any other ideas in the comments section.
Summary
Working with fragments can be simpler than using the standard service approach. It eliminates complexities like classes and inheritance. This idea makes code more flexible, by splitting it into smaller parts can improve performance. We can create shareable fragments, build our libraries, and reuse them in various projects. Fragments are also tree-shakeable.
Top comments (0)