Nos últimos anos, as aplicações de página única (SPAs) têm-se tornado cada vez mais populares. Um SPA é um website que consiste em apenas uma página. Essa página única funciona como um recipiente para uma aplicação JavaScript. O JavaScript é responsável pela obtenção do conteúdo e pela sua apresentação dentro do contentor. O conteúdo é tipicamente obtido de um serviço web e as APIs RESTful tornaram-se a escolha em muitas situações. A parte da aplicação que constitui o SPA é normalmente conhecida como cliente ou front-end, enquanto que a parte responsável pelo API REST é conhecida como servidor ou back-end. Neste tutorial, estará a desenvolver uma simples aplicação de página única Angular com um backend REST, baseado em Node e Express.
Estará a usar Angular, uma vez que segue o padrão MVC e separa limpidamente a View dos Models. É simples criar modelos HTML que são dinamicamente preenchidos com dados e actualizados automaticamente sempre que os dados mudam. Adoro esta estrutura porque é muito poderosa, tem uma enorme comunidade e excelente documentação.
Para o servidor, estará a usar Node com Express. Express é uma estrutura que facilita a criação de APIs REST, permitindo definir o código que corre para diferentes pedidos no servidor. Serviços adicionais podem ser ligados globalmente, ou dependendo do pedido. Há uma série de frameworks que constroem sobre o Express e automatizam a tarefa de transformar os modelos da sua base de dados numa API. Este tutorial não fará uso de nenhum destes para manter este enfoque.
Angular encoraja o uso de TypeScript. O TypeScript acrescenta informação datilográfica ao JavaScript e, na minha opinião, é o futuro do desenvolvimento de aplicações de grande escala em JavaScript. Por esta razão, estará a desenvolver tanto cliente como servidor utilizando TypeScript.
Aqui estão as bibliotecas que irá utilizar para o cliente e o servidor:
- Angular: A estrutura utilizada para construir a aplicação cliente
- Okta para Autorização: Um plugin que gere uma única autorização de login usando Okta, tanto no cliente como no servidor
- Angular Material: Um plugin angular que fornece Design de Material out-of-the-box
- Node: O servidor real que executa o código JavaScript
- Express: Uma biblioteca de encaminhamento para responder aos pedidos do servidor e construir APIs REST
- TypeORM: Uma biblioteca ORM de base de dados para TypeScript
Inicie a sua Aplicação Básica de Cliente Angular
P>Deixamos começar por implementar um cliente básico usando Angular. O objectivo é desenvolver um catálogo de produtos que lhe permita gerir produtos, os seus preços, e os seus níveis de stock. No final desta secção, terá uma aplicação simples composta por uma barra superior e duas vistas, Home e Products. A vista Produtos ainda não terá qualquer conteúdo e nada será protegido por palavra-passe. Isto será coberto nas seguintes secções.
Para começar, terá de instalar Angular. Assumirei que já tem o Node instalado no seu sistema e poderá usar o comando npm
. Digite o seguinte comando num terminal.
npm install -g @angular/[email protected]
Dependente do seu sistema, poderá precisar de executar este comando usando sudo
porque irá instalar o pacote globalmente. O pacote angular-cli
fornece o comando ng
que é utilizado para gerir aplicações angulares. Uma vez instalado vá a um directório à sua escolha e crie a sua primeira aplicação Angular usando o seguinte comando.
ng new MyAngularClient
usando o Angular 7, isto irá suscitá-lo com duas consultas. A primeira pergunta é se pretende incluir o encaminhamento. Responda sim a isto. A segunda pergunta diz respeito ao tipo de folhas de estilo que pretende utilizar. Deixe isto no CSS.
ng new
irá criar um novo directório chamado MyAngularClient
e preenchê-lo com um esqueleto de aplicação. Vamos dedicar um pouco de tempo a analisar alguns dos ficheiros que o comando anterior criou. No directório src
da aplicação, encontrará um ficheiro index.html
que é a página principal da aplicação. Não contém muito e desempenha simplesmente o papel de um recipiente. Verá também um ficheiro style.css
. Este contém a folha de estilo global que é aplicada ao longo de toda a aplicação. Se navegar através das pastas poderá notar um directório src/app
contendo cinco ficheiros.
app-routing.module.tsapp.component.cssapp.component.htmlapp.component.tsapp.component.spec.tsapp.module.ts
Estes ficheiros definem o componente principal da aplicação que será inserido no index.html
. Aqui está uma breve descrição de cada um dos ficheiros:
-
app.component.css
ficheiro contém as folhas de estilo do componente principalapp
. Os estilos podem ser definidos localmente para cada componente -
app.component.html
contém o modelo HTML do componente -
app.component.ts
ficheiro contém o código que controla a visualização -
app.module.ts
define quais os módulos do seu app usará -
app-routing.module.ts
é configurado para definir as rotas da sua aplicação -
app.component.spec.ts
contém um esqueleto para testes unitários oapp
componente
Não cobrirei os testes neste tutorial, mas em aplicações da vida real, deve fazer uso desta funcionalidade. Antes de poder começar, terá de instalar mais alguns pacotes. Estes ajudarão a criar rapidamente um layout bem desenhado e com boa capacidade de resposta. Navegue até ao directório base do cliente, MyAngularClient
, e digite o seguinte comando.
npm i @angular/[email protected] @angular/[email protected] @angular/[email protected] @angular/[email protected]
O @angular/material
e @angular/cdk
as bibliotecas fornecem componentes baseados no Design de Materiais do Google, @angular/animations
é usado para proporcionar transições suaves, e @angular/flex-layout
dá-lhe as ferramentas para tornar o seu design reactivo.
P>Next, cria o modelo HTML para o componente app
. Abra src/app/app.component.html
e substitua o conteúdo com o seguinte.
<mat-toolbar color="primary" class="expanded-toolbar"> <button mat-button routerLink="/">{{title}}</button> <div fxLayout="row" fxShow="false" fxShow.gt-sm> <button mat-button routerLink="/"><mat-icon>home</mat-icon></button> <button mat-button routerLink="/products">Products</button> <button mat-button *ngIf="!isAuthenticated" (click)="login()"> Login </button> <button mat-button *ngIf="isAuthenticated" (click)="logout()"> Logout </button> </div> <button mat-button ="menu" fxHide="false" fxHide.gt-sm> <mat-icon>menu</mat-icon> </button></mat-toolbar><mat-menu x-position="before" #menu="matMenu"> <button mat-menu-item routerLink="/"><mat-icon>home</mat-icon> Home</button> <button mat-menu-item routerLink="/products">Products</button>; <button mat-menu-item *ngIf="!isAuthenticated" (click)="login()"> Login </button> <button mat-menu-item *ngIf="isAuthenticated" (click)="logout()"> Logout </button></mat-menu><router-outlet></router-outlet>
O mat-toolbar
contém a barra de ferramentas de design de material, enquanto router-outlet
é o recipiente que será preenchido pelo router. O ficheiro app.component.ts
deve ser editado para conter o seguinte.
import { Component } from '@angular/core';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: })export class AppComponent { public title = 'My Angular App'; public isAuthenticated: boolean; constructor() { this.isAuthenticated = false; } login() { } logout() { }}
Este é o controlador para o componente app
. Pode ver que contém uma propriedade chamada isAuthenticated
juntamente com dois métodos login
e logout
. Neste momento, estes não fazem nada. Serão implementados na próxima secção que abrange a autenticação do utilizador com o Okta. Agora defina todos os módulos que irá utilizar. Substitua o conteúdo de app.module.ts
pelo código abaixo:
import { BrowserModule } from '@angular/platform-browser';import { NgModule } from '@angular/core';import { BrowserAnimationsModule } from '@angular/platform-browser/animations';import { FlexLayoutModule } from '@angular/flex-layout';import { MatButtonModule, MatDividerModule, MatIconModule, MatMenuModule, MatProgressSpinnerModule, MatTableModule, MatToolbarModule} from '@angular/material';import { HttpClientModule } from '@angular/common/http';import { FormsModule } from '@angular/forms';import { AppRoutingModule } from './app-routing.module';import { AppComponent } from './app.component';@NgModule({ declarations: , imports: , providers: , bootstrap: })export class AppModule { }
Notifique todos os módulos de desenho de material. A biblioteca @angular/material
requer a importação de um módulo para cada tipo de componente que deseje utilizar na sua aplicação. Começando com Angular 7, o esqueleto padrão da aplicação contém um ficheiro separado chamado app-routing.module.ts
. Edite-o para declarar as seguintes rotas.
import { NgModule } from '@angular/core';import { Routes, RouterModule } from '@angular/router';import { ProductsComponent } from './products/products.component';import { HomeComponent } from './home/home.component';const routes: Routes = ;@NgModule({ imports: , exports: })export class AppRoutingModule { }
Isto define duas rotas correspondentes ao caminho de raiz e ao products
caminho. Também anexa o HomeComponent
e o ProductsComponent
a estes percursos. Crie agora estes componentes. No directório base do cliente Angular, digite os seguintes comandos.
ng generate component Productsng generate component Home
Isto cria html
ts
, e spec.ts
ficheiros para cada componente. Também actualiza app.module.ts
para declarar os novos componentes. Abrir home.component.html
no directório src/app/home
e colar o seguinte conteúdo.
<div class="hero"> <div> <h1>Hello World</h1> <p class="lead">This is the homepage of your Angular app</p> </div></div>
Inclua algum estilo no ficheiro home.component.css
também.
.hero { text-align: center; height: 90vh; display: flex; flex-direction: column; justify-content: center; font-family: sans-serif;}
Deixe o ProductsComponent
vazio por agora. Isto será implementado assim que tiver criado o servidor REST back-end e for capaz de o preencher com alguns dados. Para que tudo fique bonito, restam apenas duas pequenas tarefas. Copiar os seguintes estilos em src/style.css
@import "~@angular/material/prebuilt-themes/deeppurple-amber.css";body { margin: 0; font-family: sans-serif;}.expanded-toolbar { justify-content: space-between;}h1 { text-align: center;}
Finalmente, a fim de renderizar os Ícones de Design de Material, adicionar uma linha dentro do ficheiro <head>
tags do ficheiro index.html
.
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
Está agora pronto para ligar o servidor Angular e ver o que conseguiu até agora. No directório base da aplicação cliente, digite o seguinte comando.
ng serve
Então abra o seu navegador e navegue para http://localhost:4200
.
Adicionar autenticação ao seu nó + aplicação angular
Se alguma vez desenvolveu aplicações web desde o início, saberá quanto trabalho está envolvido apenas para permitir que os utilizadores se registem, verifiquem, entrem e saiam da sua aplicação. Usando o Okta, este processo pode ser grandemente simplificado. Para começar, necessitará de uma conta de desenvolvedor com Okta.
No seu navegador, navegue até developer.okta.com e clique em Create Free Account e introduza os seus dados.
Após ter terminado, será levado para o seu painel de desenvolvimento. Clique no botão Adicionar Aplicação para criar uma nova aplicação.
Iniciar criando uma nova aplicação de página única. Escolha a aplicação de página única e clique em Next.
Na página seguinte, terá de editar as configurações padrão. Certifique-se de que o número da porta é 4200. Esta é a porta padrão para aplicações angulares.
É isso mesmo. Deverá agora ver um ID de cliente que deverá colar no seu código TypeScript.
Para implementar autenticação no cliente, instale a biblioteca Okta para Angular.
npm install @okta/[email protected] --save-exact
In app.module.ts
importar o OktaAuthModule
.
import { OktaAuthModule } from '@okta/okta-angular';
Na lista de imports
do módulo app
, adicionar:
OktaAuthModule.initAuth({ issuer: 'https://{yourOktaDomain}/oauth2/default', redirectUri: 'http://localhost:4200/implicit/callback', clientId: '{YourClientId}'})
Here yourOktaDomain
deve ser substituído pelo domínio de desenvolvimento que vê no seu browser quando navega para o seu painel de instrumentos Okta. YourClientId
tem de ser substituído pelo ID de cliente que obteve ao registar a sua aplicação. O código acima torna o Módulo de Autenticação Okta disponível na sua aplicação. Utilize-o em app.component.ts
, e importe o serviço.
import { OktaAuthService } from '@okta/okta-angular';
Modificar o construtor para injectar o serviço e subscrever o mesmo.
constructor(public oktaAuth: OktaAuthService) { this.oktaAuth.$authenticationState.subscribe( (isAuthenticated: boolean) => this.isAuthenticated = isAuthenticated );}
Agora, quaisquer alterações no estado de autenticação serão reflectidas na propriedade isAuthenticated
. Será ainda necessário inicializá-la quando o componente for carregado. Criar um ngOnInit
método e adicionar implements OnInit
à definição da sua classe
import { Component, OnInit } from '@angular/core';...export class AppComponent implements OnInit { ... async ngOnInit() { this.isAuthenticated = await this.oktaAuth.isAuthenticated(); }}
Finalmente, implementar o método login
e logout
para reagir à interface do utilizador e registar o utilizador dentro ou fora.
login() { this.oktaAuth.loginRedirect();}logout() { this.oktaAuth.logout('/');}
No módulo de encaminhamento, é necessário registar a rota que será utilizada para o pedido de login. Abrir app-routing.module.ts
e importar OktaCallbackComponent
e OktaAuthGuard
.
import { OktaCallbackComponent, OktaAuthGuard } from '@okta/okta-angular';
Adicionar outra rota para a matriz routes
.
{ path: 'implicit/callback', component: OktaCallbackComponent}
Isto permitirá ao utilizador iniciar sessão utilizando o botão Login. Para proteger o Products
rota de acesso não autorizado, adicione a seguinte linha ao products
rota.
{ path: 'products', component: ProductsComponent, canActivate: }
É tudo o que há a fazer. Agora, quando um utilizador tenta aceder à vista de Produtos, será redireccionado para a página de login do Okta. Uma vez ligado, o utilizador será redireccionado de volta à vista Produtos.
Implement a Node REST API
O próximo passo é implementar um servidor baseado em Node and Express que armazenará informação do produto. Isto irá utilizar um número de bibliotecas mais pequenas para facilitar a sua vida. Para desenvolver em TypeScript, necessitará de typescript
e tsc
. Para a camada de abstracção da base de dados, irá utilizar TypeORM. Esta é uma biblioteca conveniente que injecta o comportamento em classes TypeScript e transforma-as em modelos de bases de dados. Crie um novo directório para conter a sua aplicação de servidor, depois execute nele o seguinte comando.
npm init
Responder a todas as perguntas, depois executar:
npm install --save-exact [email protected] @types/[email protected] @okta/[email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] @types/[email protected]
Não vou cobrir todas estas bibliotecas em detalhe, mas verá que @okta/jwt-verifier
é utilizado para verificar os Tokens Web JSON e autenticá-los.
Para que o TypeScript funcione, crie um ficheiro tsconfig.json
e cole no seguinte conteúdo.
{ "compilerOptions": { "target": "es6", "module": "commonjs", "outDir": "dist", "experimentalDecorators": true, "emitDecoratorMetadata": true }, "include": , "exclude": }
O primeiro passo na criação do servidor é criar um modelo de base de dados para o produto. Usando TypeORM, isto é simples. Criar um subdirectório src
e, dentro dele, criar um ficheiro model.ts
. Colar o seguinte conteúdo.
import {Entity, PrimaryGeneratedColumn, Column, createConnection, Connection, Repository} from 'typeorm';@Entity()export class Product { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() sku: string; @Column('text') description: string; @Column() price: number; @Column() stock: number;}
As anotações TypeORM transformam a definição de classe num modelo de base de dados. Adoro o projecto TypeORM devido à sua facilidade de utilização e à grande variedade de conectores de base de dados SQL e NoSQL que são suportados. Sugiro que verifique a documentação em https://github.com/typeorm/typeorm.
Também terá de ter acesso a um repositório de produtos. Também no ficheiro model.ts
adicione o seguinte.
let connection:Connection;export async function getProductRepository(): Promise<Repository<Product>> { if (connection===undefined) { connection = await createConnection({ type: 'sqlite', database: 'myangularapp', synchronize: true, entities: , }); } return connection.getRepository(Product);}
Nota que isto usa SQLite para simplificar aqui. Num cenário do mundo real, deve substituir isto por um conector de base de dados à sua escolha.
Next, criar um ficheiro chamado product.ts
. Este ficheiro conterá a lógica para todas as rotas para as operações CRUD em Produtos.
import { NextFunction, Request, Response, Router } from 'express';import { getProductRepository, Product } from './model';export const router: Router = Router();router.get('/product', async function (req: Request, res: Response, next: NextFunction) { try { const repository = await getProductRepository(); const allProducts = await repository.find(); res.send(allProducts); } catch (err) { return next(err); }});router.get('/product/:id', async function (req: Request, res: Response, next: NextFunction) { try { const repository = await getProductRepository(); const product = await repository.find({id: req.params.id}); res.send(product); } catch (err) { return next(err); }});router.post('/product', async function (req: Request, res: Response, next: NextFunction) { try { const repository = await getProductRepository(); const product = new Product(); product.name = req.body.name; product.sku = req.body.sku; product.description = req.body.description; product.price = Number.parseFloat(req.body.price); product.stock = Number.parseInt(req.body.stock); const result = await repository.save(product); res.send(result); } catch (err) { return next(err); }});router.post('/product/:id', async function (req: Request, res: Response, next: NextFunction) { try { const repository = await getProductRepository(); const product = await repository.findOne({id: req.params.id}); product.name = req.body.name; product.sku = req.body.sku; product.description = req.body.description; product.price = Number.parseFloat(req.body.price); product.stock = Number.parseInt(req.body.stock); const result = await repository.save(product); res.send(result); } catch (err) { return next(err); }});router.delete('/product/:id', async function (req: Request, res: Response, next: NextFunction) { try { const repository = await getProductRepository(); await repository.delete({id: req.params.id}); res.send('OK'); } catch (err) { return next(err); }});
Este ficheiro é um pouco longo mas não contém nada de surpreendente. Product
Os objectos são criados e guardados ou apagados da base de dados.
Viremos novamente a nossa atenção para a autenticação. Vai querer ter a certeza de que apenas utilizadores autenticados podem aceder ao serviço. Crie um ficheiro chamado auth.ts
e cole o seguinte.
import { Request, Response, NextFunction} from 'express';const OktaJwtVerifier = require('@okta/jwt-verifier');const oktaJwtVerifier = new OktaJwtVerifier({ clientId: '{YourClientId}', issuer: 'https://{yourOktaDomain}/oauth2/default'});export async function oktaAuth(req:Request, res:Response, next:NextFunction) { try { const token = (req as any).token; if (!token) { return res.status(401).send('Not Authorised'); } const jwt = await oktaJwtVerifier.verifyAccessToken(token); req.user = { uid: jwt.claims.uid, email: jwt.claims.sub }; next(); } catch (err) { return res.status(401).send(err.message); }}
Apenas como na aplicação cliente yourOktaDomain
deve ser substituído pelo domínio de desenvolvimento e YourClientId
tem de ser substituído pelo ID de cliente da sua aplicação. O oktaJwtVerifier
instance pega numa ficha JWT e autentica-a. Se for bem sucedido, o ID de utilizador e o e-mail serão armazenados em req.user
. Caso contrário, o servidor responderá com um código de estado 401. A peça final para completar o servidor é o ponto de entrada principal que efectivamente inicia o servidor e regista o middleware que definiu até agora. Crie um ficheiro server.ts
com o seguinte conteúdo.
import * as express from 'express';import * as cors from 'cors';import * as bodyParser from 'body-parser';const bearerToken = require('express-bearer-token');import {router as productRouter} from './product'import {oktaAuth} from './auth'const app = express() .use(cors()) .use(bodyParser.json()) .use(bearerToken()) .use(oktaAuth) .use(productRouter);app.listen(4201, (err) => { if (err) { return console.log(err); } return console.log('My Node App listening on port 4201');});
Para compilar o TypeScript execute o comando
npx tsc
Então, se quiser iniciar o servidor, basta executar:
node dist/server.js
Fim do seu Cliente Angular
Agora que o servidor esteja completo vamos acabar com o cliente. O primeiro passo é criar uma classe que contenha os dados do Produto. Esta classe é semelhante à classe Product
na aplicação do servidor, mas sem as anotações TypeORM. Estará contida num ficheiro chamado product.ts
.
export class Product { id?: string; name: string; sku: string; description: string; price: number; stock: number;}
Guardar este ficheiro no mesmo directório que o products
componente. É melhor encapsular o acesso ao REST API num serviço separado. Criar um serviço Products
executando o comando abaixo.
ng generate service products/Products
Isto irá criar um ficheiro chamado product.service.ts
no directório src/app/products
. Preencha-o com o seguinte conteúdo.
import { Injectable } from '@angular/core';import { HttpClient } from '@angular/common/http';import { OktaAuthService } from '@okta/okta-angular';import { Product } from './product';const baseUrl = 'http://localhost:4201';@Injectable({ providedIn: 'root'})export class ProductsService { constructor(public oktaAuth: OktaAuthService, private http: HttpClient) { } private async request(method: string, url: string, data?: any) { const token = await this.oktaAuth.getAccessToken(); console.log('request ' + JSON.stringify(data)); const result = this.http.request(method, url, { body: data, responseType: 'json', observe: 'body', headers: { Authorization: `Bearer ${token}` } }); return new Promise<any>((resolve, reject) => { result.subscribe(resolve as any, reject as any); }); } getProducts() { return this.request('get', `${baseUrl}/product`); } getProduct(id: string) { return this.request('get', `${baseUrl}/product/${id}`); } createProduct(product: Product) { console.log('createProduct ' + JSON.stringify(product)); return this.request('post', `${baseUrl}/product`, product); } updateProduct(product: Product) { console.log('updateProduct ' + JSON.stringify(product)); return this.request('post', `${baseUrl}/product/${product.id}`, product); } deleteProduct(id: string) { return this.request('delete', `${baseUrl}/product/${id}`); }}
The ProductsService
contém um método público para cada rota do REST API. O pedido HTTP está encapsulado num método separado. Note-se como o pedido contém sempre um Bearer
ficha obtida do OktaAuthService
. Este é o token utilizado pelo servidor para autenticar o utilizador.
Agora o ProductsComponent
pode ser implementado. O seguinte código fará o truque.
import { Component, OnInit } from '@angular/core';import { MatTableDataSource } from '@angular/material';import { ProductsService } from './products.service';import { Product } from './product';@Component({ selector: 'app-products', templateUrl: './products.component.html', styleUrls: })export class ProductsComponent implements OnInit { displayedColumns: string = ; dataSource = new MatTableDataSource<any>(); selectedProduct: Product = new Product(); loading = false; constructor(public productService: ProductsService) { } ngOnInit() { this.refresh(); } async refresh() { this.loading = true; const data = await this.productService.getProducts(); this.dataSource.data = data; this.loading = false; } async updateProduct() { if (this.selectedProduct.id !== undefined) { await this.productService.updateProduct(this.selectedProduct); } else { await this.productService.createProduct(this.selectedProduct); } this.selectedProduct = new Product(); await this.refresh(); } editProduct(product: Product) { this.selectedProduct = product; } clearProduct() { this.selectedProduct = new Product(); } async deleteProduct(product: Product) { this.loading = true; if (confirm(`Are you sure you want to delete the product ${product.name}. This cannot be undone.`)) { this.productService.deleteProduct(product.id); } await this.refresh(); }}
O layout, em products.component.html
, mostrando o produto consiste em duas partes. A primeira parte utiliza um componente mat-table
para mostrar uma lista de produtos. A segunda parte mostra uma forma na qual o utilizador pode editar um produto novo ou existente.
<h1 class="h1">Product Inventory</h1><div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="space-between stretch" class="products"> <table mat-table fxFlex="100%" fxFlex.gt-sm="66%" ="dataSource" class="mat-elevation-z1"> <ng-container matColumnDef="name"> <th mat-header-cell *matHeaderCellDef> Name</th> <td mat-cell *matCellDef="let product"> {{product.name}}</td> </ng-container> <ng-container matColumnDef="sku"> <th mat-header-cell *matHeaderCellDef> SKU</th> <td mat-cell *matCellDef="let product"> {{product.sku}}</td> </ng-container> <ng-container matColumnDef="description"> <th mat-header-cell *matHeaderCellDef> Description</th> <td mat-cell *matCellDef="let product"> {{product.description}}</td> </ng-container> <ng-container matColumnDef="price"> <th mat-header-cell *matHeaderCellDef> Price</th> <td mat-cell *matCellDef="let product"> {{product.price}}</td> </ng-container> <ng-container matColumnDef="stock"> <th mat-header-cell *matHeaderCellDef> Stock Level</th> <td mat-cell *matCellDef="let product"> {{product.stock}}</td> </ng-container> <ng-container matColumnDef="edit"> <th mat-header-cell *matHeaderCellDef></th> <td mat-cell *matCellDef="let product"> <button mat-icon-button (click)="editProduct(product)"> <mat-icon>edit</mat-icon> </button> </td> </ng-container> <ng-container matColumnDef="delete"> <th mat-header-cell *matHeaderCellDef></th> <td mat-cell *matCellDef="let product"> <button mat-icon-button (click)="deleteProduct(product)"> <mat-icon>delete</mat-icon> </button> </td> </ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> </table> <mat-divider fxShow="false" fxShow.gt-sm ="true"></mat-divider> <div> <h2>Selected Product</h2> <label>Name <input type="text" ="selectedProduct.name"> </label> <label>SKU <input type="text" ="selectedProduct.sku"> </label> <label>Description <input type="text" ="selectedProduct.description"> </label> <label>Price <input type="text" ="selectedProduct.price"> </label> <label>Stock Level <input type="text" ="selectedProduct.stock"> </label> <button mat-flat-button color="primary" (click)="updateProduct()">{{(selectedProduct.id!==undefined)?'Update':'Create'}}</button> <button mat-flat-button color="primary" (click)="clearProduct()">Clear</button> </div> <div class="loading" *ngIf="loading"> <mat-spinner></mat-spinner> </div></div>
Finalmente, adicionar um pouco de estilo em products.component.css
ao layout.
.products { padding: 2rem;}label, input { display: block;}label { margin-bottom: 1rem;}.loading { position: absolute; display: flex; justify-content: center; align-content: center; width: 100%; height: 100%; background-color: rgba(255, 255, 255, 0.8);}
Quando tudo estiver feito, pode iniciar o cliente e o servidor e testar a sua aplicação. Apenas para repetir, no directório contendo o servidor, execute:
node dist/server.js
E no directório do cliente, execute:
ng serve
A sua aplicação deve assemelhar-se um pouco ao seguinte
Saiba mais sobre Angular, Nó, e Express
Neste tutorial, orientei-o através do desenvolvimento de uma aplicação web de página única usando Angular e Nó. Usando apenas algumas linhas de código, foi possível implementar autenticação de utilizador para o cliente e para o servidor. Angular faz uso de TypeScript que é um super conjunto da linguagem JavaScript e adiciona informação de tipo. TypeScript torna o código mais estável e é por isso que decidi implementar também o servidor Node/Express usando esta linguagem. Se ainda não está familiarizado com o TypeScript, veja esta grande introdução de Todd Motto. Ele também tem alguns bons artigos em Angular.
O código completo deste tutorial pode ser encontrado em GitHub.
Se estiver pronto para aprender mais sobre Angular, ou Nodo/Express, temos alguns outros recursos para verificar:
- Autenticação de Nodo Simples
- Build a Basic CRUD App with Node and React
- Build a Simple API Service with Express and GraphQL
- Angular 6 – What’s New and Why Upgrade?
- Build a Basic CRUD App with Angular 7 and Spring Boot
E, como sempre, gostaríamos que nos seguisse para obter mais conteúdos e actualizações frescas da nossa equipa. Pode encontrar-nos no Twitter @oktadev, no Facebook, e no LinkedIn.