I'm writing an Angular directive to display some information about a music album, currently it shows the information below the album art, but if the element gets too small then it should shrink the album art and put the information next to it. At the moment I just have the html in the page directly and have css to do the changes in the main page, but this causes the page to be quite monolithic as it also display other things, which is why I want to seperate it out into directives.
However I can't see how to include CSS in the directive, I don't want to include it inline in the html, and I could put a style tag in the html and put it in there, but then it would be repeated every time I use the directive. Is there some way of injecting a link to a CSS file into the head from the directive? Like there is a templateUrl field is there a stylesheetUrl or something?
You could check this module: angular-css-injector.
Be warned it's currently only compatible with angular 1.2.x...
You can inject css in your directive like this:
var head = document.getElementsByTagName('head')[0];
var cs = document.createElement('link');
cs.rel = 'stylesheet';
cs.href = 'css/myStylesheet.css';
head.appendChild(cs);
So a directive would look like this:
app.directive('myDirective', function () {
var head = document.getElementsByTagName('head')[0];
var cs = document.createElement('link');
cs.rel = 'stylesheet';
cs.href = 'css/myStylesheet.css';
head.appendChild(cs);
return {
templateUrl:'templates/myTemplate.html',
link: function (scope, elm, attrs, ctrl) {
}
};
});
Structuring an angular app is one of the hardest things about learning angular. Angular tries hard to modularise code, but (the current state of) html, css, and javascript doesn't really allow you to package the things together, so you have to find a way that works well for you.
The way I keep things separate (yet together) is generally using my build system (I use gulp), and a CSS preprocessor (for me, Stylus).
My process for creating a new directive is as follows:
Define a new angular module (in its own file) my-albums.coffee:
angular.module('my-albums', [])
.directive('myAlbumDirective', ()->
restrict: 'A'
templateUrl: 'SomeTemplate.jade'
# etc.
)
Create a jade template my-album-directive.jade
.album
img(ng-src="{{album.imageUrl}})
span.name {{album.name}}
Create the stylus file with the same name as the module prefixed with an underscore: _my-albums.styl. In here I will include module specific css.
[myAlbumDirective]
.album
display flex
flex-direction column
#media screen and (min-width: 600px)
flex-direction row
Then, whenever I import an angular module into my app.coffee (which contains a long list of module imports), I also import its style in my main.styl stylesheet:
#import '../my-albums/_my-albums.styl'
When I run my build system, it (among other things):
Automatically compiles .jade files into a app.templates angular module (pre-populating the $templateCache (app.templates is included in the imports in app.coffee
Compiles and concatenates all coffeescript into script.js
Compiles and concatenates all stylus files whose filenames do not begin with an underscore into style.css
Then inside my index page I just have two imports:
script(src='js/app.js')
link(rel='stylesheet', href='css/style.css')
TL;DR:
There's no easy way of keeping your directive code separate from the rest of the page, but if you research build systems and other people's angular project structures, you'll find something you like.
Note
SoonTM things will be neater (see web components and angular 2.0)
Related
I am "attempting" to use Sveltejs as a front-end web framework for a project built on Django (relevant as it defines the structure of my application). I am attempting to include Svelte components onto the various templates I have built for my Django application. Now, using the customElement API, I was able to compile and use my Svelte components as custom elements directly in my HTML templates which was great except for one major problem: my global styles weren't propagating to the components. This is, as I found out, because all custom elements are actually compiled as web components, meaning they wrap their internal HTML to prevent styles from affecting them. This is not the behavior I want. However, it seems to be the only real way to use my Svelte components in a way that works cohesively with my HTML templates. How do I solve this problem?
I am using webpack as my module bundler. I can comment a gist link to my webpack.config.js if it would be helpful, but as you can imagine, I am compiling for multiple Django "apps" and so my config is a bit messy. For reference, I have gotten everything including local styling with Sass and custom elements to work as intended. This is simply a question of "how to do something" which despite me spending literal hours Googling, I have been unable to find a clear answer to.
I know you can use the client API directly to access components, but not only is this tedious and messy (especially if I need to compose components on different HTML templates together), but I can't seem to get webpack to expose my Svelte components as Javascript classes. Here is how I am doing this approach (which is not working):
<!-- in the head -->
<script src="bundle.js"></script>
<!-- in the body -->
<script>new bundle.App{target: ...}</script>
It is telling me that App is not defined. Here is what my individual output configs in my webpack.config.js look like:
output: {
library: 'bundle',
// I do not understand at all what these two lines do -- I just found
// them somewhere on the interwebs as a suggestion to solve this problem
libraryTarget: 'umd',
umdNamedDefine: true,
// path, filename, etc.
}
To boil this down, I really have three, intertwined questions:
Can I use the customElement API (which I much prefer) and still apply global styles?
If I can't use the customElement API, is there a better approach to this problem that would allow for global styles?
If there is no other option, how do I properly use the client API with webpack?
TL;DR: There is no clean/perfect answer for this.
For once, there is no way to inject global styles into the Shadow Dom. Having said that there are few things you can try.
First, if you are not using slots, then you can write your own custom element registration function and use that to register elements. You will have to write your own adapter for web components that extends from HTMLElement class. In this approach, each Svelte component would be independent app that you are simply initializing from your web component. This is the best alternative you can explore
Additionally, you can use Constructable Stylesheets. It allows you to programmatically construct a stylesheet object and attach it to a Shadow DOM. Of course, this work only when you have flat components. When your web components are nested within one-another, each would have its Shadow DOM. You would have to create a common global style as a constructable stylesheet and attach to each component. Look here for the example:
const sheet = new CSSStyleSheet();
// Replace all styles synchronously for this style sheet
sheet.replaceSync('p { color: green; }');
class FancyComponent1 extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
// Attaching the style sheet to the Shadow DOM of this component
shadowRoot.adoptedStyleSheets = [sheet];
shadowRoot.innerHTML = `
<div>
<p>Hello World</p>
</div>
`;
}
}
class FancyComponent2 extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
// Same style sheet can also be used by another web component
shadowRoot.adoptedStyleSheets = [sheet];
// You can even manipulate the style sheet with plain JS manipulations
setTimeout(() => shadowRoot.adoptedStyleSheets = [], 2000);
shadowRoot.innerHTML = `
<div>
<p>Hello World</p>
</div>
`;
}
}
In above example, the sheet is a common stylesheet which is being used in two separate web components. But again, you will have to write you own web component wrapper to achieve this.
Is there any performance (or otherwise ) downside to using the #import directive inside custom elements?
This is how I pass global styles to svelte custom elements:
/assets/theme.css
---
:root,:host{
--some-var:1rem;
}
Then inside the component:
CustomElement.svelte
---
<svelte:options tag="custom-element"/>
...
<style>
#import "/assets/theme.css";
:host{
padding:var(--some-var)
}
</style>
Context: until now I didn't mind about how import template html file to my vanilla webcomponent because I always wrote small html codes. So I have coded html on top of my webcomponent .js file and do something like:
const template = document.createElement("template");
template.innerHTML = `<div id="firstdiv"><input id="inputAny"/></div>`;
class anyVanillaWebComponent extends HTMLElement {
...
connectedCallback() {
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(template.content.cloneNode(true));
const inputAny = this.shadowRoot.getElementById("inputAny");
...
This is quite common find in tutorials, blogs and foruns. Now I want to separate html from javascript assuming this will make my project tree more clear.
Searching around I found some discussion based on assumption that "import" will not be supported anymore in Browsers [(see UPDATE discussion on bottom about alternatives to import)].1
Basically this drove me to two possibilities:
1 - importing .js file to html from html
Exemplifying:
<template id="my-webcomponent-template">
<link rel="stylesheet" href="./some.css">
<div>some content ...</div>
</template>
<script src="MyWebcomponent.js"></script>
2 - fetch asynchronously my-webcomponent.html from .js file
(async () => {
const res = await fetch('/MyWebcomponent.html');
const textTemplate = await res.text();
const HTMLTemplate = new DOMParser().parseFromString(textTemplate, 'text/html')
.querySelector('template');
class MyWebcomponent extends HTMLElement {...
Based on such discussion from 2017, it seems I should avoid the option 1 but it is not clear for me why and if there is some real advantage on option 2. So my straight question is: is there any real diference between "importing" and "fetching" html file while coding Vanilla Webcomponents which is expected to be rendered straight by browsers that support natively Webcomponents (for instance, Chrome)?
If you don't plan to use your custom element in various pages then the 2 solutions are good.
In the first one it could be faster because you will save an HTTP request, for the HTML template is immediately available in the main HTML page.
But if you plan to reuse the custom element (or for better code separation), the second solution is better because the consumer is separated from the web component.
You can always look at a bundler/packager. Webpack and others work well.
I wrote this one specifically for Web Components:
https://www.npmjs.com/package/component-build-tools
It allows zero or more templates as real HTML files and allows importation of Locale based strings if you want to localize your components.
It also allows you to write your component using ES6 import and then cross-compile to ES5 if needed and allows output as an ES6 module file, a CJS file or a simple IIFE file.
I have the following files. All I want to do is to be able to create different components that are injected. How do I achieve this using require.js? Here are my files:
main.js
define(function(require) {
'use strict';
var Vue = require('vue');
var myTemplate = require('text!myTemplate.html');
return new Vue({
template: myTemplate,
});
});
myTemplate.html
<div>
<my-first-component></my-first-component>
</div>
MyFirstComponent.vue
<template>
<div>This is my component!</div>
</template>
<script>
export default {}
</script>
I'm going to assume you're using webpack as explained in the Vue.js docs, or else your .vue file is useless. If you're not, go check how to set up a webpack Vue app first, it's what lets you use .vue files.
import Menubar from '../components/menubar/main.vue';
Vue.component('menubar', Menubar);
That's how you add e.g. a menubar component to the global scope. If you want to add the component to just a small part of your app, here's another way of doing it (this is taken from inside another component, but can be used in exactly the same manner on your primary Vue object):
import Sidebar from '../../components/sidebar/main.vue';
export default {
props: [""],
components: {
'sidebar': Sidebar
},
...
You can load components without webpack, but I don't recommend it, if you're gonna keep using Vue (which I strongly suggest you do) it's worth it to look into using webpack.
Update
Once again, really, really, really consider using webpack instead if you're gonna be continuing with Vue.js, the setup may be slightly more annoying but the end result and development process is waaaay better.
Anyway, here's how you'd create a component without webpack, note that without webpack you can't use .vue files since the .vue format is part of their webpack plugin. If you don't like the below solution you can also use e.g. ajax requests to load .vue files, I believe there is a project somewhere out there that does this but I can't find it right now, but the end result is better with webpack than with ajax anyway so I'd still recommend going with that method.
var mytemplate = `<div>
<h1>This is my template</h1>
</div>`
Vue.component('mycomp1', {
template: mytemplate
});
Vue.component('mycomp2', {
template: `
<div>
Hello, {{ name }}!
</div>
`,
props: ['name'],
});
As you can see, this method is A LOT more cumbersome. If you want to go with this method I'd recommend splitting all components into their own script files and loading all those components separately prior to running your actual app.
Note that `Text` is a multi line string in javascript, it makes it a little easier to write your template.
And as I said, there is some project out there for loading .vue files using ajax, but I can't for the life of me find it right now.
I am making a stand alone module that I would like to use in multiple projects or maybe make a bower package out of. I figured out how to package the html templates by converting them to js but I can't figure out a good way to package some CSS with it.
I tried out angular-css and it does what I need with one downfall, it requires me to specify the file path to the CSS relative to the index.html. This is a problem as the file structures may be different in different projects and I would like this to be packaged in a way that it can just be added to the project and work.
As it stands I'm adding CSS with the method below but I'm not sure this is really a good way to do it. I can add the CSS this way and only add it once but I cant remove it when it's not needed
In the directives controller I use this code to add CSS styles
var markerAnim = document.getElementById('markerAnim');
if (!markerAnim) {
var markerStyles = {
animation: 'some css',
locMarker: 'more css',
after: 'a little more css'
};
var styleSheet = markerStyles.animation + markerStyles.locMarker + markerStyles.after;
angular.element(document).find('head').prepend('<style id="markerAnim" type="text/css">' + styleSheet + '</style>');
}
Please see attachment.
That's the template structure. login.jade extends layout.jade.
My problem is that I want:
a global.css file loaded in all pages (done)
login.css file loaded only when viewing login.jade (problem)
I already created a working pipeline configuration. The task looks like this:
devStyles: {
options: {
startTag: '<!--STYLES-->',
endTag: '<!--STYLES END-->',
fileTmpl: '<link rel="stylesheet" href="%s">',
appRoot: '.tmp/public'
},
files: {
'.tmp/public/**/*.html': require('../pipeline').frontendGlobalCssFiles,
'views/**/*.html': require('../pipeline').frontendGlobalCssFiles,
'views/**/*.jade': require('../pipeline').frontendGlobalCssFiles,
'views/auth/login.jade': require('../pipeline').frontendLoginCssFiles
}
}
The problem is that the last files rule doesn't work. I'm sure that frontendLoginCssFiles is ok, since if I load it with the 'views/**/*.jade' path, it works. So what's the problem here?
Ok, solved. Basically, the problem is not strictly related to jade inheritance. What sails-linker really does is add the assets to the physical files, no matter if they're parents, children or partials. All it does is take the passed files, search for the proper start/end Tags and add the related html BEFORE compiling the real served pages.
My problem was that I was not adding the start/end tags to the login (child) page.
Anyway, even doing so, we've got then the problem that ALL the compiled pages will load ALL the assets. So for example, the login assets will be also loaded on the home page. Not so good.
To solve, briefly, I used specific start/end tags for page-specific assets.
Following, the 'long' story:
Note: since we're speaking about jade templates, in sails-linker we are looking into the *Jade tasks, and therefore the comments are written with jade syntax (ex. // STYLES and not <!--STYLES-->)
First, we'll isolate 'global assets' > the ones we want to load on every page:
Change all the // STYLES instances to something like // GLOBAL STYLES. Both in sails-linker and eventually on .jade views.
/tasks/pipeline.js: rename default filelists to something like globalCssFiles, jsfiles etc, and change all the instances of that name.
/tasks/config/sails-linker.js: rename all the instances of point 2 modifications.
Second, we'll add 'page assets':
On child jade pages, use something like // PAGE STYLES comments.
/tasks/pipeline.js: create page-specific filelists, so ex:
var frontendLoginCss = ['styles/login.css'];
and below:
module.exports.frontendLoginCss = frontendLoginCss.map(function(path) {
return '.tmp/public/' + path;
});
/tasks/config/sails-linker.js: create page-specific tasks, like:
devPageStylesJade: {
options: {
startTag: '// PAGE STYLES',
endTag: '// PAGE STYLES END',
fileTmpl: 'link(rel="stylesheet", href="%s")',
appRoot: '.tmp/public'
},
files: {
'views/auth/login.jade': require('../pipeline').frontendLoginCss
}
}
/tasks/register/*: add the relevant tasks in all the files where you should to. For example, my linkAssets.js could be like this:
module.exports = function (grunt) {
grunt.registerTask('linkAssets', [
'sails-linker:devJs',
'sails-linker:devStyles',
'sails-linker:devTpl',
'sails-linker:devJsJade',
'sails-linker:devPageJsJade', //added
'sails-linker:devStylesJade',
'sails-linker:devPageStylesJade', //added
'sails-linker:devTplJade'
]);
};
In jade templates, use block syntax for importing the styles/js, so you can use append to append the assets to the block. Ex, the general 'parent' layout.jade will have:
block styles
// GLOBAL STYLES
// GLOBAL STYLES END
while the child template login.jade will have:
append styles
// PAGE STYLES
// PAGE STYLES END
Final tip: it really doesn't matter where you write the append directives in child templates, they'll always appended where the parent template did defined them. So I'll write all appends on the bottom of my child templates (more clean).
Hope will be usefull for people with same issues!