Angular – injetar arquivos CSS dinamicamente usando $ route.resolve e promessas

Coleção AngularJs Meetup South London | Este artigo

Conheça a folha de estilo CSS monolítica

É uma prática comum usar uma folha de estilo CSS monolítica para todo o aplicativo.

Este arquivo, digamos my-app.css , será o resultado de várias etapas: concatenação, compilação menos / sass e minimização usando uma ferramenta de construção como Grunt ou Gulp.

Isso é bom para sites de pequeno porte, mas o que acontece quando o site continua crescendo e mais CSS é adicionado a ele? Conforme você adiciona novas seções e componentes, ele fica cada vez maior. Um dia ele chega à sua mesa … A primeira visita ao seu aplicativo leva alguns segundos para carregar … Você se aprofunda um pouco e descobre que há um grande CSS monolítico em seu navegador.

Que opções existem para você agora? Bem, você claramente tem que dividir seu CSS em arquivos separados. Uma opção sensata seria usar diferentes seções de seu aplicativo para fazer isso. Em Angular, isso se traduz principalmente em suas rotas.

As rotas angulares já tratam do carregamento dos modelos correspondentes e da anexação do controlador, mas ainda não oferecem suporte a arquivos CSS.

Você poderia injetar seu CSS em seu template, mas então você perderá o cache do navegador e aumentará seus templates.

Dividindo arquivos CSS usando $ route.resolve

Para injetar nosso arquivo CSS enquanto navegamos para uma nova rota, usaremos $ route.resolve . Vamos usar _resolve _ de forma que a navegação só aconteça depois de injetarmos nosso CSS. Veja $ routeProvider.when () para mais detalhes.

// Routing setup
.config(function ($routeProvider) {
$routeProvider

.when('/home', {
controller
: 'homeCtrl',
templateUrl
: 'home.tpl.html'
}).when('/users', {
controller
: 'usersCtrl',
controllerAs
: 'vm',
templateUrl
: 'users.tpl.html',
resolve
: {
load
: ['injectCSS', function (injectCSS) {
return injectCSS.set("users", "users.css");
}]
}
}).otherwise({
// default page
redirectTo
: '/home'
});
})

Movemos o código real para um serviço de fábrica chamado _injectCSS _que contém uma função _set (id, url) _retornando uma promessa. Esta é a implementação real do serviço.

.factory("injectCSS", ['$q', '$http', 'MeasurementsService', function($q, $http, MeasurementsService){
var injectCSS = {};

var createLink = function(id, url) {
var link = document.createElement('link');
link
.id = id;
link
.rel = "stylesheet";
link
.type = "text/css";
link
.href = url;
return link;
}

var checkLoaded = function (url, deferred, tries) {
for (var i in document.styleSheets) {
var href = document.styleSheets[i].href || "";
if (href.split("/").slice(-1).join() === url) {
deferred
.resolve();
return;
}
}
tries
++;
setTimeout
(function(){checkLoaded(url, deferred, tries);}, 50);
};

injectCSS
.set = function(id, url){
var tries = 0,
deferred
= $q.defer(),
link
;

if(!angular.element('link#' + id).length) {
link
= createLink(id, url);
link
.onload = deferred.resolve;
angular
.element('head').append(link);
}
checkLoaded
(url, deferred, tries);

return deferred.promise;
};

return injectCSS;
}])

Este código pega ideias de diferentes fontes e adiciona algumas escolhas pessoais. Veja VIISON / RequireCSS e stackoverflow .

Para injetar nosso CSS, nós:

  • injetar uma tag de link no elemento head
  • evento hook to link.onload (dispara apenas em alguns navegadores)
  • verifique document.styleSheets quanto a alterações

Caso o CSS não carregue, por exemplo. falha de rede, ele continuará após o navegador adicionar uma entrada em styleSheets.

Achados

Enquanto trabalhava na solução final, descobri algumas coisas que não sabia antes:

  • injetar arquivos CSS dinamicamente não é tão fácil quanto parece. Existem muitas peculiaridades ao longo do caminho. Esta solução cobre apenas a superfície e estou começando a ver porque a equipe Angular a deixou de fora.
  • Aparentemente, $ http não usa o cache do navegador ao contrário do href no elemento de link. $ http sempre fará uma solicitação inicial, mesmo depois de ser armazenado em cache no navegador usando a tag do link.
  • Não faz diferença ter chamado $ http com o mesmo arquivo CSS. A tag do link irá ignorá-lo e levar o mesmo tempo para analisá-lo e pintá-lo na tela.> Use este código com cuidado, pois ele não foi testado em todos os navegadores.

Recursos

Encontre uma bancada de trabalho online que usei para experimentar diferentes configurações aqui .

Coleção AngularJs Meetup South London | Este artigo