Refactor Employee Form: Dynamic Logic By Profile
This article details the refactoring of an employee registration form to dynamically adjust its structure based on the selected profile (Professor, Secretary, and Teaching Assistant). The current static form structure is inadequate for handling the diverse data requirements of each profile. The goal is to implement a dynamic FormGroup that adds or removes fields and validations programmatically using addControl and removeControl, ensuring that form.valid accurately reflects the visible and mandatory fields for the selected profile. Additionally, the submission process will be routed to distinct services with profile-specific payloads.
Technical Context
- Component:
src/app/pages/secretaria/funcionario-form/ - New Service:
src/app/services/auxiliar-docente/auxiliar-docente.service.ts - Key Concept: Dynamic manipulation of
AbstractControlinReactiveFormsModule.
Acceptance Criteria and Tasks
The refactoring process is structured into several key tasks, each with specific acceptance criteria to ensure the final solution meets the requirements.
1. 🔌 Creating the AuxiliarDocenteService
This initial step involves setting up the service layer for handling Teaching Assistant data. The service will manage the creation of new Teaching Assistant records. Let's walk through the details:
- Service File Creation: The first sub-task involves creating a new service file, which serves as the central component for managing the business logic related to the Teaching Assistant profile. This file will house the methods responsible for interacting with the backend API, handling data serialization, and managing application state related to Teaching Assistants. Ensuring the service file is properly set up is crucial for maintaining a clean and modular architecture.
- Model Creation: The next step is defining the data structure for the Teaching Assistant. This involves creating a TypeScript interface named
AuxiliarDocente. This interface specifies the properties and their types that represent a Teaching Assistant's data, includingid(number),nome(string),email(string), andarea(string). Defining this model ensures type safety and consistency throughout the application, making the codebase easier to understand and maintain. This is where strong typing really shines, preventing common data-related errors. - Implementing the
criarMethod: This is a core part of the service implementation. Thecriarmethod is responsible for sending a request to the backend API to create a new Teaching Assistant record. It takesdadosof typecriarAuxiliarDocenteRequestas input and returns anObservable<AuxiliarDocente>. The method should use Angular'sHttpClientto make a POST request to the/auxiliar-docentesendpoint. Properly implementing this method ensures that the application can successfully create new Teaching Assistant records in the system. Using Observables allows for asynchronous operations, which is essential for modern web applications.
export interface AuxiliarDocente {
id: number,
nome: string,
email: string,
area: string
}
// types/auxiliardocente.types.ts
export type criarAuxiliarDocenteRequest = Omit<AuxiliarDocente, 'id'>
2. 🧠Form Logic (.ts)
Refactoring the form's TypeScript logic is crucial for implementing the dynamic behavior. This involves injecting the new service, refactoring the ngOnInit method, and implementing an observer for the profile selection. Here's a breakdown:
- Service Injection: The first step is to inject the
AuxiliarDocenteServiceinto the component. This allows the component to use the service's methods, such ascriar, to interact with the backend API. Dependency injection is a powerful feature of Angular that promotes modularity and testability. By injecting the service, the component can easily access the functionality needed to manage Teaching Assistant data. - Refactoring
ngOnInit: ThengOnInitlifecycle hook is the perfect place to initialize theFormGroupwith the common fields (nome,email,perfil). This ensures that the form is set up correctly when the component is initialized. Refactoring this method to handle the initial form setup is essential for the dynamic form logic to work correctly. By initializing the common fields first, the component can then dynamically add or remove fields based on the selected profile. - Implementing Profile Observer: This is where the magic happens. An observer needs to be set up to listen for changes to the 'perfil' form control. When the profile changes, the
atualizarEstruturaFormulariomethod will be called. This dynamic behavior is the core of the refactoring effort. Listening for changes allows the form to adapt in real-time to the user's selections. This observer pattern is a powerful way to handle asynchronous events and keep the UI in sync with the application state. - Creating
atualizarEstruturaFormulario: This private method is responsible for dynamically adding and removing form controls based on the selected profile. It's the heart of the dynamic form logic. Let's dive into its responsibilities:- If Auxiliar Docente: The method should remove the
registrocontrol, remove or clear thedisciplinascontrol, and add theareacontrol, making it required. This ensures that the form only includes the fields relevant to the Teaching Assistant profile. - If Professor: The method should remove the
areacontrol, add theregistrocontrol (making it required), and add thedisciplinascontrol as an array. This configures the form for the Professor profile, which has different data requirements. - If Secretaria: The method should remove the
areacontrol, remove or clear thedisciplinascontrol, and add theregistrocontrol (making it required). This sets up the form for the Secretary profile.
- If Auxiliar Docente: The method should remove the
- Calling
updateValueAndValidity(): After making changes to the form controls, it's crucial to callupdateValueAndValidity()on theFormGroup. This ensures that the form's validation status is updated to reflect the new form structure. Forgetting this step can lead to unexpected behavior and validation errors. This method triggers the validation process, ensuring that the form'svalidproperty accurately reflects the current state.
3. 🎨 Template Adaptation (.html)
Adapting the HTML template is necessary to conditionally render form fields based on the selected profile. This ensures that only relevant fields are displayed to the user, improving the user experience.
- Using
@If: The key to conditional rendering in Angular templates is the@Ifdirective. This directive allows you to show or hide elements based on a condition. In this case, the condition will be based on the selected profile value. Using@Ifdirectives ensures that the template remains clean and maintainable. This declarative approach to conditional rendering is a hallmark of Angular. - Checking Profile Value: Inside the
@Ifdirectives, you can check the value of the profile form control. This allows you to render different sections of the form based on the selected profile. Checking the profile value dynamically adjusts the form's appearance and functionality. This dynamic behavior is essential for creating a user-friendly and efficient form. - Specific Field Rendering:
registroInput: This input should only be displayed for the Professor and Secretary profiles. Using@Ifdirectives, you can conditionally render this input based on the selected profile. Displaying theregistroinput only when needed keeps the form uncluttered and focused.areaInput: This input should only be displayed for the Teaching Assistant profile. Similarly, an@Ifdirective can be used to control the visibility of this input. Conditionally rendering theareainput ensures that the form remains relevant to the selected profile.disciplinasBox: This section should only be displayed for the Professor profile. An@Ifdirective can be used to manage the visibility of this entire section. Displaying thedisciplinassection only for Professors helps to maintain a clear and focused user interface.
4. 🚀 Submission Logic (onSave)
Refactoring the submission logic is crucial for handling the different payloads required by each profile. This involves using a switch statement to route the submission to the appropriate service with the correct data.
- Using a
switch(perfil)Statement: Aswitchstatement is an elegant way to handle different submission scenarios based on the selected profile. Each case in theswitchstatement corresponds to a specific profile, allowing you to tailor the submission logic accordingly. This approach keeps theonSavemethod organized and easy to understand. Using aswitchstatement is a best practice for handling multiple conditional scenarios. - Payload Assembly:
- Professor: The payload should include
{ nome, email, registro, disciplinas }. This payload will be sent to theProfessorService. Assembling the correct payload ensures that the backend receives the necessary data for creating a Professor record. - Secretaria: The payload should include
{ nome, email, matricula: registro }. Note the mapping ofregistrotomatricula. This payload will be sent to theSecretariaService. Mappingregistrotomatriculaensures that the data is correctly formatted for the backend. - Aux. Docente: The payload should include
{ nome, email, area }. This payload will be sent to theAuxiliarDocenteService. This payload contains the specific fields required for a Teaching Assistant record.
- Professor: The payload should include
- Centralized Response Handling: To avoid code duplication, it's best to centralize the handling of success and error responses from the
Observable. This can be done by creating a helper function or using RxJS operators liketapandcatchError. Centralized response handling promotes code reusability and maintainability. This approach reduces the risk of errors and makes the codebase easier to manage.
Expected Payload Structure
To ensure that the frontend sends the correct data to the backend, it's crucial to understand the expected payload structure for each profile.
// Auxiliar Docente
{
"nome": "João Silva",
"email": "joao@fatec.sp.gov.br",
"area": "Laboratório de Informática"
}
// Secretaria
{
"nome": "Maria Souza",
"email": "maria@fatec.sp.gov.br",
"matricula": "123456" // Note o mapeamento de 'registro' para 'matricula'
}
Conclusion
Refactoring the employee registration form to dynamically adapt to different profiles enhances the application's flexibility and user experience. By implementing dynamic form controls and tailoring submission logic, the application becomes more robust and maintainable. This article provides a comprehensive guide to achieving this refactoring, ensuring that the form accurately captures the required data for each employee profile.
For further information on Reactive Forms in Angular, you can refer to the official Angular documentation: Angular Reactive Forms.