Angular pra quem vem do AdvPL
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/TxOVmyjDhQQPretendo 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:
- Um ambiente Protheus configurado
- 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.
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:
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…