#Angular

0 Seguidores · 10 Postagens

Angular (ou AngularJS) é um framework de aplicação web front-end, de código aberto, baseada em JavaScript, mantido principalmente pelo Google e por uma comunidade de indivíduos e corporações que lida com muitos dos desafios encontrados no desenvolvimento de aplicações de página única.

Site oficial.

Artigo Heloisa Paiva · Abr. 3, 2025 4m read

No artigo anterior, apresentamos o aplicativo d[IA]gnosis, desenvolvido para auxiliar na codificação de diagnósticos na CID-10. Neste artigo, veremos como o InterSystems IRIS for Health nos fornece as ferramentas necessárias para a geração de vetores a partir da lista de códigos da CID-10, usando um modelo de linguagem pré-treinado, seu armazenamento e a subsequente busca por similaridades em todos esses vetores gerados.

Introdução

1
0 33
Artigo Heloisa Paiva · Set. 28, 2024 1m read

Baseado num ótimo exemplo e workshop construído por @Luis Angel Pérez Ramos (veja artigos relacionados e o Open Exchange app relacionado), que incluiu um container local InterSystems for Health (e setup desejado), essa amostra apresentada aqui, adaptada do workshop para usar o InterSystems Cloud FHIR Server, e suas definições relacionadas.

0
0 35
Artigo Heloisa Paiva · Set. 20, 2024 4m read

Nós concluímos essa série de artigos SMART On FHIR com Auth0 e InterSystems FHIR Repository revisando nossa aplicação desenvolvida em Angular 16.

Vamos relembrar como foi definida a arquitetura da nossa solução:

Nossa aplicação front-end corresponde à segunda coluna e como você pode ver ela tem duas funções:

  1. Redirecionar a requisição de login para Auth0 e receber a resposta
  2. Enviar e receber a resposta de requisições via REST enviadas ao servidor FHIR

Angular

0
0 42
Artigo Heloisa Paiva · Set. 18, 2024 7m read

No último artigo nós apresentamos a arquitetura do nosso projeto SMART On FHIR, então agora é o momento de colocar a mão na massa e começar a configurar os elementos que vamos precisar. 

Vamos começar com Auth0.

Configuração AUTH0

Vamos começar criando uma conta Auth0 com um email válido. Uma vez registrada, vamos criar nossa primeira aplicação e faremos isso do menu a esquerda:

Em nosso exemplo, a aplicação será do tipo Single Page Web Application, como é uma aplicação desenvolvida em Angular 16. Selecionamos esta opção e clicamos em Create.

Na próxima pagina devemos definir os seguintes campos:

0
0 34
Artigo Heloisa Paiva · Set. 14, 2024 4m read

Introdução

Eu recentemente participei no "mão na massa" fantasticamente organizado pelo @Patrick Jamieson no qual uma aplicação Angular foi configurada junto com um servidor IRIS FHIR seguindo os protocolos definidos pelo SMART On FHIR e eu o achei muito interessante, então decidi desenvolver a minha própria aplicação Angular e então usar o que aprendi e publicar na comunidade.

SMART On FHIR

Vamos ver o que o Google nos conta sobre o SMART On FHIR:

0
0 70
Artigo Heloisa Paiva · Jul. 29, 2024 4m read

Com a introdução dos tipos de dados vetoriais e da funcionalidade de Vector Search em IRIS, se abre todo um mundo de possibilidades para o desenvolvimento de aplicações para nós, e um exemplo delas é a que vi recentemente publicada num concurso do Conselho de Saúde de Valencia, onde solicitavam uma ferramenta para ajudar na codificação CID-10 utilizando modelos de IA.

Como poderíamos implementar uma aplicação similar à solicitada? Vejamos o que seria necessário:

0
0 53
Artigo Danusa Calixto · Abr. 4, 2024 3m read

Olá, Desenvolvedores!

Suponha que você tenha uma classe persistente com dados e queira ter uma IU Angular simples para visualizar os dados e fazer operações CRUD.

Recentemente, @Alberto Fuentes descreveu como desenvolver uma IU Angular para seu aplicativo do InterSystems IRIS usando RESTForms2. 

Neste artigo, quero explicar a você como obter uma IU Angular simples para fazer operações CRUD e visualizar seus dados de classes do InterSystems IRIS automaticamente em menos de 5 minutos.

Vamos lá!

Para isso, você precisará do seguinte:

Usarei uma classe Data.Countries, que gerei e importei pelo csvgen usando este comando:

d ##class(community.csvgen).GenerateFromURL("https://raw.githubusercontent.com/datasciencedojo/datasets/master/WorldDBTables/CountryTable.csv",",","Data.Countries"

Para criar uma IU Angular, precisamos expor a API REST para essa classe, que servirá as operações CRUD.

Vamos usar o módulo restforms2 para isso. 

Esse comando no dockerfile instala restforms2 no contêiner IRIS:

zpm "install restforms2" \

Para adicionar uma API REST, precisamos derivar a classe de Form.Adaptor:

Class Data.Countries Extends (%Library.Persistent, Form.Adaptor)

Adicione os parâmetros restforms2 à classe persistente para gerenciar o comportamento geral: parâmetro de classificação, nome de exibição etc.:

// Nome do formulário, e não uma chave global, então pode ser qualquer coisa
Parameter FORMNAME = "Countries";

/// Permissões padrão /// Objetos desse formulário podem ser Criados, Lidos, Atualizados e Excluídos /// Redefina esse parâmetro para mudar as permissões para todo mundo /// Redefina o método checkPermission (veja Form.Security) para essa classe  /// para adicionar a segurança personalizada com base em usuário/funções/etc. Parameter OBJPERMISSIONS As %String = "CRUD";

/// Propriedade usada para informações básicas sobre o objeto /// Por padrão, o método getObjectDisplayName recebe seu valor dela Parameter DISPLAYPROPERTY As %String = "name";

Perfeito. Em seguida, podemos usar a sintaxe restforms2 para informar ao restforms2 quais propriedades queremos expor às operações CRUD. Você pode fazer isso adicionando o atributo "DISPLAYNAME =" às propriedades que você quer expor em restforms2-ui. Exemplo:

Property code As %Library.String(MAXLEN = 250) [ SqlColumnNumber = 2 ];

Property name As %Library.String(DISPLAYNAME = "Name", MAXLEN = 250) [ SqlColumnNumber = 3 ];

Property continent As %Library.String(DISPLAYNAME = "Continent", MAXLEN = 250) [ SqlColumnNumber = 4 ];

Property region As %Library.String(DISPLAYNAME = "Region", MAXLEN = 250) [ SqlColumnNumber = 5 ];

Property surfacearea As %Library.Integer(DISPLAYNAME = "Surface Area", MAXVAL = 2147483647, MINVAL = -2147483648) [ SqlColumnNumber = 6, SqlFieldName = surface_area ];

Property independenceyear As %Library.Integer(DISPLAYNAME = "Independence Year", MAXVAL = 2147483647, MINVAL = -2147483648) [ SqlColumnNumber = 7, SqlFieldName = independence_year ];

Ótimo! Agora vamos introduzir a camada de UI.  Esse comando no dockerfile instala restforms2-ui, que é a IU Angular para Restform2:

zpm "install restforms2-ui" \

É isso! Vamos examinar a IU para sua classe, que você pode encontrar no URL server:port/restforms2-ui:

RESTForms são usados com as classes de teste Person e Company, e você pode usar isso para examinar os recursos de restformsUI. No momento, é possível editar campos de string, número, booleano, data e consulta.

Você pode testar tudo isso no seu laptop, se clonar e compilar este repositório:

docker-compose up -d --build

E abrir o URL:

localhost:port/restforms2-ui/index.html

ou, se você usar o VSCode, selecione esse item do menu:

Boa programação e fique ligado!

0
0 87
Artigo Danusa Calixto · Set. 19, 2022 12m read

Olá! Hoje, eu quero falar sobre um dos padrões arquiteturais mais importantes no Angular.

O próprio padrão não está diretamente relacionado ao Angular, mas, como o Angular é um framework baseado em componentes, esse padrão é um dos mais essenciais para desenvolver aplicativos Angular modernos.

Padrão contêiner-apresentação

Acredita-se que bons componentes devem ser pequenos, focados, independentes, testáveis e, acima de tudo, reutilizáveis.

Se o componente está fazendo chamadas de servidor, contém lógica de negócio, está estreitamente acoplado a outros componentes, sabe muito sobre o funcionamento interno de outros componentes ou serviços, então ele fica maior e mais difícil de testar, ampliar, reutilizar e modificar. Para resolver esses problemas, existe o padrão "contêiner-apresentação".

Geralmente, todos os componentes podem ser divididos em dois grupos: contêiner (inteligente) e apresentação (burro).

Os componentes contêiner podem receber dados de serviços (mas não devem chamar APIs diretamente), contêm lógica de negócio e veiculam dados para serviços ou componentes filhos. Frequentemente, os componentes contêiner são aqueles que especificamos como componentes roteados na configuração de roteamento (mas, claro, nem sempre).

Os componentes de apresentação só podem receber dados e mostrar de alguma maneira na tela. Eles podem reagir com base nas entradas do usuário, mas só mudando o estado isolado local. Todas as comunicações com o resto do app devem ser feitas ao emitir eventos personalizados. Esses componentes são altamente reutilizáveis.

Para ilustrar, vou nomear alguns exemplos de componentes contêiner e de apresentação:

Contêiner: AboutPage, UserPage, AdminPanel, OrderPage, etc.

Apresentação: Button, Calendar, Table, ModalDialog, TabView, etc.

Exemplo de um botão maluco

Vamos analisar um péssimo exemplo de uso inadequado da abordagem de componentes que encontrei em um projeto real.

@Component({
  selector: 'app-button',
  template: `<button class="className" (click)=onClick()>{{label}}</button>`
})
export class ButtonComponent {
  @Input() action = '';
  @Input() className = '';
  @Input() label = '';

  constructor(
    private router: Router,
    private orderService: OrderService,
    private scanService: ScanService,
    private userService: UserService
  ) {}

  onClick() {
    if (this.action === 'registerUser') {
      const userFormData = this.userService.form.value;
      // some validation of user data
      // ...
      this.userService.registerUser(userFormData);
    } else if (this.action === 'scanDocument') {
      this.scanService.scanDocuments();
    } else if (this.action === 'placeOrder') {
      const orderForm = this.orderService.form.values;
      // some validation and business logic related to order form
      // ...
      this.orderService.placeOrder(orderForm);
    } else if (this.action === 'gotoUserAccount') {
      this.router.navigate('user-account');
    } // else if ...
  }
}

Simplifiquei para melhorar a legibilidade, mas, na realidade, estava muito pior. Esse é um componente de botão que contém todas as ações possíveis que o usuário pode invocar ao clicar no botão — realizar chamadas de API, validar formulários, buscar informações de serviços e muito mais. Você pode imaginar a rapidez com que esse componente pode se tornar um inferno até em um aplicativo relativamente pequeno. O código desse componente de botão que encontrei (e depois refatorei) tinha mais de 2 mil linhas. Insano!

Quanto eu perguntei ao desenvolvedor que escreveu o código por qual motivo ele decidiu colocar toda essa lógica em um único componente, ele disse que era um "encapsulamento" 🙀

Vamos lembrar das qualidades que um bom componente deve ter:

Pequeno - esse botão com mais de 2 mil linhas de código não é pequeno. Além disso, ele aumentará sempre que alguém precisar de outro botão para uma ação diferente.

Focado - esse botão faz várias coisas sem qualquer relação e não pode ser chamado de focado.

Independente - esse botão é estreitamente acoplado a vários serviços e formulários, e qualquer mudança neles afetará o botão.

Testável - sem comentários.

Reutilizável - ele não é nada reutilizável. Você precisará modificar o código do componente sempre que quiser usá-lo para uma ação que ele não tiver e lidará com todas as ações desnecessárias e dependências desse botão.

Além disso, esse componente de botão oculta o botão HTML nativo, bloqueando o acesso do desenvolvedor às propriedades. Esse é um ótimo exemplo de como não escrever o código de componentes.

Vamos ver um exemplo bastante simples que usa esse componente de botão e tentar refatorá-lo com o padrão contêiner-apresentação.

@Component({
  selector: 'app-registration-form',
  template: `<form [formGroup]="userService.form">
  <input type="text" [formControl]="userService.form.get('username')" placeholder="Nickname">
  <input type="password" [formControl]="userService.form.get('password')" placeholder="Password">
  <input type="password" [formControl]="userService.form.get('passwordConfirm')" placeholder="Confirm password">
  <app-button className="button accent" label="Register" action="registerUser"></app-button>
</form>
`
})
export class RegistrationFormComponent {
  constructor(public userService: UserService) {}
}

Você pode ver que não há lógica nesse componente — o formulário está armazenado em um serviço, e o botão contém toda a invocação da lógica ao clicar nele. Portanto, agora, nosso botão tem toda a lógica não relacionada ao comportamento e é mais inteligente do que o componente pai, que está diretamente relacionado às ações processadas pelo botão.

Refatorar componente de botão com padrão contêiner-apresentação

Vamos separar as funções desses dois componentes. O botão deve ser um componente de apresentação — pequeno e reutilizável. O formulário de registro que contém o botão pode ser um componente contêiner com toda a lógica de negócio e as comunicações com a camada de serviços.

Não abordarei a parte com o botão nativo visível (provavelmente em um artigo futuro), mas focarei principalmente no aspecto arquitetural da relação entre esses dois componentes.

Componente de botão refatorado (apresentação)

@Component({
  selector: 'app-button',
  template: `<button class="className" (click)=onClick()>{{label}}</button>`
})
export class ButtonComponent {
  @Input() className = '';
  @Input() label = '';

  @Output() click: EventEmitter = new EventEmitter();

  onClick() {
     this.click.emit();
  }
}

Como você pode ver, nosso botão refatorado é bastante simples. Só possui duas entradas e uma saída. As entradas são usadas para o recebimento de dados do componente pai e a exibição deles para o usuário (modifique a aparência do botão com classes e exiba o rótulo do botão). A saída é usada para o evento personalizado que será acionado sempre que o usuário clicar no nosso botão.

Esse componente é pequeno, focado, independente, testável e reutilizável. Ele não contém lógica não relacionada ao comportamento do próprio componente. Ele não tem ideia do funcionamento interno do aplicativo e pode ser importado com segurança e usado em qualquer parte do aplicativo ou até em outros aplicativos.

Componente de formulário de registro refatorado (contêiner)

@Component({
  selector: 'app-registration-form',
  template: `<form [formGroup]="userService.form">
  <input type="text" [formControl]="userService.form.get('username')" placeholder="Nickname">
  <input type="password" [formControl]="userService.form.get('password')" placeholder="Password">
  <input type="password" [formControl]="userService.form.get('passwordConfirm')" placeholder="Confirm password">
  <app-button className="button accent" label="Register" (click)="registerUser()"></app-button>
</form>
`
})
export class RegistrationFormComponent {
  constructor(public userService: UserService) {}

  registerUser() {
    const userFormData = this.userService.form.value;
    // some validation of user data
    // ...
    this.userService.registerUser(userFormData);
  }
}

Você pode ver que nosso formulário de registro agora usa botões e reage ao evento de clique com o método de chamada registerUser. A lógica desse método está estreitamente relacionada a esse formulário, então é recomendável incluir aqui.

Esse é um exemplo bastante simples e a árvore de componentes só possui dois níveis. Esse padrão apresenta alguns perigos quando a árvore de componentes tiver mais níveis.

Exemplo mais sofisticado

Esse não é um exemplo do mundo real, mas espero que ajude a entender possíveis problemas com esse padrão.

Imagine uma árvore de componentes desta forma (de cima para baixo):

user-orders - componente de nível superior. É o componente contêiner que fala com a camada de serviços, recebe os dados sobre o usuário e as ordens, transmite adiante na árvore e renderiza a lista de ordens.

user-orders-summary - componente de nível médio. É o componente de apresentação que renderiza a barra acima da lista de ordens do usuário com o número total de ordens.

cashback - componente de nível inferior (folha). É o componente de apresentação que exibe o valor total de cashback do usuário e tem um botão para transferir para a conta bancária.

Componente de contêiner de nível superior

Vamos analisar nosso componente de contêiner user-orders de nível superior.

@Component({
  selector: 'user-orders',
  templateUrl: './user-orders.component.html'
})
export class UserOrdersComponent implements OnInit {
  user$: Observable<User>;
  orders$: Observable<Order[]>;

  constructor(
    private ordersService: OrdersService,
    private userService: UserService
  ) {}

  ngOnInit() {
      this.user$ = this.userService.user$;
      this.orders$ = this.ordersService.getUserOrders();
  }

onRequestCashbackWithdrawal() {
    this.ordersService.requestCashbackWithdrawal()
      .subscribe(() => /* notification to user that cashback withdrawal has been requested */);
    }
}
<div class="orders-container">
    <user-orders-summary
        [orders]="orders$ | async"
        [cashbackBalanace]="(user$  | async).cashBackBalance"
        (requestCashbackWithdrawal)="onRequestCashbackWithdrawal($event)"
    >
    </user-orders-summary>

    <div class="orders-list">
      <div class="order" *ngFor="let order of (orders$ | async)"></div>
    </div>
</div>

Como você pode ver, o componente user-orders define 2 observables: user$ e orders$, usando async pipe no modelo para fazer a inscrição. Além de transmitir os dados para o componente de apresentação user-orders-summary, renderiza uma lista de ordens. Também se comunica com a camada de serviço reagindo ao evento personalizado requestCashbackWithdrawal emitido de user-orders-summary.

Componente de apresentação de nível médio

@Component({
  selector: 'user-orders-summary',
  template: `
    <div class="total-orders">Total orders: {{orders?.length}}</div>
    <cashback [balance]="cashbackBalanace" (requestCashbackWithdrawal)="onRequestCashbackWithdrawal($event)"></cashback>
    `, 
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserOrdersSummaryComponent {
    @Input() orders: Order[];
    @Input() cashbackBalanace: string;

    @Output() requestCashbackWithdrawal = new EventEmitter();

    onRequestCashbackWithdrawal() {
        this.requestCashbackWithdrawal.emit();
    }
}

Esse componente é projetado de maneira bastante semelhante ao componente de botão refatorado. Ele renderiza os dados recebidos pelas entradas e emite o evento personalizado com base em uma ação do usuário. Ele não chama serviços nem contém lógica de negócio. Portanto, é um componente de apresentação puro que usa outro componente de apresentação de cashback.

Componente de apresentação de nível inferior

@Component({
    selector: 'cashback',
    template: `
<div class="cashback">
  <span class="balance">Your cashback balance: {{balance}}</span>
  <button class="button button-primary" (click)="onRequestCashbackWithdrawal()">Withdraw to Bank Account</button>
</div>
`,
    styleUrls: ['./cashback.component.css']
})
export class CashackComponent {
    @Input() balance: string;

    @Output() requestCashbackWithdrawal = new EventEmitter();

    onRequestCashbackWithdrawal() {
        this.requestCashbackWithdrawal.emit();
    }
}

Esse é outro componente de apresentação que só recebe dados por entrada e gera eventos com saída. Bastante simples e reutilizável, mas há alguns problemas na árvore de componentes.

Você provavelmente percebeu que o componente user-orders-summary e cashback têm entradas semelhantes (cashbackBalanace e balance) e a mesma saída (requestCashbackWithdrawal). Isso ocorre porque nosso componente contêiner está muito longe do componente de apresentação mais profundo. Quanto mais níveis de árvore com esse design, pior será o problema. Vamos analisar os problemas mais a fundo.

Problema 1 - Propriedades extrínsecas em componentes de apresentação de nível médio

O user-orders-summary recebe a entrada cashbackBalanace para transmitir à parte inferior da árvore, mas não usa ela sozinha. Se você se deparar com essa situação, esse é um dos indicadores de que você provavelmente tem um design de árvore de componentes com falhas. Componentes na vida real podem ter várias entradas e saídas e com esse design você terá várias entradas de "proxy", que deixará os componentes de nível médio ainda menos reutilizáveis (conforme você une a componentes filhos), e repetições de código.

Problema 2 - Bubbling de eventos personalizados de componentes de nível inferior a superior

Esse problema é muito parecido com o anterior, mas está relacionado às saídas do componente. Como você pode ver, o evento personalizado requestCashbackWithdrawal está repetido nos componentes cashback e user-orders-summary. Novamente, isso ocorre porque o componente contêiner está muito longe do componente de apresentação mais profundo. Isso também impede que o componente médio seja reutilizado sozinho.

Existem pelo menos duas soluções possíveis para esses problemas.

1º – torne os componentes de nível médio mais agnósticos a conteúdo usando ngTemplateOutlet e revele os componentes mais profundos diretamente aos componentes contêiner. Pularemos isso hoje, já que merece um artigo separado.

2º – reformule a árvore de componentes.

Refatorando a árvore de componentes

Vamos refatorar nosso código para ver como podemos resolver os problemas com as propriedades extrínsecas e o bubbling de eventos no componente de nível médio.

Componente de nível superior refatorado

@Component({
  selector: 'user-orders',
  templateUrl: './user-orders.component.html'
})
export class UserOrdersComponent implements OnInit {
  orders$: Observable<Order[]>;

  constructor(
    private ordersService: OrdersService,
  ) {}

  ngOnInit() {
      this.orders$ = this.ordersService.getUserOrders();
  }
}
<div class="orders-container">
    <user-orders-summary [orders]="orders$ | async"></user-orders-summary>

    <div class="orders-list">
      <div class="order" *ngFor="let order of (orders$ | async)"></div>
    </div>
</div>

Removemos o observable user$ e o método onRequestCashbackWithdrawal() do componente contêiner de nível superior. Está muito mais simples agora e só transmite os dados necessários para renderizar o próprio componente user-orders-summary, mas não o componente filho cashback.

Componente de nível médio refatorado

@Component({
  selector: 'user-orders-summary',
  template: `
    <div class="total-orders">Total orders: {{orders?.length}}</div>
    <cashback></cashback>
    `, 
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserOrdersSummaryComponent {
    @Input() orders: Order[];
}

Também está bastante simplificado. Agora, só tem uma entrada e renderiza o número total de ordens.

Componente de nível inferior refatorado

@Component({
    selector: 'cashback',
    template: `
<div class="cashback">
  <span class="balance">Your cashback balance: {{ (user$ | async).cashbackBalance }}</span>
  <button class="button button-primary" (click)="onRequestCashbackWithdrawal()">Withdraw to Bank Account</button>
</div>
`,
    styleUrls: ['./cashback.component.css']
})
export class CashackComponent implements OnInit {
  user$: Observable<User>;

  constructor(
     private ordersService: OrdersService,
     private userService: UserService
  ) {}

  ngOnInit() {
    this.user$ = this.userService.user$;
  }

  onRequestCashbackWithdrawal() {
    this.ordersService.requestCashbackWithdrawal()
      .subscribe(() => /* notification to user that cashback withdrawal has been requested */);
    }
  }
}

Uau. Como você pode ver, não é mais de apresentação. Agora, é bastante semelhante ao componente de nível superior, então o componente contêiner está na parte inferior da árvore. Essa refatoração permitiu simplificar o design inteiro da árvore de componentes e as APIs e a lógica dos nossos dois componentes no topo da árvore.

E quanto à reusabilidade do novo componente cashback? Ainda é reutilizável, já que contém apenas a lógica relacionada ao próprio componente, então permanece independente.

O novo design da nossa árvore de componentes parece ser mais fácil de manter, mais otimizada e atomizada. Não há mais bubbling de eventos e entradas repetidas na árvore de componentes, e o design geral está muito mais simples. Conseguimos isso ao colocar o componente contêiner adicional na parte inferior da árvore de componentes. Esse método pode ser usado para simplificar o design da sua árvore de componentes, mas você precisa ter uma boa compreensão de quais componentes na árvore podem ser contêineres sem grande perda na reusabilidade e quais devem ser apenas componentes de apresentação. Essa é sempre uma questão de equilíbrio e escolhas design ao criar a arquitetura do app.

É muito fácil entender errado o padrão contêiner-apresentação e pensar que componentes contêiner só podem ser de nível superior (intuitivamente, eles contêm todos os outros componentes na árvore de componentes local). No entanto, esse não é o caso. Os componentes contêiner podem estar em qualquer nível da árvore de componentes e, como você viu, até no nível da folha. Gosto de chamá-los de componentes inteligentes, porque, para mim, é muito claro que eles terão lógica de negócio e poderão estar em qualquer lugar da árvore de componentes.

Palavras finais

Espero que agora você tenha uma visão melhor do padrão contêiner-apresentação e dos possíveis problemas da implementação.

Tentei manter o mais simples possível, mas há muitas informações disponíveis relacionadas a esse padrão.

Em caso de dúvidas ou observações, não hesite em entrar em contato nos comentários.

O próximo artigo será sobre changeDetectionStrategy no Angular (que tem grande relação com esta postagem).

Até mais!

0
0 462
Artigo Danusa Calixto · Set. 7, 2022 9m read

Olá! Meu nome é Sergei Sarkisian e crio o front-end do Angular há mais de 7 anos trabalhando na InterSystems. Como o Angular é um framework bastante popular, ele é geralmente escolhido pelos nossos desenvolvedores, clientes e parceiros como parte da pilha para seus aplicativos.

Quero começar uma série de artigos sobre diferentes aspectos do Angular: conceitos, instruções, práticas recomendadas, tópicos avançados e muito mais. Essa série será destinada às pessoas que já estão familiarizadas com o Angular e não abordará conceitos básicos. Como ainda estou no processo de planejamento dos artigos, queria começar destacando alguns recursos importantes da versão mais recente do Angular.

Formulários com tipos estritos

Provavelmente, esse é um dos recursos mais desejados do Angular nos últimos anos. Com o Angular 14, os desenvolvedores agora podem usar toda a funcionalidade de verificação de tipos estritos do TypeScript com os formulários reativos do Angular.

A classe FormControl é agora genérica e assume o tipo do valor que detém.

/* Antes do Angular 14 */
const untypedControl = new FormControl(true);
untypedControl.setValue(100); // o valor está definido, sem erros

// Agora
const strictlyTypedControl = new FormControl<boolean>(true);
strictlyTypedControl.setValue(100); // você receberá a mensagem de erro de verificação de tipo aqui

// Também no Angular 14
const strictlyTypedControl = new FormControl(true);
strictlyTypedControl.setValue(100); // você receberá a mensagem de erro de verificação de tipo aqui

Como você pode ver, o primeiro e o último exemplos são quase iguais, mas têm resultados diferentes. Isso ocorre porque, no Angular 14, a nova classe FormControl deduz tipos do valor inicial informado pelo desenvolvedor. Portanto, se o valor true foi fornecido, o Angular define o tipo boolean | null para FormControl. O valor anulável é necessário para o método .reset(), que anula os valores se nenhum for fornecido.

Uma classe FormControl antiga e sem tipo foi convertida para UntypedFormControl (isso também se aplica para UntypedFormGroup, UntypedFormArray e UntypedFormBuilder), que é basicamente um codinome para FormControl<any>. Se você estiver fazendo upgrade de uma versão anterior do Angular, todas as menções à classe FormControl serão substituídas pela classe UntypedFormControl pela CLI do Angular.

As classes sem tipo* são usadas com metas específicas:

  1. Fazer seu app funcionar da mesma maneira como era antes da transição da versão anterior (lembre-se de que o novo FormControl deduzirá o tipo a partir do valor inicial).
  2. Verificar se todos os usos de FormControl<any> são desejados. Portanto, você precisará mudar qualquer UntypedFormControl para FormControl<any> por conta própria.
  3. Dar aos desenvolvedores mais flexibilidade (abordaremos isso abaixo).

Lembra-se de que, se o valor inicial for null, você precisará especificar explicitamente o tipo FormControl. Além disso, o TypeScript tem um bug que exige que você faça o mesmo se o valor inicial for false.

Para o grupo do formulário, você também pode definir a interface e transmitir essa interface como um tipo para FormGroup. Nesse caso, TypeScript deduzirá todos os tipos dentro de FormGroup.

interface LoginForm {
    email: FormControl<string>;
    password?: FormControl<string>;
}

const login = new FormGroup<LoginForm>({
    email: new FormControl('', {nonNullable: true}),
    password: new FormControl('', {nonNullable: true}),
});

O método .group() do FormBuilder agora tem um atributo genérico que pode aceitar sua interface predefinida, como no exemplo acima, em que criamos manualmente o FormGroup:

interface LoginForm {
    email: FormControl<string>;
    password?: FormControl<string>;
}

const fb = new FormBuilder();
const login = fb.group<LoginForm>({
    email: '',
    password: '',
});

Como nossa interface só tem tipos primitivos não anuláveis, ela pode ser simplificada com a nova propriedade nonNullable do FormBuilder (que contém a instância da classe NonNullableFormBuilder, também criada diretamente):

const fb = new FormBuilder();
const login = fb.nonNullable.group({
    email: '',
    password: '',
});

❗ Se você usar o FormBuilder nonNullable ou definir a opção nonNullable no FormControl, quando chamar o método .reset(), ele usará o valor inicial do FormControl como um valor de redefinição.

Além disso, também é muito importante observar que todas as propriedades em this.form.value serão marcadas como opcionais. Desta forma:

const fb = new FormBuilder();
const login = fb.nonNullable.group({
    email: '',
    password: '',
});

// login.value
// {
//   email?: string;
//   password?: string;
// }

Isso ocorre porque, quando você desativa qualquer FormControl dentro do FormGroup, o valor desse FormControl será excluído do form.value

const fb = new FormBuilder();
const login = fb.nonNullable.group({
    email: '',
    password: '',
});

login.get('email').disable();
console.log(login.value);

// {
//   password: ''
// }

Para obter todo o objeto do formulário, você precisa usar o método .getRawValue():

const fb = new FormBuilder();
const login = fb.nonNullable.group({
    email: '',
    password: '',
});

login.get('email').disable();
console.log(login.getRawValue());

// {
//   email: '',
//   password: ''
// }

Vantagens de formulários com tipos estritos:

  1. Qualquer propriedade e método que retorna valores do FormControl / FormGroup é agora estritamente tipado. Por exemplo: value, getRawValue(), valueChanges.
  2. Qualquer método de mudança do valor do FormControl é agora seguro para os tipos: setValue(), patchValue(), updateValue()
  3. Os FormControls têm agora tipos estritos. Isso também se aplica ao método .get() do FormGroup. Isso evitará que você acesse FormControls inexistentes no momento da compilação.

Nova classe FormRecord

A desvantagem da nova classe FormGroup é que ela perdeu sua natureza dinâmica. Após a definição, você não poderá adicionar ou remover FormControls dela rapidamente.

Para resolver esse problema, o Angular apresenta uma nova classe — FormRecord. FormRecord é praticamente igual ao FormGroup, mas é dinâmico e todos os seus FormControls devem ter o mesmo tipo.

folders: new FormRecord({
  home: new FormControl(true, { nonNullable: true }),
  music: new FormControl(false, { nonNullable: true })
});

// Adicione o novo FormContol ao grupo 
this.foldersForm.get('folders').addControl('videos', new FormControl(false, { nonNullable: true }));

// Isso gerará um erro de compilação, já que o controle tem um tipo diferente
this.foldersForm.get('folders').addControl('books', new FormControl('Some string', { nonNullable: true }));

Como você pode ver, há uma outra limitação — todos os FormControls precisam ter o mesmo tipo. Se você realmente precisar de um FormGroup dinâmico e heterogêneo, deverá usar a classeUntypedFormGroup para definir seu formulário.

Componentes sem módulos (individuais)

Apesar de ainda ser considerado experimental, esse é um recurso interessante. Ele permite definir componentes, diretivas e pipes sem incluí-los em qualquer módulo.

O conceito ainda não está totalmente pronto, mas já conseguimos desenvolver um aplicativo sem ngModules.

Para definir um componente individual, você precisa usar a nova propriedade standalone no decorator Component/Pipe/Directive:

@Component({
  selector: 'app-table',
  standalone: true,
  templateUrl: './table.component.html'
})
export class TableComponent {
}

Nesse caso, o componente não pode ser declarado em qualquer NgModule. No entanto, ele pode ser importado em NgModules e outros componentes individuais.

Cada componente/pipe/diretiva individual agora tem um mecanismo para importar as dependências diretamente no decorator:

@Component({
  standalone: true,
  selector: 'photo-gallery',
  // um módulo existente é importado diretamente em um componente individual
  // CommonModule é importado diretamente para usar diretivas padrão do Angular como *ngIf
  // o componente individual declarado acima também é importado diretamente
  imports: [CommonModule, MatButtonModule, TableComponent],
  template: `
    ...
    <button mat-button>Next Page</button>
    <app-table *ngIf="expression"></app-table>
  `,
})
export class PhotoGalleryComponent {
}

Como mencionei acima, você pode importar componentes individuais em qualquer ngModule existente. Não é mais necessário importar todo o sharedModule. Podemos importar somente o que é realmente necessário. Essa também é uma boa estratégia para começar a usar novos componentes individuais:

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpClientModule, TableComponent], // import our standalone TableComponent
  bootstrap: [AppComponent]
})
export class AppModule {}

Você pode criar um componente individual com a CLI do Angular ao digitar:

ng g component --standalone user

Aplicativo Bootstrap sem módulos

Se você quiser se livrar de todos os ngModules do seu aplicativo, você precisa usar o bootstrap de maneira diferente. O Angular tem uma nova função para isso que você precisa chamar no arquivo main.ts:

bootstrapApplication(AppComponent);

O segundo parâmetro dessa função permitirá definir os fornecedores necessários em todo o app. Como a maioria dos fornecedores geralmente existe em módulos, o Angular (por enquanto) exige o uso de uma nova função de extração importProvidersFrom para eles:

bootstrapApplication(AppComponent, { providers: [importProvidersFrom(HttpClientModule)] });

Rota de componente individual de lazy load:

O Angular tem uma nova função loadComponent de rota de lazy loading, que serve exatamente para o carregamento de componentes individuais:

{ 
  path: 'home',
  loadComponent: () => import('./home/home.component').then(m => m.HomeComponent)
}

Agora, loadChildren não só permite o lazy load de ngModule, mas também carrega rotas filhas diretamente do arquivo de rotas:

{ 
  path: 'home',
  loadChildren: () => import('./home/home.routes').then(c => c.HomeRoutes)
}

Algumas observações no momento da redação do artigo

  • O recurso de componentes individuais ainda está em fase experimental. Ela receberá melhorias no futuro com a migração para Vite builder em vez de Webpack, ferramentas otimizadas, tempos de desenvolvimento mais rápidos, arquitetura de app mais robusta, testes mais fáceis e muito mais. Por enquanto, várias dessas coisas estão faltando, então não recebemos o pacote completo, mas pelo menos podemos começar a desenvolver nossos apps com o novo paradigma do Angular em mente.
  • Os IDEs e ferramentas do Angular ainda não estão totalmente prontos para analisar estatisticamente novas entidades individuais. Já que é necessário importar todas as dependências em cada entidade individual, se você deixar algo passar, o compilador pode também não perceber e falhar no tempo de execução. Isso melhorará com o tempo, mas agora as importações exigem maior atenção dos desenvolvedores.
  • Não há importações globais no Angular no momento (como em Vue, por exemplo), então você precisa importar cada uma das dependências em todas as entidades individuais. Espero que isso seja solucionado em uma versão futura, já que o principal objetivo desse recurso a meu ver seja reduzir o boilerplate e facilitar as coisas.

Isso é tudo por hoje. Até mais!

0
0 390
Artigo Danusa Calixto · Ago. 26, 2022 9m read

Antes de começarmos com alguns tópicos intermediários e avançados, gostaria de resumir alguns pontos mais gerais. Eles são subjetivos, é claro, então ficarei feliz em discuti-los se você tiver outra opinião ou argumentos melhores para qualquer um deles.

A lista não é abrangente e isso é intencional, pois abordarei alguns tópicos em artigos futuros.

Dica 1. Siga o guia de estilo oficial

Angular é bastante rigoroso em termos de limitação da arquitetura possível de um aplicativo, mas ainda há muitos lugares nele que permitem que você faça as coisas do seu jeito. A imaginação dos desenvolvedores é ilimitada, mas às vezes dificulta o trabalho de quem trabalha no projeto com você ou depois de você.

A equipe Angular faz um bom trabalho mantendo a própria arquitetura Angular e suas bibliotecas, então eles definitivamente sabem como criar uma base de código estável e suportável.

Eu recomendo seguir seu guia de estilo oficial e se desviar dele apenas se as coisas não funcionarem dessa maneira. Isso tornará as coisas mais fáceis quando você chegar a um novo projeto ou se alguém entrar em seu projeto.

Lembre-se, código e arquitetura de aplicativo com suporte, estável e fácil de entender são mais importantes do que soluções inteligentes, mas enigmáticas, do que ninguém será capaz de acompanhar (possivelmente até você no futuro).

Guia oficial de estilo Angular: https://angular.io/guide/styleguide

Dica 2. Considere comprar o livro Angular Ninja

Você pode pensar que isso é um anúncio, mas é o seguinte: esse é um livro muito bom com todos os principais conceitos de Angular cobertos e o preço fica a seu critério. Você também pode escolher quanto dinheiro irá para escritores e quanto irá para caridade.

Um dos autores deste livro é membro do time Angular, então esta é definitivamente a fonte de informação mais confiável sobre o Angular após a documentação oficial. Você pode ver os capítulos do livro na página do livro e ler capítulos de amostra para decidir se vale a pena seu investimento.

Além disso, o livro é atualizado com o lançamento de uma nova versão do Angular e você obtém todas as atualizações do livro gratuitamente.

O próprio blog do Ninja Squad é uma fonte muito boa de informações sobre Angular, com artigos e notícias sobre novas versões, melhores práticas, recursos experimentais e muito mais.

Livro Angular Ninja: https://books.ninja-squad.com/angular

Dica 3. Leia a documentação oficial

Antes de mergulhar na escrita de algum código do seu aplicativo, é uma boa ideia ler a documentação e os guias oficiais, especialmente se você iniciar seu projeto em uma versão do Angular que não usou antes. As coisas continuam sendo obsoletas o tempo todo, tutoriais e guias na Internet podem estar desatualizados e você pode acabar aumentando sua dívida técnica em vez de usar novas práticas e funcionalidades recomendadas.

Também é um bom hábito verificar para qual versão o guia foi escrito. Se for a versão 2+ atrás da que você está usando, é melhor verificar se as coisas mudaram desde então.

Documentação oficial: https://angular.io

Dica 4. Considere usar o Angular CDK mesmo que você não use o Angular Material

Falarei melhor em artigos futuros, mas sei que muitos desenvolvedores Angular nem conhecem o Angular CDK.

Angular CDK é uma biblioteca de diretivas úteis e classes base que podem ajudá-lo a desenvolver melhores aplicativos. Por exemplo, tem coisas como FocusTrap, Drag & Drop, VirtualScroll e muito mais, que podem ser facilmente adicionados aos seus componentes

CDK angular: https://material.angular.io/cdk/categories

Dica 5. Corrija suas dependências no package.json

Não está particularmente relacionado ao Angular e pode ser importante em qualquer projeto. Quando você faz npm install --save <something> , ele será adicionado ao seu package.json com ^ ou ~ no início da versão do pacote. Isso significa que seu projeto poderá usar qualquer versão secundária/patch da mesma versão principal da dependência. Isso vai dar errado no futuro com quase 100% de probabilidade. Eu enfrentei esse problema em diferentes projetos tantas vezes. Tempo passando e nova versão menor de alguma dependência saindo e seu aplicativo não será mais compilado. Porque um autor desta dependência atualizou suas dependências em versão menor (ou mesmo patch) e agora elas estão em conflito com sua compilação. Ou pode haver um novo bug apresentado em uma nova versão menor de sua dependência e você não tem problemas em seu código (porque você instalou suas dependências antes da nova versão aparecer), mas qualquer outra pessoa tentando instalar e compilar seu projeto a partir do repositório irá enfrente o bug que você nem conhece (e não poderá reproduzi-lo em sua máquina).

O package-lock.json existe para resolver esse problema e ajudar os desenvolvedores a ter o mesmo conjunto de dependências em todo o projeto. Idealmente, ele deve ser comprometido com o repositório, mas muitos desenvolvedores estão decidindo adicionar esse arquivo ao .gitignore. E se acabou, você pode acabar com os problemas acima. Portanto, é melhor não confiar cegamente na resolução de suas dependências e corrigi-la na versão específica, escaneá-la regularmente em busca de vulnerabilidades (com npm audit) e depois atualizá-la e testá-la manualmente.

Dica 6. Tente atualizar seu aplicativo para a nova versão do Angular assim que puder

Angular está evoluindo continuamente e novas versões são lançadas a cada 6 meses ou mais. Manter seu aplicativo atualizado permitirá que você use todos os novos recursos, incluindo compilações mais rápidas e outras otimizações. Além disso, atualizar para a próxima versão principal do Angular não deve ser um grande problema neste caso. Mas se você está 5 versões atrás e seu aplicativo é de tamanho médio ou grande, o processo de atualização para a última versão pode ser difícil, pois o Angular não possui esquemas para atualizar aplicativos pulando versões intermediárias do Angular. Você precisará atualizar todas as versões intermediárias uma a uma, verificando se o aplicativo funciona em cada versão. A atualização direta sem os esquemas da CLI do Angular é possível, mas também pode ser complicada, então sugiro manter seu aplicativo atualizado e atualizado.

Dica 7. Tente reduzir sua lista de dependências

É fácil cair no hábito de trazer novas dependências em seu aplicativo, pois você precisa de novas funcionalidades. Mas com cada dependência, a complexidade do seu aplicativo está crescendo e o risco de uma avalanche de dívida técnica repentina está aumentando.

Se você decidiu adicionar uma nova dependência em seu aplicativo, pense nisso:

  • Quão bem suportada é a dependência?
  • Quantas pessoas estão usando?
  • Qual o tamanho da equipe de desenvolvimento?
  • Com que rapidez eles estão fechando os problemas do GitHub?
  • Como a documentação da dependência está escrita?
  • Com que rapidez ele é atualizado após o lançamento do novo major de Angular?
  • Qual o impacto dessa dependência no desempenho e no tamanho do pacote do seu aplicativo?
  • Quão difícil será substituir essa dependência se ela for abandonada ou preterida no futuro?

Se algumas dessas perguntas tiverem resposta de tom negativo, considere se livrar dela ou pelo menos substituí-la por algo mais maduro e bem fundamentado.

Dica 8. Não use <any> como seu tipo “temporário”

Novamente, é fácil começar a usar qualquer um enquanto você escreve sua lógica de negócios, porque escrever tipos apropriados pode ser demorado e você precisa terminar sua tarefa neste sprint. Os tipos podem ser adicionados mais tarde, é claro. Mas é escorregadio aumentar a dívida técnica.

Uma arquitetura de seu aplicativo e tipos devem ser definidos antes de escrever qualquer lógica de negócios. Você deve entender claramente quais objetos você terá e onde eles serão usados. Especificação antes do código, certo? (Tom Demarco escreveu um livro sobre isso antes de se tornar popular: [https://www.amazon.com/Deadline-Novel-About-Project-Management/dp/0932633390](https://www.amazon.com/Deadline -Novel-About-Project-Management/dp/0932633390)).

Se você estiver escrevendo o código sem tipos predefinidos, poderá acabar com a pior arquitetura de aplicativo e funções que usam objetos muito semelhantes, mas diferentes. Portanto, você precisará criar tipos diferentes para cada função (o que fará as coisas ainda piores) ou gastar seu tempo escrevendo especificações, tipos de refatoração E , o que é uma perda de tempo comparado a se tivesse sido feito antes.

Dica 9. Gaste seu tempo para entender o processo de construção

Angular faz um ótimo trabalho para facilitar o trabalho do desenvolvedor em relação à construção do projeto. Mas as opções padrão nem sempre são as melhores para todos os casos.

Gaste seu tempo para entender como o processo de compilação funciona no Angular, quais as diferenças entre as compilações de Desenvolvimento e Produção, quais opções o Angular tem para compilações (como otimizações, mapas de origem, agrupamento e muito mais).

Dica 10. Investigue o que está dentro do seu pacote

Nem todas as bibliotecas fornecem trepidação de árvores e nem sempre importamos da maneira certa, então sempre há uma chance de que algo redundante seja empacotado com nosso aplicativo.

Portanto, é um bom hábito investigar o conteúdo do seu pacote de tempos em tempos.

Existem bons artigos descrevendo o processo usando webpack-bundle-analyzer , então não vou abordar aqui, mas aqui está um link para um deles: https://www.digitalocean.com/community/tutorials/angular-angular-webpack-bundle-analyzer

Abordarei este tópico mais detalhadamente mais adiante na série.

Dica 11. Use a propriedade services providedIn ao invés de importar o serviço no módulo

Muitos exemplos de código Angular na Internet usam a importação dos serviços nos módulos. Mas não é a maneira preferida de declarar serviços desde o Angular 6 ou 7. Devemos usar a propriedade do decorador @InjectableprovidedIn para habilitar o tree-shake e melhor agrupamento de nossos aplicativos. Angular é inteligente o suficiente para entender em qual pacote o serviço deve ser incluído, quando deve ser inicializado e quantas instâncias do serviço devem ser criadas.

Existem três valores que providedIn aceita. Na maioria das vezes root é suficiente, mas existem mais dois:

  • root: o serviço será singleton no escopo de aplicação
  • any: uma instância do serviço será criada para todos os módulos carregados ansiosamente, com uma instância diferente criada para cada módulo lento
  • plataforma: o serviço será singleton para todas as aplicações rodando na mesma página

Dica 12. Não esqueça das principais regras de desempenho

  • Use trackBy para coleções para reduzir operações de redesenho e execução de JavaScript
  • Use a estratégia de detecção de alterações onPush em todos os lugares que puder (abordarei no artigo dedicado)
  • Realize cálculos pesados fora do ngZone
  • Use padrões de aceleração e debounce com seus eventos para evitar chamadas de servidor desnecessárias e inundação de eventos
  • Use a rolagem virtual para exibir conjuntos de big data
  • Use tubos puros para transformação de dados em seus modelos
  • Use a compilação AoT
  • Carregar lentamente as partes do seu aplicativo que não são necessárias para o início do aplicativo
  • Evite cálculos e chamadas de função condicionais em seus templates (chamadas de função devem ser usadas apenas para eventos)

Obrigado por ler! Espero que algumas dessas dicas tenham sido úteis para você. Se você tiver quaisquer comentários e observações, por favor, deixe-me saber nos comentários, ficarei feliz em discutir 😃

Até mais!

0
0 106