I wan't to add some interactions to my Angular 2 project to enhance its user experience. I know how to interact with DOM, or change the status of element property. It is possible to write code for each of my component. But there are some examples which will be used site wide, for which I don't want to repeat the code everywhere I want to use it.
A simple example will the fade in when scroll elements. I know how to achieve this in a particular controller, but I need help to make this behaviour global without code repetition.
I Javascript / jQuery, we can have a master js file included which will have the event listeners bound to the elements, which is available for all pages. How to achieve similar in Angular?
This can mostly be done with Directives. Taking your example, you would create a [scroll-fade] Directive:
#Directive({
selector: '[scroll-fade]'
})
export class ScrollFade {
}
You'd then need to listen for the global scroll event, with #HostListener('window:scroll') and apply your styles to the :host element.
You would then use it by applying it to the elements you want affected:
<div class="scroller" scroll-fade></div>
If you need something more complex, you could always build a Shared Module where you would create reusable components, without repeating the code - which you could then transform into a library and share back with the community.
Theres an answer here on StackOverflow that explains how to.
Related
I am having the same error as this person:
Add onclick event to SVG element
But I am having it in Angular which makes it even harder to deal with.
I want single SVGPathElement to be clickable & access component properties and functions in this click handler. But this is giving me the error 'property undefined' or 'function doesn't exist'.
According to this answer:
Add onclick event to SVG element
It's because all of the JavaScript must be included inside of the SVG for it to run.
But I cannot even do what the mentioned answer Add onclick event to SVG element suggested, since I get an error probably because Angular cannot create the component when parsing the html.
I also looked at http://xn--dahlstrm-t4a.net/svg/html/from-svg-to-parent-html-script.html (mentioned in the comments of the above answer) but that gave me the same error as in the picture above.
Now the second requirement to the solution to my question:
I am planning on having many different svgs and interacting differently with them. So manually pasting some <script> into the svg won't be a scalable nor maintainable solution for me. So actually I would prefer not doing this over the html with <script> tags at all.
If anyone has a good solution to solve both of my inquries in angular I would be greatful.
Additional Info:
I am using the ionic framework but I don't think it matters in this case
One possible alternative solution:
One could use ReactJS instead of Angular.
SVGPathElement<->Component interaction works in ReactJS without doing anything manually. I have no idea why but there is no such errors maybe because the component is not a class but rather a function. This would be great to know why exactly! I don't want to use ReactJS since I am not familiar with it. But maybe it's my best option to just switch frameworks since SvgPathElement<->Component interaction is a key part of my mobile app (it's like the seterra mobile app (https://play.google.com/store/apps/details?id=com.seterra&hl=en&gl=US)).
I found the culprit:
Don't do this (Angular doesn't allow it for some reason):
ngOnInit() {
document.querySelectorAll('.area').forEach((element: Element) => {
element.addEventListener('click', this.clickHandler);
}
);
}
Instead you have to add the click handlers manually like this:
<svg id="svgroot">
<path ... (click)="clickHandler()">
If anyone knows how to make this work by adding click handlers programmatically then this would make the solution a lot more automated/scalable/maintainable.
Adding click handlers programmatically to the SvgPathElement works in ReactJS for some reason, probably due to some internal differences between how the frameworks generate components.
I am trying to figure out a way to use Ember components as a view template for tooltips. Let me explain this:
I am supposed to create a library to show tooltips in Ember. The content of this tooltip is unknown. It might be very complex or it might be a simple text. The developer is the one who will decide it but the library must offer a way to do it easily. Also, I want to offer this solution in the format of an Ember modifier so that the developer would code it something like:
<div {{tooltip foo bar}}>
Hello World
</div>
As the modifier offers a reference to its element it is easy to use the good old JS to create elements and append them to this element. For a simple text it works like a charm and I was able to do it already as shown in the example below, but for complex component it's better to create a component and show it inside the tooltip's container.
// tooltip.js
import { modifier } from 'ember-modifier';
import { isPresent } from '#ember/utils'
export default modifier(function tooltip(element) {
const content = document.createElement('span')
content.append('Hi, I am the tooltip\'s text')
element.append(content)
});
The problem starts when you want to build a complex view, specially if it's supposed to contain some logic associated.
What I thought is that I could programmatically insert Ember components into the element that is passed as the argument of the modifier function. I can't use the dynamic component helper ({{component}}) as it infers that I have to mess with .hbs files more than what I am doing already and I can't approach it using the tooltip as a component; I need it to be a modifier.
I looked into this solution here but it doesn't seem to work in Ember 3.8.
Can anybody give me a clue on how do make it happen?
I am using Ember 3.8 and Ember Modifier#1.0.5
You could use the same technique that either of these use:
https://github.com/NullVoxPopuli/ember-popperjs
https://github.com/CrowdStrike/ember-velcro
They allow for "any content tooltips" by using an external library (popper or floating-ui, depending -- these are important though, because positioning is hard).
The gist is the following:
modifier on the "reference" / "hook" element (what you hover over)
modifier on the "target" / "popover" / "loop" element
some code that communicates between the two modifiers to wait until both are present before rendering the tooltip in the correct location / position.
The key part missing from your original code is that you need two modifiers -- tooltips with complex content are not possible with a single modifier (unless you manually manage element references).
For ember 3.8, I don't know how much you'll be able to do.
Ember 3.28 is the oldest LTS supported now and is passed its last bugfixes date, and it will step receiving security patches in January -- see: https://emberjs.com/releases/lts/
you may be able to use 2 global modifiers and a service to communicate between them -- but I don't know how that would work when you attempt to have multiple tooltips / popovers on the screen at the same time.
The minimum ember version you need to use the two addons linked above is 3.27.
Personally, it's well worth the upgrade, as staying up to date is generally easier than leap frogging years at a time (because you have the community doing upgrades with you) -- and shiny stuff is fun :)
I'm learning web components. When designing a custom element, I have to decide what is going to be hidden in the the shadow DOM. The remainder will then be exposed in the light DOM.
As far as I understand, the APIs allow two extreme use cases with different tradeoffs:
hide almost nothing in the shadow DOM, most of the element's content is in the light DOM and in the element's attributes:
this allows an HTML author to provide anything for the component to display without writing JS;
this is close to the status quo regarding searchability and accessibility
but there is little reward for the work involved; I add complexity with components but they don't encapsulate anything (everything is exposed).
hide almost everything in the shadow DOM, the element's innerHTML is empty:
this requires the element to be instantiated from JS;
this locks usage a lot more because instantiating from JS is more strict (type-wise) than using HTML slots and attributes;
this may be less searchable and accessible (I'm not sure whether this is the case);
I currently lean toward hiding everything in the shadow DOM for the following reasons:
I intend to instantiate everything from JS. I'm not going to author pages in HTML manually. It would be more work to code both an HTML API and a JS API.
It's less cognitive work to hide everything. I don't need to find a right balance about which information is visible in the light DOM.
It's closer to most JS frameworks I'm familiar with.
Am I missing something?
Edit
Thank you, I am answered that it depends on the use case which partially answers my question. But I'm still missing an answer regarding the case I'm in: I'd rather not support slots for some of my components.
I'll add an example for each extreme of the spectrum:
Light-DOM-heavy component: the component user has to insert elements into slots
<template id=light-email-view>
<div>
<div><slot name=from></slot></div>
<ul><slot name=to></slot></ul>
<h1><slot name=subject></slot></h1>
<div><slot name=content></slot></div>
<ul><slot name=attachements></slot></ul>
<div class=zero-attachment-fallback>no attachments</div>
</div>
</template>
Shadow-DOM-heavy component: the component user has to use the JS API
<template id=shadow-email-view>
<div></div>
</template>
<script>
...
let view = document.createElement('shadow-email-view');
// this method renders the email in the shadow DOM entirely
view.renderFromOject(email);
container.appendChild(view);
</script>
In the first example, the component author has more work to do since they need to "parse" the DOM: they have to count attachments to toggle the fallback; basically, any transformation of input that isn't the browser copying an element from the light DOM into the matching shadow DOM slot. Then they need to listen for attribute changes and whatnot. The component user also has more work, they have to insert the right elements into the right slots, some of them non-trivial (the email content may have to be linkified).
In the second example, the component author doesn't need to implement support for instantiating from HTML with slots. But the component user has to instantiate from JS. All the rendering is done in the .renderFromObject method by the component author. Some additional methods provide hooks to update the view if needed.
One may advocate for a middle ground by having the component offer both slots and JS helpers to fill those. But I don't see the point if the component isn't to be used by HTML authors and that's still more work.
So, is putting everything with the shadow DOM viable or should I provide slots because not doing so isn't standard compliant and my code is going to break on some user agent expecting them (ignoring older UAs that are not at all aware of custom elements)?
#supersharp has nailed it.
One thing I see with Web Components is that people tend to have their component do way too much instead of breaking into smaller components.
Let's consider some native elements:
<form> there is no shadow DOM and the only thing it does is read values out of its children form elements to be able to do an HTTP GET, POST, etc.
<video> 100% shadowDOM and the only thing it uses the app supplied children for is to define what video will be playing. The user can not adjust any CSS for the shadow children of the <video> tag. Nor should they be allowed to. The only thing the <video> tag allows is the ability to hide or show those shadow children. The <audio> tag does the same thing.
<h1> to <h6> No shadow. All this does is set a default font-size and display the children.
The <img> tag uses shadow children to display the image and the Alt-Text.
Like #supersharp has said the use of shadowDOM is based on the element. I would go further to say that shadowDOM should be a well thought out choice. I will add that you need to remember that these are supposed to be components and not apps.
Yes, you can encapsulate your entire app into one component, but the browsers didn't attempt to do that with Native components. The more specialized you can make your components to more reusable they become.
Avoid adding anything into your Web Components that is not vanilla JS, in other words, do not add any framework code into your components unless you never want to share them with someone that does not use that framework. The components I write are 100% Vanilla JS and no CSS frameworks. And they are used in Angular, React and vue with no changes to the code.
But chose the use of shadowDOM for each component written. And, if you must work in a browser that does not natively support Web Components that you may not want to use shadowDOM at all.
One last thing. If you write a component that does not use shadowDOM but it has CSS then you have to be careful where you place the CSS since your component might be placed into someone else's shadowDOM. If your CSS was placed in the <head> tag then it will fail inside the other shadowDOM. I use this code to prevent that problem:
function setCss(el, styleEl) {
let comp = (styleEl instanceof DocumentFragment ? styleEl.querySelector('style') : styleEl).getAttribute('component');
if (!comp) {
throw new Error('Your `<style>` tag must set the attribute `component` to the component name. (Like: `<style component="my-element">`)');
}
let doc = document.head; // If shadow DOM isn't supported place the CSS in `<head>`
// istanbul ignore else
if (el.getRootNode) {
doc = el.getRootNode();
// istanbul ignore else
if (doc === document) {
doc = document.head;
}
}
// istanbul ignore else
if (!doc.querySelector(`style[component="${comp}"]`)) {
doc.appendChild(styleEl.cloneNode(true));
}
}
export default setCss;
The choice is 100% dependent on the use case.
Also:
if you want the user to be able to format your custom element with global CSS style attributes, you may opt for the normal, light DOM.
you're right: in the Shadow DOM, "this may be less searchable": the document.querySelector() method won't inspect the Shadow DOM content.
as a consequence, some third-pary JS library may fail to integrate easily with Shadow DOM
if you intend to use a Custom Element polyfill for legacy browsers, you may avoid Shadow DOM because some of its features cannot be really polyfilled.
in many cases, the answer is to provide a mix of Light DOM and Shadow DOM. As suggested by #JaredSmith:
Shadow DOM for the Web Component author,
Light DOM for the Web Compoent user, intergrated in the Shadow DOM with <slot>.
As a conclusion, you should consider the context in which your Web Component will be used to decide whether Shadow DOM is required or not.
Answer to the Edit
Considering your use case, I would create a custom element and:
let the user populate the light DOM with atomic value(s): type element <div class="mail-to"> or custom sub-components <mail-to> as suggested by #Intervalia,
use a Shadow DOM to mask the light DOM,
use Javascript: this.querySelectorAll('.mail-to') or this.querySelectorAll('mail-to') instead of <slot> to extract data from the light DOM and copy (or move) them to the Shadow DOM.
This way users won't have to learn the <slot> working, and the developer will be able to format the web component rendering with more freedom.
<email-view>
<mail-to>guillaume#stackoverflow.com</mail-to>
<mail-to>joe#google.fr</mail-to>
<mail-from>supersharp#cyber-nation.fr</mail-from>
<mail-body>hello world!</mail-body>
<email-view>
Alright. Setting aside for a moment that I think this is a bad questionable idea, here's some code that should do what you want (I didn't run it, but it should work):
class FooElement extends HTMLElement {
constructor () {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(document.importNode(template.content, true));
}
_xformObject (object) {
// turn the obj into DOM nodes
}
renderFromObject (object) {
// you may need to do something fancier than appendChild,
// you can always query the shadowRoot and insert it at
// a specific point in shadow DOM
this.shadowRoot.appendChild(this._xformObject(object));
}
}
You'll have to register the custom element of course.
Now sometimes you really can't get away from doing something like this. But it should be the absolute last resort. See below:
Why I think this is a bad questionable idea, and how to make it better:
One of the main draws to web components is that it enables declarative HTML markup rather than procedural JS DOM manipulations. While providing an API like what you're talking about is certainly a big step up from e.g. creating a table by creating a table node, creating a row node, creating some tds, appending them to the row, then appending that to the table, I (and I think most) developers are of the idea that if your custom element requires direct JavaScript manipulation by the user, then it's not really an HTML element: it's a JavaScript interface.
Let me qualify that a little. When I say "requires" JavaScript I mean there's no way to drop it on the page with some appropriate attributes and wind up with the thing you want. When I say "direct" I mean by calling methods of the object representation of the element directly rather than say toggling an element attribute. To put my point in code:
// good
myCustomElement.setAttribute("toggled-on", true);
// this isn't *bad*, but don't *force* people to do this
myCustomElement.toggleState();
You may want to still provide the second as part of your public API as a convenience to your users, but requiring it seems beyond the pale. Now one issue is that you obviously can't easily pass complex data structures to an HTML attribute (Polymer has helpers for this if you're using Polymer).
But if that's the case, rather than have that be part of the element API, I'd provide a standalone function that returns the appropriate DOM structure rather than baking that in to an element. You could even make it a class method of your custom element class if that's how you roll.
Consider the case where you have a List element that renders an arbitrary number of Item elements. I think it's great to provide a convenience method that takes an array and updates the (light) DOM. But users should be able to append them directly as well.
Your use case may require hacking around the problem. Sometimes you really do legit need to use an antipattern. But consider carefully whether that's the case in your element.
This is purely dependent on your case. But as a general rule, if you find yourself buried in a hell of nested shadow roots, then you may consider going easy on using shadow doms.
Like the follow example illustrates:
<my-outer-element>
shadowRoot
<slot1> ---Reveal dom
<my-inner-element>
shadowRoot
....
Background
I am trying to create a blog using Angular (5). I am using markdown and storing that data outside the application. It downloads the markdown, parses it into an html string, then binds to the innerHTML of a div.
I understand that I am working against the grain, but I would really like to be able to create an elegant solution here.
Problem
Having the ability to use custom components gives us the ability to do a bunch of stuff with our blog that we won't be able to do otherwise. Signup components, custom widgets, etc. We can do all this and still have the ability to store the content separately outside of the application.
Custom components are not detected from the innerHTML string. Which doesn't allow it. It seems like DynamicComponentLoader used to provide a solution for this, but not anymore.
Clarity
I am not trying to render only the html, or only a single component. I want to render the html and all components included.
I also don't care that it's bound to the innerHTML property, it just seemed to get me the furthest. I can/will use a resolver if that would help.
Example
https://stackblitz.com/edit/angular-wylp55
As you can see the hello component renders in the html, but not the component itself.
Any help would be appreciated.
So I finally figured this out and did a write up.
Here's the link to the updated stack blitz.
https://stackblitz.com/edit/angular-dynamic-html.
I also did a full write up on my company blog. https://www.arka.com/blog/dynamically-generate-angular-components-from-external-html.
Angular provides us with a mechanism to write directives - which is extremely powerful in what it can do. But the thing I keep wondering is - in what scenario should you be actually writing a custom directive of your own.
We keep seeing questions in and around Stack Overflow with various people attempting to write directives which ( in my opinion ) need not be written in the first place. In most cases they can be solved with a combination of repeat, switch and show. See examples of questions containing directives that I think shouldnt be directives in the first place!
https://stackoverflow.com/questions/16101073/angularjs-directive-is-not-working-in-ie-10
Fire button click in AngularJS
angularjs: using a directive inside the ui-bootstrap modal
Some examples scenarios. I am not picking on them in anyway..because I am sure it is not clear to anyone when we should be using / writing a directive.
We see scenario's where people use directives as a mechanism for templating. Is this the right way of doing things? Or is there a better way? ( ng-include perhaps? ) Are there any upsides / downsides to using directives as a templating mechanism? The reason for this question is that sometimes I wonder if people write directives because coming from the jquery world the first thing they can think of is writing DOM manipulating code and since the Angular way is to not manipulate the DOM in controllers it all gravitates towards writing all that code in a directive.
EDIT :
I believe this confusion ( of shoving things inside a directive ) arises because Angular does not have a separate concept of a "view" - unlike Backbone ( which only has a "view" but no component! ). Directives are amazing at defining components - But I think if you use them to create "views", you will lose some of the "angular" way. This is my opinion though -which is why I am soliciting what the rest of the angular community thinks.
The good thing about simpler directives ( directives that do just 1 thing! ) is that they are absolutely easy to test. If you look at all the ng directives they all do 1 thing and do that thing pretty well.
What is the best way of defining reusable "views" ( not components! ) in Angular ? Should that be written in a directive? Or is there a better way?
It would be awesome if one of the Angular Dev's have an opinion in this matter!
Well... quite a good question.
I believe directives are mainly meant to "extending HTML so you can build a DSL", improving productivity and code quality.
The question is that this is achieved through componentizing things. But it is of most importance that we understand that directive is not about visual components only, neither templating only, but also, about behavior.
To summarize, using directives you could:
create a DSL to augment elements behavior
create DSL widgets, so you can stop repeating yourself
wrapping already existent components, buying you productivity.
optimization
Augmenting behavior is nothing more than componentizing behavior. ng-click, for example, adds the clickable behavior to any element. Imagine you're creating an app with dozens of draggable elements. Than you would create a directive to augment element behavior, making it draggable without even touching the element visual (<span draggable>Test</span>). Yet another example, imagine you gonna have special hints on mouse hover. title attribute is not suitable to this, then you can create your own my-title attribute, that automatically create your "special hint" on mouse hover (<span my-title="Some caption">Test</span>).
And when developing an app, you have a plenty of domain specific concepts and behaviors. Stackoverflow, for example, have a strong concept of voting. You can vote up/down questions, answers, comments... So you could create a reusable votable directive, that would add "vote behavior" and "vote widget" (up/down arrows) to praticaly any element.
This last one gives us another face: templating. Not only for lazy ones, but to improve code quality and maintainability following DRY principle. If you are repeating controllers code, HTML structure, or anything else, why not templating and componentizing it, right? Directive is your guy for this job.
Of course, you also have some generic application of directives. Many application (not to say all of them) rely on clickable elements, this is why we have a ng-click, for example. Many applications have upload areas. And what would you do in a jQuery way of thinking? You would create a jQuery plugin. Right? In Angular, you would create a Angular Widget (using directives). You could even wrap an already existing plugin using a directive, and, once more, augmenting its behavior so it can smoothly talk to your application.
Regarding wrapping plugins, to every jQuery plugin (but could be MooTools, Ext...), you gonna have to create a controller, call $('element').plugin() on it, and caring that jQuery events change and $digest your scope for you. This is another perfect use of directive. You can create a directive that apply .plugin() on your element, listening to the events and changing/digesting your scope for you. This is what Angular UI Project is all about, take a look.
One last point is the optimization. I recently created and app that creates tables with dynamic columns and rows (a grid). The question is that data is updated in real time! And the ng-repeat inside ng-repeat, to create headers, was killing application performance (nested loops in every $apply cycle, happening each half second). So you can create a directive that create the columns template and still use ng-repeat inside it, but you would have a loop through columns only upon columns edition.
So, wrapping up, I believe directives are about componentizing behavior and form (templating), which buy you producitivity and code quality.
I personally write directives quite a lot, as they tend to make my program much more declarative.
An example: in a JSON -> HTML form parser I made recently, I created a "form-element" directive, that parses the JSON element a creating the necessary directives as it's children. That way I have a directive for each field type, with specific behavior and methods. Also, any common behavior shared between all elements is in the form-element directive.
This way, a group element is a directive with a ng-repeat in it's template, and a title element is as simple as a h1. But all can have the same conditional behavior (a group can only appear if a previous field has a certain value set, for instance). And all extremely clean - any time i need to add/change, it all stays perfectly put, and the html is extremely declarative.
EDIT - included a snippet of code, as requested via comments.
/**
* Form Element
* ============
*
* Handles different elements:
* Assigns diferent directives according to the element type
* Instanstiates and maintains the active property on the formElem
*/
.directive("formElement", ['$compile', function($compile){
return{
restrict: "E",
scope:{formElemModel: '='},
link: function(scope, element, attrs){
var template = '';
var type = scope.formElem.type;
switch (type){
case "field":
template =
"<form-field-"+scope.formElemModel.fieldType+" ng-switch-when='true'>\
</form-field-"+scope.formElemModel.fieldType+">";
break;
default:
template = "<form-"+type+" ng-switch-when='true' ></form-"+type+">";
break;
}
element.html(template);
$compile(element.contents())(scope);
// Active state of form Element
scope.formElem.active = true;
scope.testActive = function(){
if(scope.$parent.formElem && scope.$parent.formElem.active == false){
scope.formElem.active = false;
}
else{
scope.formElem.active =
scope.meetsRequirements(scope.formElem.requirements);
}
}
scope.$watch("meetsRequirements(formElem.requirements)", scope.testActive);
scope.$watch("$parent.formElem.active", scope.testActive);
}
}
}])