Angular pra quem vem do AdvPL

Ricardo Mansano Godoi
TOTVS Developers
Published in
13 min readAug 5, 2019

Todo começo é difícil e, para quem vem do AdvPL, entender o “jeitão” de programar utilizando SPAs e WebComponentes pode parecer confuso. Nesse post eu vou mostrar como iniciar um projeto Angular, e as similaridades e diferenças em relação à programação procedural, como a que é usada em AdvPL.

Mesmo focando no desenvolvedor AdvPL, esse material será um bom ponto de partida para quem nunca brincou com esse Framework.

Nestes links os vídeos sobre este conteúdo:
Vídeo 1: https://youtu.be/_lb1Mcsdg_w
Vídeo 2: https://youtu.be/TxOVmyjDhQQ

Pretendo na sequência montar o mesmo conteúdo sobre o React.

Um canal excelente para quem quer se especializar em Angular é o da Loiane Groner.

Para acompanhar esse conteúdo, colocando a mão na massa, você vai precisar de:

  1. Um ambiente Protheus configurado
  2. Um ambiente Angular previamente instalado

Instalando o Angular

Vamos começar com a parte divertida, instalando o Angular e rodando seu exemplo padrão.

Usaremos o NPM para criar e manipular o projeto. Caso esteja utilizando Windows, baixe o nodejs do site nodejs.org, dê preferência para versão recomendada, pois é a mais estável.

A instalação segue o modelo next-finish.

Para Linux, instale à partir do comando sudo apt install npm. Isso, obviamente, se utilizar um Linux com o gerenciador de pacotes APT.

A criação do projeto e sua compilação são feitas via linha de comando, então é bom se acostumar com ela, mesmo que esteja usando o Windows.

Crie uma pasta onde iremos iniciar o projeto, e acesse essa pasta via linha de comando (cmd/terminal). Exemplo: Crie a pasta projs que será a raiz de vários projetos.

Nos comandos a seguir, caso esteja utilizando Windows, apenas retire o bom e velho sudo.

Vamos instalar o Angular a partir do comando:A

sudo npm install -g @angular/cli

Para confirmar se a instalação foi bem sucedida, use este comando:

ng --version

Para criar o projeto, que no exemplo será o myProj1, utilizamos o seguinte comando:

sudo ng new myProj1 --routing --style=css

O --routing é o responsável pelo roteamento de páginas, permitindo o Single Page Application;

e --style=css definirá o CSS como página estilo padrão do projeto.

O Angular não aceita underline como parte do nome, então um projeto com nome my_proj1 não é válido.

Após criar o projeto, entramos no diretório.

cd myProj1

Vamos instalar o hammerjs, que é necessário para algumas animações e para ações de touch.

sudo npm i --save hammerjs

Faremos a primeira execução, já abrindo o navegador, através do comando:

ng serve --open

Neste momento, o projeto será compilado e exibido no navegador.

Com um Ctrl+C você encerra o ng serve. Abra então o VSCode neste mesmo diretório, digitando:

code .

Roteamento

O mais importante sobre uma Single Page Application é entender como rotear uma página, trocando partes dela sem a necessidade de recarga.

Abrindo o arquivo /src/app/app.component.html de seu projeto verá a tag router-outlet, que controla a recarga. Vamos limpar este arquivo, colocando dois botões que usaremos para testar o roteamento, como no modelo abaixo:

<div id=”App”>
<button [routerLink]=”[‘’]”>
Home
</button>
<button [routerLink]=”[‘nivers’]”>
Nivers
</button>
</div>
<router-outlet></router-outlet>

Agora, abra uma sessão do Terminal no VSCode e repita o comando abaixo.

ng serve --open

A partir daí, todas as alterações salvas serão automaticamente recarregadas no navegador, facilitando o desenvolvimento.

Você verá os dois botões em tela, mas eles ainda não terão ações pois não criamos nem os componentes nem o roteamento, então vamos lá!

Vamos criar os componentes home e nivers. Abra um segundo terminal no VSCode e digite os dois comandos a seguir:

ng generate component home
ng generate component nivers

Ao término, os componentes estarão criados

Acessando o arquivo /src/app/app.module.ts esses componentes já estarão referenciados:

Neste mesmo arquivo vamos inserir o trecho responsável pelo roteamento:

import { Routes, RouterModule } from ‘@angular/router’;
import { ModuleWithProviders } from ‘@angular/compiler/src/core’;
const APP_ROUTES: Routes = [
{ path: ‘’, component: HomeComponent },
{ path: ‘nivers’, component: NiversComponent }
];
export const routing: ModuleWithProviders = RouterModule.forRoot(APP_ROUTES);

E na tag imports precisamos inserir o routing:

imports: [
routing,

Que ficará assim:

Salvando este arquivo, automaticamente será exibido o texto padrão do componente home, isso porque nós o definimos como a página raiz, indicando o path deste roteamento como vazio:

A partir daí, pressionando os botões você verá o roteamento operando.

E pronto! Você aprendeu a rotear páginas usando o Angular…

Baixando e executando os projetos

Agora que conhecemos o básico podemos começar a comparar os projetos.

Baixe aqui o exemplo em Angular

Vamos criar uma nova pasta e descompactar esse projeto. Exemplo:

niversAngular

Você pode inicializar um projeto facilmente, pois todas as suas dependências ficam no arquivo package.json. Vá para a pasta do projeto e digite:

npm install

Na sequência, compile o projeto, lembrando que não poderá abrir dois na mesma porta (a padrão é a 4200):

ng serve --open

Caso precise executar dois projetos em paralelo, use o --port 4201, 4202, e por aí vai:

ng serve --open --port 4201

Executando o projeto, veremos a tela a seguir:

Navegue pelo menu e pelos botões para conhecer o exemplo:

Chegou a hora do exemplo em AdvPL.

Baixe aqui exemplo em AdvPL

Compile o fonte e as imagens, e execute a função u_niversAdvPL. Aqui, vou partir do princípio que você já conhece o ecossistema Protheus.

Comparando o Angular com o AdvPL

Para facilitar nossa vida durante a comparação, eu inseri tags nos códigos, bastando pesquisar em cada uma delas. Todas se iniciam por [* , veja um exemplo:

A criação da barra de menus em AdvPL:

Agora em Angular:

Algumas dessas tags não terão referência direta no AdvPL, pois falamos de paradigmas de programação bem diferentes.

Material Design — Tag: [*Material_UI]
Essa tag não tem referência em AdvPL.

Aqui habilitamos o Material Design em nosso projeto. Todas as dependências estão no package.json do projeto que baixaram, mas caso precisem criar um do zero é importante estudar o link abaixo:

https://material.angular.io/guide/getting-started

Abaixo o comando para inclusão das bibliotecas em seu projeto.

npm install --save @angular/material @angular/cdk @angular/animations

/src/app/app.module.ts
O primeiro passo é habilitar o uso do Material Design para cada componente necessário, como ícones, toggles, inputs, etc. O vetor MaterialElements foi criado para podermos importar todos os componentes em uma única chamada.

/src/index.html
Aqui, habilitamos o uso dos ícones do Material:

<!-- [*Material_UI] Necessário pra utilizar os icones do Material -->
<link href=”https://fonts.googleapis.com/icon?family=Material+Icons" rel=”stylesheet”>

/src/styles.css
Aqui, o tema que utilizaremos:

/* [*Material_UI] O CSS deve ficar aqui para habilitar a animacao dos botoes */
@import ‘@angular/material/prebuilt-themes/indigo-pink.css’;

Roteamento — Tag: [*Router]
Essa tag não tem referência em AdvPL.

Como já falamos sobre o roteamento, segue aqui os fontes onde devem procurar a tag:

/src/app/app.modules.ts
/src/app/app.component.html

Componentes SPA — Tag: [*SPA]
Essa tag não tem referência em AdvPL.

Já vimos como criar novos componentes no Angular, e cada um deles é referenciado automaticamente no arquivo:

/src/app/app.module.ts

// [*SPA] Componentes da aplicacao
import { HomeComponent } from ‘./home/home.component’;
import { NiversComponent, NiverComponentItem } from ‘./nivers/nivers.component’;
import { HooksComponent } from ‘./hooks/hooks.component’;

Formulários — Tag: [*Form]

/src/app/app.module.ts
No Angular precisamos habilitar o FormsModule para interagir com formulários.

import { FormsModule } from ‘@angular/forms’; // [*Form] Necessario para utilizar os Forms HTML

/src/app/nivers/nivers.component.html
Aqui, criamos efetivamente o formulário na sub-rotina de Aniversários.

<!-- [*Form] ngNativeValidate é necessário para manter a validacao padrao de preenchimento -->
<form #niverForm=”ngForm” ngNativeValidate (ngSubmit)=”onClickSubmit($event, niverForm)” >
<mat-form-field>
<mat-label>Aniversariante</mat-label>
<input matInput required type=”text” name=”person” ngModel>
</mat-form-field>

niversAdvPL.prw
Em AdvPL utilizamos os componentes do tipo TSay, TGet e TButton (entre outros) para criação do form.

// [*Form] — Formulario para inclusao dos aniversarios
DEFINE MSDIALOG oDlg TITLE “Nivers” FROM 000, 000 TO 320, 500 COLORS 0, 16777215 PIXEL
@ 014, 003 MSGET oGet1 VAR cAniversariante SIZE 060, 010 OF oDlg

Inclusão do registro — Tag: [*Submit]

/src/app/nivers/nivers.component.ts
Ainda na sub-rotina de Aniversários, efetuamos uma “pseudo” inclusão em um Json

// [*Submit]
onClickSubmit = (e, form) => {
this.nivers.push({ list: [form.value.birthday, form.value.person] } )
// Limpa o valor do campo depois do submit
form.reset()
e.preventDefault();
}

niversAdvPL.prw
Em AdvPL, eu simplesmente inseri um item em um TMultiGet, pois, nesse post, nosso foco é o Angular.

static function niversInsert(oMultiGe1, cAniversariante, dNiver)
// [*Submit] Insere aniversarios na lista
oMultiGe1:appendText(chr(10) + trim(cAniversariante) +” | “+ DtoC(dNiver))

Deleção do item — Tag: [*Delete_Item]
Essa tag não tem referência em AdvPL.

/src/app/nivers/nivers.component.ts
A deleção é feita retirando o item do Json

// [*Delete_Item]
delNiver = (idx) => {
this.nivers.splice(idx, 1)
}

Loop de criação de componentes — Tag: [*LoopCreation]
Essa tag não tem referência em AdvPL.

/src/app/nivers/nivers.component.html
No Angular, podemos contar com o ngFor para criar em cadeia uma série de componentes e, assim, cada item inserido no Json é exibido em tela.

<!-- [*LoopCreation] No Angular o ngFor eh usado pra executar um Loop de criacao de componentes 
neste momento usamos criando um componente niver-item para cada item do Vetor nivers -->
<niver-item
*ngFor=”let nivers of this.nivers | keyvalue”
idx={{nivers.key}}
birthday={{nivers.value.list[0]}}
person={{nivers.value.list[1]}}
>
</niver-item>

Customização de Estilos — Tag: [*CustomButton]

/src/app/nivers/nivers.component.css
No Angular o primeiro passo é inserir o estilo

/* [*CustomButton] Definicao do CSS */
.mat-CustomButton {
color: #fff;
background-color: #007bff;
}
.mat-CustomButton:hover{
background-color: #0069d9;
}

/src/app/nivers/nivers.component.html
Aqui, definimos o uso do estilo. Atentem que ele é criado com o nome .mat-CustomButtom e “utilizado” como CustomButton.

<!-- [*CustomButton] Utilizando o CSS no [color] -->
<button mat-flat-button [color]=”’CustomButton’”>INSERIR</button>

niversAdvPL.prw
Em AdvPL, a definição do CSS pode ser feita através do método SetCss.

@ 014, 145 BUTTON oButton1 PROMPT “INSERIR” SIZE 054, 012 OF oDlg PIXEL Action … 
/* [*CustomButton] Definicao do CSS */
oButton1:SetCss(;
“QPushButton {“+;
“ border: none;”+;
“ color: #fff;”+;
“ background-color: #007bff;}”+;
“QPushButton:hover {“+;
“ background-color: #0069d9;}”)

Barra superior — Tag: [*AppBar]

/src/app/app.component.html
No Angular, como já comentei, usamos o Material Design, e o webcomponent mat-toolbar faz parte desta biblioteca.

<!-- [*AppBar] -->
<mat-toolbar color=”primary” style=”max-height: 50px; padding: 0;”><div>
<button mat-icon-button [matMenuTriggerFor]=”mainMenu”>

niversAdvPL.prw
Em AdvPL, usamos o componente TBar, já definindo seu estilo e seus botões.

// [*AppBar]
oTBar := TBar():New( oDlg, 25, 32, .T.,,,, .F. )
oTBar:SetCss(“QFrame{background-color: #007bff;}”)
oBtn1 := TButton():New(0,0,””,oTBar,{|| }, 12,12,,,.F.,.T.,.F.,,.F.,,,.F. )

Menu lateral — Tag: [*MainMenu]

/src/app/app.component.html
Para criação do menu em Angular usei o mat-menu

<!-- [*MainMenu] -->
<!-- https://material.angular.io/components/menu/examples -->
<mat-menu #mainMenu=”matMenu”>
<button mat-menu-item color=”primary”
[routerLink]=”[‘’]”>
<mat-icon>home</mat-icon>Home
</button>
<button mat-menu-item color=”primary”
[routerLink]=”[‘nivers’]”
[queryParams]=”{title: ‘Nivers’}”>
<mat-icon>cake</mat-icon>Aniversários
</button>

niversAdvPL.prw
Em AdvPL, utilizei o componente TMenu. Infelizmente, não consegui um “print”, pois como ele é nativo, qualquer ação o fecha.

// [*MainMenu]
oMenu := TMenu():New(0,0,0,0,.T.)
oTMenuIte1 := TMenuItem():New(oDlg,”Home”,,,,{|| home() },,,,,,,,,.T.)
oTMenuIte2 := TMenuItem():New(oDlg,”Aniversarios”,,,,{|| nivers() },,,,,,,,,.T.)
oTMenuIte3 := TMenuItem():New(oDlg,”Hooks”,,,,{|| hooks() },,,,,,,,,.T.)
oMenu:Add(oTMenuIte1)

Templates — Tag: [*Template_Item]
Essa tag não tem referência em AdvPL

/src/app/nivers/nivers.component.ts
No Angular, você pode criar trechos HTML em um template. Isso dispensa a criação de um arquivo HTML para renderização.

@Component({
selector: ‘niver-item’,
// [*Template_Item]
template: `
<div style=’
margin: 0;
margin-bottom: 2;
border-bottom-color: #ff0000;
border-bottom-style: dotted;
border-bottom-width: 1;’
>

<button mat-icon-button (click)=”delNiver()”>
<mat-icon>delete_forever</mat-icon>
</button>
| <mat-slide-toggle color=”primary”>Ativo</mat-slide-toggle>
| {{this.birthday}} | {{this.person}}
</div>
`,

Definição e captura de dados — Tag: [*Getter_and_Setter]

/src/app/hooks/hooks.component.ts
No Angular, definimos explicitamente o Getter/Setter

// [*Getter_and_Setter]
_count: number = 0;
get count(): number{ return this._count; }
set count( value: number ){ this._count = value; }

/src/app/hooks/hooks.component.html
E usamos de maneira transparente

<!-- [*Getter_and_Setter] -->
<button (click)=”count = count + 1;”>
Incrementa Hook->count
</button>

niversAdvPL.prw
Em AdvPL, recebo a variável nCount por referência e incremento seu valor.

static function hooksINC(oSay1, nCount, cClicksIni)
nCount++ // [*Getter_and_Setter] Incrementa contador
oSay1:setText( StrTran(cClicksIni, “{{count}}”, cValToChar(nCount)))

Interoperação entre classes — Tag: [*Interop]

/src/app/nivers/nivers.component.ts
Das maneiras que encontrei para chamar funções entre classes, esta me pareceu mais simples, mas no link abaixo tem também uma opção interessante de controle de Emits, vou deixar o link para que conheçam o processo:

O que faremos é passar o componente NiversComponent no construtor do NiverComponentItem, permitindo instanciar a função delNiver() de seu “pai”, deletando um item, no caso ele mesmo(this.idx), do Array de aniversários.

*Função da classe PAI// [*Delete_Item]
delNiver = (idx) => {
this.nivers.splice(idx, 1)
}
*Classe FILHAexport class NiverComponentItem implements OnInit {

// [*Interop] Passando o NiverComponent no construtor eh possivel
// utiliza-lo para fazer a chamada de sua funcao delNiver()
constructor( private parent: NiversComponent ) { }

ngOnInit() { }
delNiver = () => {
this.parent.delNiver(this.idx)
}

Essa ação será disparada ao pressionar a lixeira.

Passagem de parâmetros no roteamento (query)- Tag: [*QueryParams]

/src/app/app.component.html
É possível passar parâmetros facilmente via roteamento, bastando usar a tag [queryParams]. Neste momento, envio um Json simples.

<button mat-icon-button matTooltip=”Aniversários” 
[routerLink]=”[‘nivers’]”
[queryParams]=”{title: ‘Nivers’}”> <!-- [*QueryParams] — Passagem de parâmetros via get -->
<mat-icon>cake</mat-icon>
</button>

/src/app/nivers/nivers.component.ts
O Json que vai ser exibido no onInit do componente, será capturado pelo método queryParams.subscribe do roteador.

ngOnInit() { 
// [*QueryParams] Recupera queryparams passados no routerLink
this.route.queryParams.subscribe(params => {
this.title = params.title;
});
}

Suporte a gestos — Tag: [*Gesture]

/src/main.ts
Eu já comentei sobre o hammer.js, mas é importante ressaltar que ele disponibiliza um conjunto completo de recursos para componentes como mat-slide-toggle, mat-slider, matTooltip. Neste link, terá mais informações:

https://material.angular.io/guide/getting-started

Compilando e executando seu projeto Angular

Utilizando o comando ng serve, você testou toda aplicação, mas para distribuí-la vai ter que fazer sua compilação.

Para tanto, vai usar o seguinte comando:

ng build --prod

Onde o --prod vai criar uma versão compactada (minified) de seu projeto, pronta para “entrega”.

Para executar a aplicação depois de compilada, você precisará de um servidor HTTP. Existem várias opções! Vou deixar aqui duas muito simples de serem instaladas.

A primeira deve ser usada apenas para execução local, basicamente como uma extensão de testes. É um plugin para o Chrome chamado Web Server for Chrome.

Escolha na extensão a pasta onde foi compilado seu projeto, localizando o arquivo index.html. E depois, acesse através do link. Ex: http://127.0.0.1:8887

O segundo server é o http-server, disponível a partir do próprio npm

https://www.npmjs.com/package/http-server

Para sua instalação, utilize o comando:

npm install http-server -g

Para executar, utilize o comando abaixo, direcionando para o diretório onde está o index.html, e definindo a porta (-p), sendo que a default é a 8080:

Aqui o http-server sendo executado em Linux e consumido em uma VM Windows

Executando a aplicação via TWebEngine

O TWebEngine é o nosso chromium embedded no SmartClient, e renderiza perfeitamente as aplicações compiladas com o Angular, React, Vue, etc. Abaixo, a execução em um trecho simples de código.

Utilizando o componente TWebChannel é possível também se comunicar via websocket com a camada HTML/JavaScript, permitindo ao JS utilizar o AdvPL como back-end,

Espero que aproveitem este material…

--

--

Ricardo Mansano Godoi
TOTVS Developers

Chief engineer of Front-end and Development Tools on TOTVS, developing software since 88, plugged to the brand new technologies and Nerd to the bone.