I'd like to be able to set custom attributes on the host element for Angular 1.5 components.
Why?
I want to add a class to a component so I can style it. For example, if a component's parent has display: flex set, I'll likely want to set the flex property on the component.
It's often useful to conditionally apply a class depending on a component's state.
In certain circumstances, I'd like to use ARIA attributes to make a component more accessible.
Here's a simplified example of what I'd like to do (it obviously doesn't work, but I'm looking for something similar):
angular.module("app").component('hello', {
attributes() {
return {
class: "hello " + (this.greeting ? "hello--visibile" : ""),
data-greeting: this.greeting
}
},
bindings: {
greeting: "<",
}
})
It looks like Angular 2.0 supports this feature, but I don't see anything in the docs about supporting it in 1.5. Is there a way to accomplish this for those of us still stuck using .component?
In the past, I would have simply used replace: true to solve this problem, but that's been deprecated and isn't even available for .component.
As you mention, it is not directly possible as you describe. An attributes property would be usefull, but does not exist as of yet.
A possible work-around would be to use $element inside your controller. This passes a jQuery element as a dependency, so you can add attributes as needed.
angular
.module('myComponent', [])
.component('myComponent', {
bindings: {
greeting: '<'
},
controller: function($element) {
$element.addClass('hello-world');
$element.attr('aria-hidden', true);
$element.data('my-greeting', this.greeting);
},
template: 'Define your template here.'
});
The <myComponent> element would now have a hello-world class and a aria-hidden attribute. It is even possible to use bindings, as described above with greeting.
The angular component definition is just a wrapper around normal directives.
Related
I need a solution to get all inputs in my vue app and then do something with them i.e styling.
I am thinking about using directives for this.
I always use this way when I work on angular project, where I can create a directive, which has a selector i.e
selector : input[type="text"].
export class MyCustomDirective {
constructor(el) {}
.............
}
and then when I start my app I get all inputs of the app without adding the created directive to them one by one.
Unfortunately Vue directives are not able to do this, because I must prefix the created directive with v-
so this wont work
Vue.directive('input', {
bind: function (el) {
// some magic
},
});
any help or new ideas ?
I am trying to create a grid component in AngularJS that has it's grid items provided at runtime. (think render props pattern in React).
I am trying to build this using the "new" AngularJS components API along with transclusion.
<grid-items>
<grid-item-type-1></grid-item-type-1>
</grid-items>
<grid-items>
<grid-item-type-2></grid-item-type-2>
</grid-items>
Any of these should be valid. <grid-items> should take care of the data and the <grod-item-type-x> should take care of how each individual item is going to be dislpayed.
After researching I have found out two ways to do this. One of them is to require the parent controller in the child component and use it's data, or by using the link function along with compile (this doesn't work using the components API), something like this:
link: function(scope, element) {
scope.items.forEach((item) => {
const tpl = "<" + item.type + " item=" + item + " ></grid-item>"
const child = $compile(tpl)(item)
element.append(child);
});
}
The above approach doesn't use transclusion but you can still use whatever child component is provided to the parent at runtime.
Well, if I got it correctly there are some ways to do this. When defining your component, like this:
{
restrict: 'E',
bindings: {
myProp: '#',
},
transclude: {
'myTransclude': 'myTransclude',
},
template,
controller,
controllerAs: 'ctrl',
}
whatever you pass to bindings can be accessed from your component. So in the example above if you did something like this: <my-component my-prop="hey" /> you can as well access ctrl.myProp an get your hey value.
In more complex ways you can use the transclusion item, that insides your myComponent template you'll have something like this:
<div ng-transclude="myTransclude"></div>
and everytime you'll use it'll be like this:
<my-component>
<my-transclude>
<!-- whatever you wan to go inside your component -->
</my-transclude>
</my-component>
And, of course, you can have a HTML compile directive (I recommend searching for existing ones) where you could have your template and just use it
<div html-compile-directive="ctrl.myTemplate"></div>
I'm creating a master checkbox mixin, which manages the state of a masterCheckbox, and followerCheckboxes. Instead of having to set directives like v-model several times on different checkbox elements, it would be alot nicer to set one directive which takes care of that.
The example below is not working, and instead I have to set these individually on the component that uses the mixin. Am I doing something wrong, or is this just not possible?
// define a mixin object
module.exports = {
directives: {
masterCheckbox: {
// directive definition
bind: function (el, binding, vnode) {
el.setAttribute("v-on:click", "toggleMasterCheckbox");
el.setAttribute("v-model", "masterCheckboxChecked");
el.setAttribute(":indeterminate.prop", "masterCheckboxIndeterminate");
}
},
followerCheckbox: {
// directive definition
bind: function (el, binding, vnode) {
el.setAttribute("v-model", "checkedCheckboxes");
}
}
},
...
}
Directives are not attributes, they are non-HTML markup in the template that Vue processes away before creating HTML. You're setting attributes on DOM elements well after Vue has created them from template. So what you are trying to do cannot be done.
You should probably set up a component – possibly a dynamic component – in whose template you can include all the appropriate directives, and then use multiple places. But I think your click and v-model directives are going to fight, as v-model is going to change the value of masterCheckboxChecked, and so is toggleMasterCheckbox.
If I have a template in a component that references non-existant components, angular 1.6 seems perfectly happy to render it as nothing at all. For example I have a route currently that looks like:
when('/something',{
title: 'Something',
template: "<mycomponent></mycomponent>",
})
If I forget to register mycomponent on my application, this route renders nothing. Is there some mode I can use in angular that will cause a harder error in a case like that? Or at least print something to the console when it occurs?
To be perfectly clear, I have this issue with both top level components referenced by the router, as well as other child components that those reference recursively.
No, there is no option for that. By the way "non rendered" components are a benefit IMO, because you could override this slot later.
A short example:
when('/something',{
title: 'Something',
template: "<slot><mycomponent></mycomponent></slot>",
})
assume you want to override the ui-part of mycomponent, just define a component for "slot"
There was a routeProvider.otherwise before. Not sure if it’s still supported. I’m on a phone so limited. Let me know how it goes.
UI Router supports components for routes, this means that it is capable of triggering an error if a component doesn't exist.
AngularJS currently doesn't offer component router. This means that route template is compiled as any other template with $compile, and unknown selectors are just ignored.
It is possible to patch ngRoute ngView directive to additionally check if a route that is expected to route to a component actually has it compiled.
// the whole list can be retrieved from commonly available sources
var htmlTags = ['p', 'div', ...];
app.directive('ngView', function () {
return function($scope, $element, $attrs) {
if (!DEBUG_MODE)
return;
if ($element.children().length !== 1)
return;
var routeComponent = angular.element($element.children()[0]);
var routeComponentTagName = routeComponent.prop('tagName').toLowerCase();
if (htmlTags.indexOf(routeComponentTagName) >= 0)
return;
var routeComponentName = $attrs.$normalize(routeComponentTagName);
if (!routeComponent.controller(routeComponentName)) {
console.warn(routeComponentTagName + ' element is non-standard yet not a component');
}
}
});
AngularJS already has $normalize() which is primarily intended for attributes and strips some specific prefixes, but as long as the components names don't have x or data prefix, it can work as generic kebab to camel case transformer.
There may be other ways to detect if a component was compiled on child element. The code above will trigger false negative if there already is parent routeComponentName component, because controller() travels up the hierarchy.
And the proper way to handle this problem is to provide test coverage for the application. Router-related cases should likely be handled in integration or e2e tests.
I have started working on Knockout recently, and have been assigned to create custom components that can be used in various application. While creating a component, I used camel case to name it, e.g: "datePicker".
Component code
ko.components.register("datePicker",{
viewModel: require('./components/date-picker-widget'),
template: require('raw!./components/date-picker-widget.html')
});
HTML Code
<datePicker params="{value:returnValue, initialValue:returnValue.initialValue}"></datePicker>
But this is what was rendered:
So its clear that Knockout expects component names in lower case (JSFiddle reference). The question remains is why?
I also saw similar constraints on naming of components in React, where you have to start the name with capital character only.
The key is here in the documentation:
By default, Knockout assumes that your custom element tag names correspond exactly to the names of components registered using ko.components.register. This convention-over-configuration strategy is ideal for most applications.
If you want to have different custom element tag names, you can override getComponentNameForNode to control this. For example,
ko.components.getComponentNameForNode = function(node) {
var tagNameLower = node.tagName && node.tagName.toLowerCase();
if (ko.components.isRegistered(tagNameLower)) {
// If the element's name exactly matches a preregistered
// component, use that component
return tagNameLower;
} else if (tagNameLower === "special-element") {
// For the element <special-element>, use the component
// "MySpecialComponent" (whether or not it was preregistered)
return "MySpecialComponent";
} else {
// Treat anything else as not representing a component
return null;
}
}
You can use this technique if, for example, you want to control which subset of registered components may be used as custom elements.
In fact, the documentation is not entirely up to par with the code, because it claims "correspond exactly", whereas in fact in the code only the tag name is lowercased, and the registration isn't.
In any case, because browsers return tagName in upper case, the above code will only work if your registration is in lowercase.
So, you can either use datePicker camelCased in your view and register it lower case:
<datePicker></datePicker>
ko.components.register("datepicker",{
viewModel: function() { },
template: '<strong>DatePicker stub is showing!</strong>'
});
$(function() { ko.applyBindings({}); });
Or you can monkey patch the register function:
<datePicker></datePicker>
var originalFn = ko.components.register;
ko.components.register = function(name, options) {
originalFn(name.toLowerCase(), options);
};
ko.components.register("datePicker",{
viewModel: function() { },
template: '<strong>DatePicker stub is showing!</strong>'
});
$(function() { ko.applyBindings({}); });
Though I'm not sure how stable and cross-browser compatible that would be.
In any case that shouldn't matter much, since you merely asked "why" this was like it was.
My suggestion: stick to the docs' style of dash-cased-element-names.
PS. The screenshot you posted is how the debugger rendered your elements, not how Knockout or the browser would have it. In fact, most likely if you use "view source" you'll get a camelCased element name; to be entirely sure you could inspect the raw body of the request.
Its not just the case with knockoutjs or react components, if you are writing xHTML then according to the specification tag names and attributes must be in lower case. (Ref: http://www.w3.org/TR/xhtml1/#h-4.2).
In most browsers, although the rendered html will have case insensitive tags (View Source), but the DOM built by browser(debugger tools, inspect element) will usually have all lowercase for tag names and attributes.
Although there is no wrong with case insensitive tags and attribute names, the web developers have, IMO, just adopted the convention of using lowercase names for XHTML or HTML.