Criando Universal Apps para WindowsRT e Windows Phone com AngularJS

Esse post foi migrado do blog antigo. A publicação original foi no dia 12/08/2014

E aí! Tudo bem? Esse é um post sobre Universal Apps e vai explicar como podemos criar aplicativos para WindowsRT e Windows Phone com HTML5, javascript e, tudo isso, compartilhando o máximo possível de código!

Antes de começar

Universal Apps é um novo modelo de aplicações lançado em 2014 para aumentar ainda mais o compartilhamento de código entre aplicações WinRT e WP. Já existe bastante material na internet pra você começar:

Apps nativos com HTML5 e javascript

O mais legal é poder aproveitar meu conhecimento em HTML5, javascript, CSS3 e AngularJS para desenvolver apps nativos para WinRT e WP.

Começando

Depois de criar o projeto vazio, precisamos seguir alguns passos para ter um app com AngularJS funcionando!

Por enquanto, nosso projeto deve estar assim:

A primeira coisa a fazer é se livrar daquela pasta desnecessária chamada SharedApp.

O vídeo não aparece? Clique e faça o download.

Depois, podemos começar a baixar os pacotes nuget em qualquer um dos projetos. No meu exemplo, vou baixar no projeto de WinRT. Para isso, vou usar o Package Manager Console (Menu Tools > Nuget Package Manager > Package Manager Console), mas você pode fazer pela interface visual também!

Install-Package AngularJS.Core  
Install-Package AngularJS.Route
Install-Package jQuery

Agora, essa deve ser sua estrutura de projetos.

Mas nós queremos que os scripts sejam compartilhados entre os projetos, então precisamos arrastar a pasta scripts para o projeto .Shared (lembre-se de fazer isso sempre que adicionar um novo script que precisa ser compartilhado).

O vídeo não aparece? Clique e faça o download.

Agora, só precisamos adicionar os scripts no html. Existe um arquivo default.html para cada projeto. Isso acontece porque cada um dos projetos tem que carregar seu próprio WinJS. Esse é o único código que não vai ser compartilhado.

No projeto de WinRT, vamos deixar o default.html assim:

<!DOCTYPE html>  
<html ng-app="app">
<head>
<meta charset="utf-8" />
<title>Meu app WinRT</title>

<!-- WinJS -->
<link href="//Microsoft.WinJS.2.0/css/ui-light.css" rel="stylesheet" />

<!-- App -->
<link href="/css/default.css" rel="stylesheet" />

<!-- WinJS references -->
<script src="//Microsoft.WinJS.2.0/js/base.js"></script>
<script src="//Microsoft.WinJS.2.0/js/ui.js"></script>

<!-- Lib -->
<script src="/Scripts/jquery-2.1.1.js"></script>
<script src="/Scripts/angular.js"></script>
<script src="/Scripts/angular-route.js"></script>

<!-- App -->
<script src="/js/default.js"></script>
</head>
<body>
<div ng-view></div>
</body>
</html>

Já no projeto de Windows Phone, vamos deixar assim:

<!DOCTYPE html>  
<html ng-app="app">
<head>
<meta charset="utf-8" />
<title>Meu app WP</title>

<!-- WinJS -->
<!-- At runtime, ui-themed.css resolves to ui-themed.light.css or ui-themed.dark.css
based on the user’s theme setting. This is part of the MRT resource loading functionality. -->
<link href="/css/ui-themed.css" rel="stylesheet" />

<!-- App -->
<link href="/css/default.css" rel="stylesheet" />

<!-- WinJS -->
<script src="//Microsoft.Phone.WinJS.2.1/js/base.js"></script>
<script src="//Microsoft.Phone.WinJS.2.1/js/ui.js"></script>

<!-- Lib -->
<script src="/Scripts/jquery-2.1.1.js"></script>
<script src="/Scripts/angular.js"></script>
<script src="/Scripts/angular-route.js"></script>

<!-- App -->
<script src="/js/default.js"></script>
</head>
<body>
<div ng-view></div>
</body>
</html>

Isso é tudo o que precisa ser feito.

A partir de agora, só precisamos do nosso app!

Todo o nosso app vai ser compartilhado, então só vamos trabalhar no projeto .Shared. Para não mexer no arquivo default.js, vamos criar um novo arquivo chamado app.js e adicionar o script aos arquivos default.html de cada projeto.

Aqui, é só mais um app em angularjs. Sem segredos!

(function () {
'use strict';

var myApp = angular.module('app', ['ngRoute']);

myApp.config(['$routeProvider', function ($routeProvider) {
$routeProvider.
when('/', {
template: '<div><h2>Olá {{nome}}!</h2><input type="text" ng-model="nome" /></div>',
controller: 'AppController'
}).
otherwise({
redirectTo: '/'
});
}
]);
myApp.controller('AppController', ['$scope', function ($scope) {
$scope.nome = 'Luís Rudge';
}
]);
})();

Vamos rodar o app e:

Erro!

Esse erro acontece porque o AngularJS ainda não se entende direito com as diretrizes de segurança desse tipo de app. Pra resolver, precisamos fazer duas simples alterações.

No arquivo app.js, precisamos adicionar isso (logo após a declaração das rotas):

myApp.run(['$rootScope', function ($rootScope) {
var scope = Object.getPrototypeOf($rootScope);
var old$Eval = scope.$eval;
scope.$eval = function (expr, locals) {
var that = this;
if (window.MSApp) {
return MSApp.execUnsafeLocalFunction(function () {
return old$Eval.call(that, expr, locals);
});
} else {
return old$Eval.call(that, expr, locals);
}
};
}]);

Já no arquivo angular.js, englobaremos a última linha dentro deste bloco:

MSApp.execUnsafeLocalFunction(function() {  
//última linha aqui
//!window.angular.$$csp() && window.angular.element(document)[.....]
});

Explicando: o método MSApp.execUnsafeLocalFunction é uma api do Windows para chamar explicitamente uma função que pode ser unsafe (para o WinRT).

Essa parte não é tão trivial e pode ocasionar algum retrabalho ao atualizar a versão do AngularJS, mas acho que isso será resolvido nas próximas versões :)

Bom, de qualquer maneira, agora temos nosso app funcionando no WinRT!

O vídeo não aparece? Clique e faça o download.

E também no Windows Phone!

O vídeo não aparece? Clique e faça o download.

Lembrando: Isso tudo sem duplicar a lógica do app

No próximo post, vou mostrar como interagir com as apis do Windows utilizando AngularJS e WinJS!