Criando um CRUD com Angular e Nodejs + Sequelize

Para ficar completo o post anterior (ORM Sequelize com database existente do Postgres no Nodejs), criei um crud para melhor visualização das informações.

Usei o framework Angular, que permite desenvolver aplicações web e mobile mantido pela Google.

Apesar de ter mantido o nome Angular é outro conceito de desenvolvimento, sendo assim quem quiser aprender sugiro a nova versão.

Verificações de instalações!

Antes de começar é necessário certificar que o Nodejs 6.9.5 (ou Superior) e o NPM 3 (ou Superior) estão instalados.

$ node -v
$ npm -v

Instalei uma das maiores novidades do Angular o @angular/cli (para aplicações angulares baseadas no projeto ember-cli) e a linguagem adotada é o Typescript, para desenvolvimento JavaScript em larga escala. Com TypeScript é possível escrever código utilizando uma estrutura fortemente tipada e ter este código compilado para JavaScript puro. Qualquer navegador, qualquer host.

$ npm i -g typescript @angular/cli

Criei o projeto Angular dentro do diretório database_existente (projeto do post anterior)

$ ng new myApp
$ cd myApp

Testei o projeto utilizando o comando abaixo

$ ng serve

Gerando e atendendo um projeto Angular através de um servidor de desenvolvimento navegue até http://localhost:4200/. O aplicativo será recarregado automaticamente se você alterar qualquer um dos arquivos de origem.

Pode-se configurar o host e a porta HTTP padrão usados ​​pelo servidor de desenvolvimento com duas opções de linha de comando:

$ ng serve --host 0.0.0.0 --port 4201

ngserve Pronto, projeto criado com sucesso.

Deixei o ng serve executando, assim toda alteração feita irá compilar e mostrar no navegador

Utilizei o framework Bootstrap, jquery e o fontawesome

$ npm i --save bootstrap jquery font-awesome

Alterei o .angular-cli.json para adicionar o caminho dos mesmos

//Antes
        "styles": [
            "styles.css"
        ],
        "scripts": []
//Depois adicionei o bootstrap, jquery e o font-awesome
        "styles": [
            "../node_modules/bootstrap/dist/css/bootstrap.css",
            "../node_modules/font-awesome/css/font-awesome.css",
            "styles.css"
        ],
        "scripts": [
            "../node_modules/jquery/dist/jquery.js",
            "../node_modules/bootstrap/dist/js/bootstrap.js"
        ]

Construindo o Front-end

Adicinei um navbar no arquivo myApp/src/app/app.component.html

<nav class="navbar navbar-default">
    <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
            <a class="navbar-brand" href="#">Menu</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Cadastro <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="#">Setor</a></li>
                        <li><a href="#">Produto</a></li>
                    </ul>
                </li>
            </ul>
        </div>
        <!-- /.navbar-collapse -->
    </div>
    <!-- /.container-fluid -->
</nav>

Verifiquei se o ng serve estava rodando.

navbar

Criei o Component (Representam uma unidade unificada de ​View + Model + Style que você deve compor para criar uma aplicação) dentro do diretório myApp/src/app/

$ ng g c setor

Generate / Component - gerou-se os arquivos setor.component.css, setor.component.html, setor.component.spec.ts, setor.component.ts e importei no app.module.ts

Criei um arquivo setor.service.ts dentro do diretório src/app/setor

$ ng g service setor.service

Importei no @ngModule do app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { SetorComponent } from './setor/setor.component';

//Adicionei esse import
import { SetorService } from './setor/setor.service';

@NgModule({
  declarations: [
    AppComponent,
    SetorComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  //Adicionei o service no providers - é um recurso que o Angular usa para fornecer (resultar, gerar) algo que queremos usar.
  //Se planeja usar um objeto várias vezes, por exemplo serviço Http em diferentes componentes, você pode pedir a mesma instância desse serviço (reutilizá-lo)
  providers: [SetorService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Incluí o código no arquivo setor.service.ts

import { Injectable } from '@angular/core';

//Importei o Http, Response, Headers, RequestOptions para requisições no Back-end
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Observable';

import 'rxjs/add/operator/map';

@Injectable()
export class SetorService {

  headers: any = new Headers({ 'Content-Type': 'application/json' });
  options: any = new RequestOptions({ headers: this.headers });

  constructor(private http: Http) { }

  //Função que irá fazer uma request no server para buscar a lista de setores
  getSetor() {
      return this.http.get('/listSetor')
                      .map(response => response.json())
  }

  //Função que irá fazer uma request no server para buscar apenas o setor de um determinado ID
  getSetorId(data) {

      return this.http.post('/byIdSetor', { id: data }, this.options)
                      .map(response => response.json())
  }

  //Função que irá fazer uma request no server para inserir um setor
  saveSetor(data) {

    return this.http.post('/saveSetor', data, this.options)
                    .map(response => response.json())
  }

  //Função que irá fazer uma request no server para dar update em um setor
  updateSetor(data) {

    return this.http.post('/updateSetor', data, this.options)
                    .map(response => response.json())
  }

  //Função que irá fazer uma request no server para deletar um setor
  delSetor(data) {

    return this.http.post('/deleteSetor', data, this.options)
                    .map(response => response.json())
  }

}

Observable matriz cujos itens chegam de forma assíncrona ao longo do tempo. Observables ajudam a gerenciar dados assíncronos, como dados proveniêntes de um serviço back-end. Observables ​​são usados ​​dentro do próprio Angular, incluindo o sistema do evento do Angular e seu serviço do cliente do HTTP.

Criei a interface adicionando o código no arquivo setor.component.html

<section class="content">
    <div class="row">
        <div class="col-xs-12">
            <div class="box">
                <div class="box-header">
                    <div class="box-tools">
                        <div class="input-group">
                            <button class="btn btn-sm btn-primary" (click)="addSetor()"><i class="glyphicon glyphicon-plus"></i>Adicionar</button>
                            <input type="text" name="table_search" class="form-control input-sm pull-right" style="width: 150px;" placeholder="Pesquisa Setor" [(ngModel)]="filtro" />
                            <div class="input-group-btn">
                                <button class="btn btn-sm btn-default"><i class="fa fa-search"></i></button>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="box-body table-responsive no-padding">
                    <table class="table table-hover">
                        <tr>
                            <th>Código</th>
                            <th>Descrição</th>
                            <th>Ação</th>
                        </tr>
                        <tr *ngFor="let setor of obterSetores()">
                            <td></td>
                            <td></td>
                            <td>
                                <div class="buttons">
                                    <button class="btn btn-success btn-xs" (click)="editSetor(setor)">
                                                <span class="glyphicon glyphicon-edit" aria-hidden="true"></span>
                                                Editar
                                            </button>
                                    <button class="btn btn-danger btn-xs" (click)="delSetor(setor)">
                                                <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
                                                Exluir
                                            </button>
                                </div>
                            </td>
                        </tr>
                    </table>
                </div>
            </div>
        </div>
    </div>
</section>
  • (ngModel) - two-way data binding cria uma instância FormControl de um modelo de domínio e vincula a um elemento de controle de formulário.
  • (click) - Event binding ( (event) ) sintaxe de vinculação de eventos que consiste em um nome de evento de destino entre (parênteses) à esquerda de um sinal de igual e uma indicação de modelo citado à direita.

Criei um arquivo setor.ts que é uma classe com os atributos:

export class Setor {
  id: number;
  descricao: string;
}

Adicionei as funções dos events no arquivo setor.component.ts

import { Component, OnInit } from '@angular/core';

//Importei o Router devido os eventos click "editar", "excluir" e "adicionar"
import { Router } from '@angular/router';

//Importei o SetorService que foi criado anteriormente e a class Setor
import { SetorService } from './setor.service';
import { Setor } from './setor';

@Component({
  selector: 'app-setor',
  templateUrl: './setor.component.html',
  styleUrls: ['./setor.component.css']
})
export class SetorComponent implements OnInit {

  public setores: Setor[];
  filtro: string;
  private errorMessage: string;

  //Adicionei as instâncias SetorService, e Router
  constructor(private service: SetorService, private router: Router) { }

  ngOnInit() {
    this.onLoad()
  }

  onLoad() {
      //Estou invocando o método getSetor do SetorService
      this.service.getSetor()
                  .subscribe(data => this.setores = data,
                  error => this.errorMessage = <any>error);
  }

  addSetor() {
    this.router.navigate(['/app-manutencao']) //Criarei o component "app-manutenção"
  }

  editSetor(setor) {
      this.router.navigate(['/app-manutencao', {id: setor.id}]) //Passando parâmetro pelo Router Navigate
  }

  delSetor(setor) {
     if (confirm(`Deseja excluir o setor ${setor.id} ?`)) {
        this.service.delSetor(setor)
                .subscribe(data => {
                    if(data){
                        alert('Registro excluído com sucesso')
                        this.onLoad()
                    }else{
                        alert('Registro não foi excluído')
                    }
                }, error => this.errorMessage = <any>error)
    }
  }

  //Função para filtrar dados a partir de um input do usuário
  obterSetores() {

    if(this.setores === undefined || this.filtro === undefined || this.filtro.trim() === '' ) {
      return this.setores
    }

    return this.setores.filter((setor) => {

      if (setor.descricao.toLowerCase().indexOf(this.filtro.toLowerCase()) >= 0 ) {
        return true
      } else {
        return false
      }
    })

  }
}
  • Router permite a navegação de uma visão para a próxima, à medida que os usuários executam tarefas de aplicação.
  • Constructor é inicializado pelo mecanismo de JavaScript, e o TypeScript nos permite dizer ao Angular quais dependências precisam ser mapeadas em relação a uma propriedade específica.
  • ngOnInit ciclo de vida que é chamado após as propriedades de uma diretiva vinculadas a dados são inicializados.

Criei a manutenção dos dados (Insert/Update), dentro do diretório myApp/src/app/setor/

$ ng g c manutencao

Dentro do diretório myApp/src/app/setor/manutencao/ geraram-se os arquivos manutencao.component.css, manutencao.component.html, manutencao.component.spec.ts, manutencao.component.ts e importei no app.module.ts

Criei a interface adicionando o código no arquivo manutencao.component.html

<section class="content">
    <div class="row">
        <div class="col-xs-12">
            <div class="box">
                <form name="form" (ngSubmit)="save()" #setor="ngForm">
                    <div class="box-header">
                        <div class="box-tools">
                            <div class="input-group">
                                <button type="submit" class="btn btn-sm btn-success" [disabled]="!setor.form.valid"><i name="save" id="save" class="glyphicon glyphicon-ok"></i>Salvar</button>
                                <button class="btn btn-sm btn-danger" (click)="cancel()"><i class="glyphicon glyphicon-remove"></i>Cancelar</button>
                            </div>
                        </div>
                    </div>
                    <div class="box box-primary">
                        <div class="box-header">
                            <h3 class="box-title">Informe os dados</h3>
                        </div>
                        <div class="form-group">
                            <input type="text" id="nameSetor" name="nameSetor" class="form-control" placeholder="Informe a descrição do setor" maxlength="100" [(ngModel)]="model.descricao" #nameSetor="ngModel" required/>
                            <span [hidden]="nameSetor.valid || nameSetor.pristine" class="alert alert-danger">descrição do setor</span>
                        </div>

                    </div>
                    <!-- /.box -->

                </form>
            </div>
        </div>
    </div>
</section>

Incluí as funções dos events do código anterior no arquivo manutencao.component.ts

import { Component, OnInit } from '@angular/core';

import { Router, ActivatedRoute } from '@angular/router';
import { Subscription, Observable } from 'rxjs/Rx';

import { SetorService } from '../setor.service';
import { Setor } from '../setor';

@Component({
  selector: 'app-manutencao',
  templateUrl: './manutencao.component.html',
  styleUrls: ['./manutencao.component.css']
})
export class ManutencaoComponent implements OnInit {

  public model: Setor;
  private isNew: boolean = true;
  private subscription: Subscription;
  private errorMessage: string;

  constructor(private service: SetorService, private router: Router, private route: ActivatedRoute) { }

  ngOnInit(): void {
    this.model = new Setor();

    this.subscription = this.route.params.subscribe((params: any) => {

        if (params.hasOwnProperty('id')) {
            this.isNew = false;
            this.service.getSetorId(params['id'])
                  .subscribe(data => this.model = data[0],
                  error => this.errorMessage = <any>error);
        } else {
            this.isNew = true;
        }

      }
    )
  }

  save() {

    let alerta = 'Registro gravado com sucesso!';
    let alError = 'Verificar os dados, não foi gravado';

    if(this.isNew){
        this.service.saveSetor(this.model)
                .subscribe(data => {
                    if(data){
                        alert(alerta)
                        this.router.navigate(['/app-setor'])
                    }else{
                        alert(alError)
                    }
                }, error => this.errorMessage = <any>error)
    }else{
        this.service.updateSetor(this.model)
                .subscribe(data => {
                    if(data){
                        alert(alerta)
                        this.router.navigate(['/app-setor'])
                    }else{
                        alert(alError)
                    }
                }, error => this.errorMessage = <any>error)
    }

  }

  cancel() {
    this.router.navigate(['/app-setor'])
  }

}

Adicionando o Router…

Importei o Router no arquivo app.module.ts para funcionar o navigate e o router-link que irei incluir no Menu

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

//Acrescentei esse código
import { RouterModule, Routes } from '@angular/router';

import { AppComponent } from './app.component';
import { SetorComponent } from './setor/setor.component';
import { SetorService } from './setor/setor.service';
import { ManutencaoComponent } from './setor/manutencao/manutencao.component';

//Acrecentei esse código
const appRoutes: Routes = [
  { path: 'app-setor', component: SetorComponent },
  { path: 'app-manutencao', component: ManutencaoComponent }
  { path: '**', redirectTo: '/' }
];

@NgModule({
  declarations: [
    AppComponent,
    SetorComponent,
    ManutencaoComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot(appRoutes, {useHash: true}) //Adicionei essa linha
  ],
  providers: [SetorService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Modifiquei o app.component.html

<nav class="navbar navbar-default">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
            <a class="navbar-brand" href="#">Menu</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Cadastro <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        //Alterado href="#" para [routerLink]
                        <li><a [routerLink]="['/app-setor']">Setor</a></li>
                        <li><a href="#">Produto</a></li>
                    </ul>
                </li>
            </ul>
        </div>
        <!-- /.navbar-collapse -->
    </div>
    <!-- /.container-fluid -->
</nav>

//Incluí o router-outlet
<router-outlet></router-outlet>

router-outlet atua como um espaço reservado que o Angular preenche dinamicamente com base no estado atual do roteador.

Para fazer o produto é o mesmo processo.

Criei o Component produto dentro do diretório myApp/src/app/

$ ng g c produto

Gerou-se os arquivos produto.component.css, produto.component.html, produto.component.spec.ts, produto.component.ts e importei no app.module.ts

Criei um arquivo produto.service.ts dentro do diretório src/app/produto/

$ ng g service produto.service

Importei no @ngModule do app.module.ts e adicionei no Router a rota do produto

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { RouterModule, Routes } from '@angular/router';

import { AppComponent } from './app.component';
import { SetorComponent } from './setor/setor.component';
import { SetorService } from './setor/setor.service';
import { ManutencaoComponent } from './setor/manutencao/manutencao.component';
import { ProdutoComponent } from './produto/produto.component';
import { ProdutoService } from './produto/produto.service';       //Incluí essa linha

const appRoutes: Routes = [
  { path: 'app-setor', component: SetorComponent },
  { path: 'app-manutencao', component: ManutencaoComponent },
  { path: 'app-produto', component: ProdutoComponent }            //Adicionei essa linha
  { path: '**', redirectTo: '/' }
];

@NgModule({
  declarations: [
    AppComponent,
    SetorComponent,
    ManutencaoComponent,
    ProdutoComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot(appRoutes, {useHash: true})
  ],
  providers: [SetorService, ProdutoService], //Adicionei ProdutoService
  bootstrap: [AppComponent]
})
export class AppModule { }

Adicionei o código no arquivo produto.service.ts

import { Injectable } from '@angular/core';

import { Http, Response, Headers, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Observable';

import 'rxjs/add/operator/map';

@Injectable()
export class ProdutoService {

  headers: any = new Headers({ 'Content-Type': 'application/json' });
  options: any = new RequestOptions({ headers: this.headers });

  constructor(private http: Http) { }

  getProduto() {
      return this.http.get('/listProduto')
                      .map(response => response.json())
  }

  getProdutoId(data) {

      return this.http.post('/byIdProduto', { id: data }, this.options)
                      .map(response => response.json())
  }

  saveProduto(data) {

    return this.http.post('/saveProduto', data, this.options)
                    .map(response => response.json())
  }

  updateProduto(data) {

    return this.http.post('/updateProduto', data, this.options)
                    .map(response => response.json())
  }

  delProduto(data) {

    return this.http.post('/deleteProduto', data, this.options)
                    .map(response => response.json())
  }

}

Criei a interface adicionando o código no arquivo produto.component.html

<section class="content">
    <div class="row">
        <div class="col-xs-12">
            <div class="box">
                <div class="box-header">
                    <div class="box-tools">
                        <div class="input-group">
                            <button class="btn btn-sm btn-primary" (click)="addProduto()"><i class="glyphicon glyphicon-plus"></i>Adicionar</button>
                            <input type="text" name="table_search" class="form-control input-sm pull-right" style="width: 150px;" placeholder="Pesquisa Produto" [(ngModel)]="filtro" />
                            <div class="input-group-btn">
                                <button class="btn btn-sm btn-default"><i class="fa fa-search"></i></button>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="box-body table-responsive no-padding">
                    <table class="table table-hover">
                        <tr>
                            <th>Código</th>
                            <th>Descrição</th>
                            <th>Barra</th>
                            <th>Setor</th>
                            <th>Ação</th>
                        </tr>
                        <tr *ngFor="let produto of obterProdutos()">
                            <td></td>
                            <td></td>
                            <td></td>
                            <td></td>
                            <td>
                                <div class="buttons">
                                    <button class="btn btn-success btn-xs" (click)="editProduto(produto)">
                                                <span class="glyphicon glyphicon-edit" aria-hidden="true"></span>
                                                Editar
                                            </button>
                                    <button class="btn btn-danger btn-xs" (click)="delProduto(produto)">
                                                <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
                                                Exluir
                                            </button>
                                </div>
                            </td>
                        </tr>
                    </table>
                </div>
            </div>
        </div>
    </div>
</section>

Criei um arquivo produto.ts que é uma classe com os atributos:

export class Produto {
  id: number;
  descricao: string;
  barra: string;
  id_setor: number;
}

Incluí as funções dos events no arquivo produto.component.ts

import { Component, OnInit } from '@angular/core';

import { Router } from '@angular/router';

import { ProdutoService } from './produto.service';
import { Produto } from './produto';

@Component({
  selector: 'app-produto',
  templateUrl: './produto.component.html',
  styleUrls: ['./produto.component.css']
})
export class ProdutoComponent implements OnInit {

  public produtos: Produto[];
  filtro: string;
  private errorMessage: string;

  constructor(private service: ProdutoService, private router: Router) { }

  ngOnInit() {
    this.onLoad()
  }

  onLoad() {

      this.service.getProduto()
                  .subscribe(data => this.produtos = data,
                  error => this.errorMessage = <any>error);
  }

  addProduto() {
    this.router.navigate(['/app-manutencao-produto'])
  }

  editProduto(produto) {
      this.router.navigate(['/app-manutencao-produto', {id: produto.id}])
  }

  delProduto(produto) {

     if (confirm(`Deseja excluir o produto ${produto.id} ?`)) {
        this.service.delProduto(produto)
                .subscribe(data => {
                    if(data){
                        alert('Registro excluído com sucesso')
                        this.onLoad()
                    }else{
                        alert('Registro não foi excluído')
                    }
                }, error => this.errorMessage = <any>error)
    }
  }

  obterProdutos() {

    if(this.produtos === undefined || this.filtro === undefined || this.filtro.trim() === '' ) {
      return this.produtos
    }

    return this.produtos.filter((produto) => {

      if (produto.descricao.toLowerCase().indexOf(this.filtro.toLowerCase()) >= 0 ) {
        return true
      } else {
        return false
      }
    })

  }

}

Criei a manutenção dos dados (Insert/Update), dentro do diretório myApp/src/app/produto/

$ ng g c manutencao

Dentro do diretório myApp/src/app/produto/manutencao/ geraram-se os arquivos manutencao.component.css, manutencao.component.html, manutencao.component.spec.ts, manutencao.component.ts e importei no app.module.ts

Criei a interface adicionando o código no arquivo manutencao.component.html

<section class="content">
    <div class="row">
        <div class="col-xs-12">
            <div class="box">
                <form (ngSubmit)="save()" #produtoForm="ngForm">
                    <div class="box-header">
                        <div class="box-tools">
                            <div class="input-group">
                                <button type="submit" class="btn btn-sm btn-success" [disabled]="!produtoForm.form.valid"><i name="save" id="save" class="glyphicon glyphicon-ok"></i>Salvar</button>
                                <button class="btn btn-sm btn-danger" (click)="cancel()"><i class="glyphicon glyphicon-remove"></i>Cancelar</button>
                            </div>
                        </div>
                    </div>
                    <div class="box box-primary">
                        <div class="box-header">
                            <h3 class="box-title">Informe os dados</h3>
                        </div>
                        <div class="form-group">
                            <input type="text" id="descricao" name="descricao" class="form-control" placeholder="Informe a descrição do produto" maxlength="100" [(ngModel)]="model.descricao" #descricao="ngModel" required/>
                            <div [hidden]="descricao.valid || descricao.pristine" class="alert alert-danger">Descrição do produto</div>
                        </div>

                        <div class="form-group">
                            <input type="text" id="barra" name="barra" class="form-control" placeholder="Informe o código de barras" maxlength="14" [(ngModel)]="model.barra" #barra="ngModel" required/>
                            <div [hidden]="barra.valid || barra.pristine" class="alert alert-danger">Código de barras</div>
                        </div>

                        <div class="form-group">
                            <select class="form-control" id="setor" required [(ngModel)]="model.id_setor" name="setor" #setor="ngModel">
                                    <option *ngFor="let setor of setores" [value]="setor.id"></option>
                            </select>
                            <div [hidden]="setor.valid || setor.pristine" class="alert alert-danger">Setor</div>
                        </div>

                    </div>
                    <!-- /.box -->

                </form>
            </div>
        </div>
    </div>
</section>

Adicionei as funções dos events do código anterior no arquivo manutencao.component.ts

import { Component, OnInit } from '@angular/core';

import { Router, ActivatedRoute } from '@angular/router';
import { Subscription, Observable } from 'rxjs/Rx';

import { ProdutoService } from '../produto.service';
import { SetorService } from '../../setor/setor.service';
import { Produto } from '../produto';
import { Setor } from '../../setor/setor'

@Component({
  selector: 'app-manutencao-produto',            //Alterei o nome de app-manutencao para app-manutencao-produto não da conflito
  templateUrl: './manutencao.component.html',
  styleUrls: ['./manutencao.component.css']
})
export class ManutencaoProdutoComponent implements OnInit {

  public model: Produto;
  public setores: Setor[];
  private isNew: boolean = true;
  private subscription: Subscription;
  private errorMessage: string;

  constructor(
      private service: ProdutoService,
      private setorService: SetorService,
      private router: Router,
      private route: ActivatedRoute
  ) { }

  ngOnInit(): void {
    this.model = new Produto();

    this.onGetSetores()

    this.subscription = this.route.params.subscribe((params: any) => {

        if (params.hasOwnProperty('id')) {
            this.isNew = false;
            this.service.getProdutoId(params['id'])
                  .subscribe(data => this.model = data[0],
                  error => this.errorMessage = <any>error);
        } else {
            this.isNew = true;
        }

      }
    )
  }

  onGetSetores() {

    return this.setorService.getSetor()
                  .subscribe(data => this.setores = data,
                  error => this.errorMessage = <any>error);
  }

  save() {

    let alerta = 'Registro gravado com sucesso!';
    let alError = 'Verificar os dados, não foi gravado';

    if(this.isNew){
        this.service.saveProduto(this.model)
                .subscribe(data => {

                    if(data){
                        alert(alerta)
                        this.router.navigate(['/app-produto'])
                    }else{
                        alert(alError)
                    }
                }, error => this.errorMessage = <any>error)
    }else{
        this.service.updateProduto(this.model)
                .subscribe(data => {
                    if(data){
                        alert(alerta)
                        this.router.navigate(['/app-produto'])
                    }else{
                        alert(alError)
                    }
                }, error => this.errorMessage = <any>error)
    }
  }

  cancel() {
    this.router.navigate(['/app-produto'])
  }

}

Incluí a rota no arquivo app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { RouterModule, Routes } from '@angular/router';

import { AppComponent } from './app.component';
import { SetorComponent } from './setor/setor.component';
import { SetorService } from './setor/setor.service';
import { ManutencaoComponent } from './setor/manutencao/manutencao.component';
import { ProdutoComponent } from './produto/produto.component';
import { ProdutoService } from './produto/produto.service';
import { ManutencaoProdutoComponent } from './produto/manutencao/manutencao.component';

const appRoutes: Routes = [
  { path: 'app-setor', component: SetorComponent },
  { path: 'app-manutencao', component: ManutencaoComponent },
  { path: 'app-produto', component: ProdutoComponent },
  { path: 'app-manutencao-produto', component: ManutencaoProdutoComponent },   //Adicionei essa linha
  { path: '**', redirectTo: '/' }
];

@NgModule({
  declarations: [
    AppComponent,
    SetorComponent,
    ManutencaoComponent,
    ProdutoComponent,
    ManutencaoProdutoComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot(appRoutes, {useHash: true})
  ],
  providers: [SetorService, ProdutoService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Modifiquei o app.component.html

<nav class="navbar navbar-default">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
            <a class="navbar-brand" href="#">Menu</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Cadastro <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a [routerLink]="['/app-setor']">Setor</a></li>
                        //Alterado href="#" para [routerLink]
                        <li><a [routerLink]="['/app-produto']">Produto</a></li>
                    </ul>
                </li>
            </ul>
        </div>
        <!-- /.navbar-collapse -->
    </div>
    <!-- /.container-fluid -->
</nav>

<router-outlet></router-outlet>

Gerei o build com o comando:

$ ng build

Gerou-se o diretório dist dentro do /myApp/

Modifiquei o arquivo setor.js do back-end /modulos/setor/controller/

exports.update = (req, res) => {

    const dados = req.body;

    model.Setor
        .update(dados, {
            where: {
                id: dados.id                //alterei essa linha
            }
        })
        .then((data) => }
            res.send(true);
        }).catch((error) => {
            console.log(error);
            res.send(false);
        });
};
exports.delete = (req, res) => {

    const dados = req.body;

    model.Setor
        .destroy({
            where: {
                id: dados.id                //alterei essa linha
            }
        })
        .then((rowDeleted) => {
            res.send(true);
        }, (err) => {
            console.log(err);
            res.send(false);
        });
};

Modifiquei o arquivo produto.js do back-end /modulos/produto/controller/

exports.update = (req, res) => {

    const dados = req.body;

    model.Produto
        .update(dados, {
            where: {
                id: dados.id                //alterei essa linha
            }
        })
        .then((data) => }
            res.send(true);
        }).catch((error) => {
            console.log(error);
            res.send(false);
        });
};
exports.delete = (req, res) => {

    const dados = req.body;

    model.Produto
        .destroy({
            where: {
                id: dados.id                //alterei essa linha
            }
        })
        .then((rowDeleted) => {
            res.send(true);
        }, (err) => {
            console.log(err);
            res.send(false);
        });
};

Modifiquei o arquivo app.js do back-end que se encontra na raíz do projeto.

'use strict'

const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');

const setor = require('./modulos/setor/setor');
const produto = require('./modulos/produto/produto');

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

//alterei a linha abaixo adicionando o diretório "dist"
app.use(express.static(path.join(__dirname, './myApp/dist/')));

setor.init(app);
produto.init(app);

module.exports = app;

Para rodar a api

$ node bin/www
Listening on port 3000

Conclusão

Com o @angular/cli é possivel criar um single-page application (SPA) simples em pouco tempo. Traduzir de uma linguagem para outra é principalmente uma questão de mudar a maneira como você organiza seu código e acessa APIs Angulares, qualquer coisa que você pode fazer com Angular no TypeScript você também pode fazer em JavaScript.

Estou disponibilizando o projeto no GitHub , acesse lá.

Wharley Ornelas

Wharley Ornelas

Meu nome é Wharley Ornelas. Desenvolvedor Full-Stack, com mais de 15 anos de experiência de software. Membro ativo em comunidade de desenvolvimento..