Brincando com Angular2+Typescript, Parte 1

Gustavo Bicalho
8 min readSep 22, 2015

--

Montando o ambiente

Código completo: https://github.com/gusbicalho/ng2base/tree/tuto01-ambiente

Resolvi começar a brincar com Angular2. Eu já desenvolvi projetos complexos com AngularJS 1.x, e a nova versão do framework by Google me pareceu interessante, pois adota várias ideias novas (annotations, observables), aperfeiçoa ideias antigas (directives) e descarta ideias obsoletas (controllers, $scope).

Nessa primeira parte, vamos montar um ambiente de desenvolvimento para Angular2 com Typescript — linguagem desenvolvida pela Microsoft e recomendada pelo Google para desenvolvimento com Angular2. O tutorial pressupõe o básico de conhecimento com Typescript — se você não tem, vale muito a pena dar uma olhada.

Para automatizar as tarefas de build, vamos usar gulp. Explicaremos abaixo como criar cada tarefa, o que vai exigir o básico de conhecimento dessa ferramenta, mas, se preferir, você pode simplesmente copiar o código pronto dos arquivos de configuração.

O objetivo é ter um ambiente capaz de ser usado para o desenvolvimento de aplicações usando Angular2 e Typescript. Se você quiser apenas ver o Angular2 funcionando, veja o quickstart no site oficial.

O código final desse post está em https://github.com/gusbicalho/ng2base/tree/tuto01-ambiente. Você pode copiar e usá-lo, se preferir, ao invés de seguir o tutorial.

Para começar, você precisa ter node.js e o npm instalado. Veja como no site oficial do Node.js. Você também vai precisar do git (http://www.git-scm.com/).

Instalando pacotes

Primeiro, crie um diretório para seu projeto de testes (aqui usaremos ng2play), e inicialize o repositório git e o pacote npm:

mkdir ng2play
cd ng2play
git init
npm init

Como usaremos o projeto apenas para experimentos, podemos aceitar as opções padrão do npm init.

Crie o arquivo .gitignore, com o conteúdo abaixo:

# .gitignore
# Não guarde os módulos instalados do npm no repositório.
# Como a lista de módulos necessários fica gravada no arquivo
# package.json, se quiser clonar o repositório basta usar
# npm install para instalar todos eles.
node_modules

Começamos instalando globalmente alguns pacotes: gulp, um build system que nos ajudará a automatizar nossas tarefas; typescript, que contém o transpiler que converte TS para JS; e tsd, que gerencia as referências de tipos do TS.

npm install -g gulp typescript tsd

Agora, vamos instalar localmente (na pasta do projeto), todos os módulos que precisaremos para construir o projeto. São vários pacotes responsáveis por automatizar as tarefas de build, o processo de transpiling do TS para JS e o servidor de arquivos que rodará durante o desenvolvimento.

npm install --save-dev gulp gulp-load-plugins gulp-connect gulp-plumber gulp-util browserify watchify tsify vinyl-source-stream yargs

Browserify?

Três dos pacotes que instalamos acima foram browserify, watchify e tsify.

Browserify é uma ferramenta que permite usar módulos projetados para node.js e instalados com o npm no browser. Você pode requerer esses módulos no seu código javascript via require(‘modulo’). Ela analisa seu código-fonte e recursivamente determina quais módulos ele requer, buscando os arquivos correspondente no diretório node_modules. Por fim, inclui todos num único arquivo (bundle), que é referenciado no seu HTML.

Watchify vigia seus arquivos e, a qualquer mudança, refaz todo o processo de bundling via browserify de forma muito rápida, processando apenas os arquivos que mudaram. Tsify é um plugin para browserify que passa o código-fonte de seus arquivos *.ts pelo transpiler Typescript, gerando o código javascript suportado pelo browser.

Uma outra opção para trabalhar com Angular2 é o module loader SystemJS, com o package manager jspm. Esse método é até mais fácil de configurar que usando browserify, mas tem dois contras: 1) precisamos aprender a lidar com outro gerenciador de pacotes, que funciona em paralelo ao npm, e 2) o tempo de loading das páginas durante o desenvolvimento é muito maior, porque o navegador precisa carregar cada arquivo de cada módulo individualmente — o jspm até permite fazer um bundle do código todo, mas a operação é lenta e ainda não há uma opção como o watchify que acelere o processo.

Principalmente por causa do segundo contra, vamos usar browserify aqui, mas vale a pena ficar de olho no jspm/SystemJS no futuro.

Estrutura de diretórios

Até agora, seu projeto deve conter apenas os diretórios .git e node_modules e os arquivos .gitignore e package.json. Crie os diretórios e arquivos necessários para ter a estrutura abaixo:

ng2play
|---[.git]
|---[.temp] *
|---[node_modules]
|---[src] *
| |---[scripts] *
| | |--- app.ts *
| |
| |--- index.html *|
|
|--- .gitignore
|--- gulp.config.js *
|--- gulpfile.js *
|--- package.json
|--- tsconfig.json *

Os itens entre [colchetes] são diretórios, e aqueles marcados com * são os que você precisa criar agora.

Cole o código abaixo no arquivo index.html, e deixe os outros arquivos em branco por enquanto:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Angular2</title>
</head>
<body>
<my-app>Quando Angular2 estiver funcionando, esse texto vai sumir.</my-app>
<script src="bundle.js"></script>
</body>
</html>

Acrescente o diretório .temp ao arquivo .gitignore:

# .gitignore
# Não guarde os módulos instalados do npm no repositório.
# Como a lista de módulos necessários fica gravada no arquivo
# package.json, se quiser clonar o repositório basta usar
# npm install para instalar todos eles.
node_modules
# O diretório .temp guardará apenas arquivos compilados durante
# o desenvolvimento. Não é preciso gravá-lo no repositório.
.temp

Os arquivos tsconfig.json, gulpfile.js e gulp.config.js servirão para configurar o gulp na próxima seção. Na pasta .temp, colocaremos a versão compilada do nosso código-fonte (o bundle gerado pelo browserify). O servidor de desenvolvimento servirá tanto os arquivos da pasta src quanto os da pasta .temp na raiz. Assim, o browser poderá acessar o arquivo compilado como se estivesse no mesmo diretório que o index.html e outros arquivos (imagens, estilos, etc) que estejam na pasta src.

tsconfig.json

O arquivo tsconfig.json contém as configurações do transpiler de linha de comando do typescript. Se você quisesse converter o código TS para JS sem usar browserify, bastaria configurar corretamente o arquivo tsconfig.json e digitar tsc na linha de comando.

Como vamos usar browserify com o plugin tsify para transpilar o código TS, nem precisaríamos colocar nossas configuração no arquivo tsconfig.json. No entanto, ele serve de guia para editores e IDEs como o VS Code e o WebStorm analisarem seu código, para que possam pegar erros e oferecer intellisense. Então é bem útil colocar nossas configurações de typescript nesse arquivo, e depois importá-las para usar no tsify.

Copie o código abaixo para o arquivo tsconfig.json. Informações sobre a função de cada item estão no wiki do repósitório do TypeScript no github.

{
"compilerOptions": {
"target": "ES5",
"module": "commonjs",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"newLine": "LF"
},
"files": [
"src/scripts/app.ts"
]
}

Build tasks

O gulp funciona através do arquivo gulpfile.js. Nele você define tarefas ou tasks, que recebem um nome. Depois você pode rodar essas tarefas na linha de comando usando gulp nome-da-tarefa. Alguns editores/IDEs também permitem que você rode tarefas do gulp a partir deles.

Vamos colocar nossas definições de diretórios, nomes de arquivo, etc. em um módulo no arquivo gulp.config.js. Assim, separamos essas definições do código das tarefas propriamente ditas. Copie o código abaixo para o arquivo gulp.config.js:

module.exports = function() {
// Pega informações do arquivo tsconfig.
var tsconfig = require('./tsconfig.json');
var temp = '.temp/';
var src = 'src/';

var config = {
/** Diretório de arquivos compilados para dev */
temp: temp,
/** Arquivos fonte */
src: src,
/**
* Arquivos principais do app
* (processamento parte recursivamente daqui)
*/
tsMain: tsconfig.files,
tsconfig: tsconfig,
/** Nome do bundle gerado pelo browserify */
jsBundle: 'bundle.js',
/** index do app */
index: src + 'index.html',
/** Porta do servidor de dev */
devServerPort: 5000,
/** Recarregar a página se algum desses arquivos mudar */
watchReload: [
src + '**/*',
'!' + src + '**/*.ts'
],
};
return config;
};

No arquivo gulpfile.js, começamos importando os módulos necessários.

var gulp = require('gulp');
// args: pega os argumentos da linha de comando como array
var args = require('yargs').argv;
// config: nosso arquivo de configuração
var config = require('./gulp.config')();
// plugins: Carrega todos os módulos gulp-*
var plugins = require('gulp-load-plugins')();
// browserify e watchify
var browserify = require('browserify');
var watchify = require('watchify');
// source: transforma o output do browserify em um objeto que
// o gulp consegue manipular
var source = require('vinyl-source-stream');

Em seguida, definimos uma tarefa principal dev, que vai apenas chamar duas sub-tarefas dev-serve e dev-watchify. Também criamos uma função auxiliar log:

gulp.task('dev', ['dev-watchify', 'dev-serve']);gulp.task('dev-serve', function() {
log('Iniciando servidor: [' +
[].concat(config.temp).concat(config.src).join(', ') +
']');
});
gulp.task('dev-watchify', function() {
log('Iniciando watchify');
});
///////////////////////////////////////function log(msg) {
if (typeof(msg) === 'object') {
for (var item in msg) {
if (msg.hasOwnProperty(item)) {
plugins.util.log(plugins.util.colors.blue(msg[item]));
}
}
} else {
plugins.util.log(plugins.util.colors.blue(msg));
}
}

A tarefa dev-serve usará o plugin gulp-connect (plugins.connect) para criar um servidor para as pastas src e .temp. Observe os seguintes detalhes na configuração do servidor:

  • Caso o browser requeira um arquivo inexistente, enviaremos o arquivo index.html (fallback). Isso é útil quando desenvolvemos single page applications, já que uma URL que não se refira a um arquivo no servidor provavelmente servirá para direcionar o cliente a um estado específico, e deverá ser processada via javascript no browser.
  • Se o usuário passar a váriável port na linha de comando (e.g. gulp dev — port 999), usaremos esse valor como porta do servidor. Caso contrário, usaremos a porta definida em devServerPort no arquivo gulp.config.js
  • Ativaremos o recurso livereload, assim o browser atualizará a página automaticamente quando mudarmos algum arquivo.
  • Como o watchify pode demorar um ou dois segundos para terminar de compilar o bundle.js quando modificamos nosso código-fonte, precisamos atualizar o browser só quando houver uma modificação no bundle. Por isso, não vamos vigiar os arquivos .ts (ver campo watchReload no arquivo gulp.config.js).
gulp.task('dev-serve', function() {
log('Iniciando servidor: [' +
[].concat(config.temp).concat(config.src).join(', ') +
']');
var port = args.port || config.devServerPort;
plugins.connect.server({
root: [config.temp, config.src],
fallback: config.index,
port: port,
livereload: true
});
gulp.watch(config.watchReload, function() {
return gulp.src(config.watchReload, {read: false})
.pipe(plugins.connect.reload());
});
});

Por fim, a tarefa gulp-watchify configura o watchify para processar nosso código-fonte e salvar o resultado no arquivo .temp/bundle.js, atualizando o resultado sempre que mudarmos alguma coisa. Usamos o módulo vinyl-source-stream para capturar o output do browserify e passar para o gulp. No fim, também notificamos o gulp-connect para que recarregue a página no browser.

gulp.task('dev-watchify', function() {
log('Iniciando watchify');
// Cria o bundler watchify
var bundler = watchify(browserify(config.tsMain, watchify.args));
// Ativa o tsify para transpilar nossos arquivos TS
bundler.plugin('tsify', config.tsconfig);
// Sempre que houver mudança, gera novo bundle
bundler.on('update', watchifyBundle);
bundler.on('log', log); // Escreve logs no terminal

return watchifyBundle();
function watchifyBundle() {
return bundler.bundle()
.on('error',
plugins.util.log.bind(plugins.util, 'Browserify Error'))
.pipe(source(config.jsBundle))
.pipe(gulp.dest(config.temp))
.pipe(plugins.connect.reload());
}
});

Nesse ponto, seus arquivos tsconfig, gulp.config.js e gulpfile.js devem ter o código que está nesse gist: https://gist.github.com/gusbicalho/9235d58c13a4fe999e9f .

Abra o terminal e rode o comando abaixo, depois acesse http://localhost:5000 no browser:

gulp dev

Você deverá ver uma página com o texto “Quando Angular2 estiver funcionando, esse texto vai sumir.”.

Teste final

Finalmente, vamos instalar o angular2 e outras bibliotecas requeridas:

npm install --save angular2 reflect-metadata zone.js
tsd query angular2 es6-promise rx rx-lite --action install --save

O segundo comando vai criar a pasta typings, que contém definições de tipos para Typescript. Teremos que incluir uma referência ao arquivo typings/tsd.d.ts nos nossos scripts para que o transpiler reconheça os tipos do Angular2.

No arquivo app.ts, cole o código abaixo:

/// <reference path="../../typings/tsd.d.ts" />
import 'zone.js';
import 'reflect-metadata';
import {Component, View, bootstrap,
NgFor} from 'angular2/angular2';
@Component({
selector: 'my-app'
})
@View({
template: '<h1 *ng-for="#name of names">Oi {{name}}!</h1>',
directives: [NgFor]
})
export class MyAppComponent {
names: string[];

constructor() {
this.names = ['Alice', 'Bob', 'Charlie'];
}
}
bootstrap(MyAppComponent);

Inicie a tarefa gulp dev e abra o browser. Se você vir uma tela como a abaixo, você está rodando Angular2!

Screenshot do teste final. Diga oi a todos os seus amigos! :D

No próximo post, vou explicar o código de exemplo do app.ts acima e fazer algumas coisas mais interessantes, usando property-bindings, event-bindings e two-way-bindings. Até lá!

--

--