Executando verificação de segurança...
1

Transforme seus componentes Angular com variáveis CSS

Imagem Capa

Introdução

Como engenheiros de software sabemos que existem inúmeras maneiras de resolvermos um problema. Sabemos também que existem boas ou não tão boas soluções e que elas podem ser impactadas por diversos fatores como falta de tempo, baixo conhecimento da tecnologia utilizada no projeto, falta de documentação técnica e de negócio.

Compartilho com vocês uma solução que pode não ser a melhor, mas foi, a solução que atendeu bem a demanda impactada pela perda de conhecimento técnico e de negócio da jornada que precisava do desenvolvimento de uma funcionalidade bem pontual.


Funcionalidade

A funcionalidade em si era bem tranquila, precisávamos alterar algumas cores dos componentes de uma jornada do nosso sistema, com base em uma informação do usuário.

Após efetuarmos uma análise mais aprofundada nos projetos, identificamos que seria muito complicado implementarmos uma lógica que permitisse alterar de forma dinâmica os componentes.

Escolhemos trazer a responsabilidade da mudança dos componentes para o projeto principal. Começamos a desenhar algumas soluções, como a jornada foi desenvolvida em Angular, inicialmente consideramos criar vários arquivos de SCSS específicos para serem carregados, sempre que o usuário, com a informação necessária para troca dos componentes, efetuasse login em nosso sistema.


Uma pequena observação sobre essa jornada: ela faz parte de um projeto em uma arquitetura de “micro-frontend”, logo, essa abordagem foi desenvolvimento levando essa ponto em consideração.

Implementando Service

O dado que precisamos é disponibilizado via um BFF (Backend for Frontend) desenvolvido para essa jornada, implementamos um novo endpoint, que retorna se o cliente contém ou não a informação que precisamos para efetuarmos as alterações no componente.

Com endpoint implementado, o próximo passo foi desenvolver o service, que tem a responsabilidade de chamar o serviço sempre que fosse necessário.

@Injectable({ providedIn: 'root' })
export class ComponentService {
  constructor(
    private http: HttpClient,
  ) {}

  getComponentInfo(): Observable<ComponentModel> {
    return this.http.get<ComponentModel>('url do BFF');
  }
}


Implementando Guard

No nosso “micro-forntend” implementamos um guard, que fica responsável por solicitar ao BFF a informação do cliente, sempre que o usuário acessa uma rota específica em nosso sistema.

@Injectable({
  providedIn: 'root',
})
export default class ComponentGuard implements CanActive {
  constructor(
    private readonly componentService: ComponentService,
  ) { }

  canActive(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
    boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
    const keyName = localStorage.getItem('keyName');
    if (!keyName) {
      this.componentService.getItem().subscribe((componentType: boolean) => {
        localStorage.setItem('keyName', componentType.keyName);
        styleProperties.map((style: IStyleProperties) => {
          document.documentElement.style.setProperty(
            `--${style.nameProperty}`,
            `${componentType.type ? style.newValueProperty : style.defaultValueProperty}`
          );
        });
      });
    } else {
      styleProperties.map((style: IStyleProperties) => {
        document.documentElement.style.setProperty(
          `--${style.nameProperty}`,
          `${keyName ? style.newValueProperty : style.defaultValueProperty}`
        );
      });
    }
    return true;
  }
}

Agora entraremos um pouco mais em detalhes da implementação.


Detalhes do Guard

Verificamos que o dado não está armazenado no localStorage, efetuamos uma nova chamada para BFF, para saber se o usuário possui ou não a informação que precisamos.

this.componentService.getItem().subscribe((componentType: boolean) => {});

Depois, adicionamos novamente no localStorage.

localStorage.setItem('keyName', componentType.keyName);

Criamos um arquivo de type bem simples, que contém informações de tipagem dos objetos que usamos para criação das variáveis de forma dinâmica.

export type IStyleProperties = {
  nameProperty: string,
  newValueProperty: string,
  defaultValueProperty: string,
}

Também, criamos um arquivo que contém todos os dados que precisamos para efetuar a customização na nossa jornada.

import { IStyleProperties } from 'IStyleProperties';

export const styleProperties: IStyleProperties[] = [
  {
    nameProperty: 'component-class',
    componentMain: '#000',
    componentDefault: '#fff'
  },
  ...
];

Partimos agora para o trecho código do nosso guard, responsável por adicionar as variáveis de CSS. Usando a função map, pegamos todos os dados do arquivo de propriedades e criamos todas as variáveis necessárias para customização dos componentes.

styleProperties.map((style: IStyleProperties) => {
  document.documentElement.style.setProperty(
    `--${style.nameProperty}`,
    `${componentType.type ? style.newValueProperty : style.defaultValueProperty}`
  );
});

O document.documentElement retorna o Elemento que é o elemento raiz do documento (por exemplo, o elemento <html> para documentos HTML).

O setProperty() define um novo valor para uma propriedade em um objeto de declaração de estilo CSS.

No arquivo app.component.scss recuperamos os valores das variáveis e alteramos o que precisamos dos componentes, para esse exemplo alteramos as fontes, background e algumas cores.

:host ::ng-deep component-selector .component-class {
  background-color: var(--component-class-background-color);
  color: var(--component-class-color);
  font-size: var(--component-class-font-size)
}

:host ::ng-deep other-component-selector .other-component-class {
  border-radius: var(--other-component-class-border-radius)
}

O seletor :host, seleciona o nódulo raiz do shadow DOM, que no caso pode ser representado por ```. No exemplo ele é responsável por mudar a cor do fundo.

A aplicação da pseudoclasse ::dg-deep a qualquer regra CSS desativa completamente o encapsulamento de exibição dessa regra. Qualquer estilo com ::dg-deep aplicado torna-se um estilo global.

Por fim, implementamos o método localStorage.removeItem('keyName'); para limparmos o localStorage sempre que o usuário desloga do sistema.


Conclusão

Meu objetivo em trazer esse case é mostrar que muitas vezes pensamos sempre em entregar a melhor solução possível, para área de negócio e os usuários finais, porém, algumas dessas vezes por questões tecnológicas e por falta de conhecimento do projeto, precisamos entregar a melhor solução naquele contexto que estamos inseridos.

Espero que gostem do conteúdo, deixem o seu like, comentem e compartilhem a opinião de vocês, com dicas e sugestões de melhorias.

~> LinkedIn


Referências