How can I embed angular directive style within the js code? - javascript

I have a project written in angular 1.5 Until I upgrade to angular 2, I would like to reach a state where I write something like
angular.module(..).directive('name', () => {
return {
restrict: 'A',
template: `
<div> ... some template... </div>
`,
style: `// this is what I am missing
div{
// some css
}
`,
controller: function($scope){ ... }
}
})
The only piece I am missing is the style part. How can I get that? Is it supported in angular 1.x? Is there a tool that can help me get this functionality?
To make it clear - I am not asking about scoped css.
I don't care that these rules will be global or not.
I just want to be able to write the css next to the template and the controller.
So if you know about a tool like babel for example that can extract the style part to a css/scss file, that would qualify.
It can even be a general solution that I can use for this scenario.

Taken from Reddit
To my knowledge, there is no way to do that "automatically". Personally when i create a component i just give a specific ID / class to the top element, and restrict the component's CSS to that element.
Example: my-component.html
<div class="my-component-wrapper">
....
</div>
my-component.scss
.my-component-wrapper {
// style that applies only to my component
}
A style property on an angular 1.5 component is not supported or it'd have been mentioned in the docs

Related

Angular 1.5 to use transclude or not to use transclude

A question regarding transclude within an angular 1.5.8 component, and it's uses.
Here is an example of some code;
var app = angular.module('app', [])
function AccordionController () {
var self = this;
// add panel
self.addPanel = function(panel) {
// code to add panel
}
self.selectPanel = function() {
//code to select panel
}
}
// register the accordion component
app.component('accordion', {
template: '<!---accordion-template-->',
controller: AccordionController
}
function AccordionPanelController () {
// use parents methods here
var self = this;
// add panel
self.parent.addPanel(self);
// select panel
self.parent.selectPanel(self);
}
// register the accordion-panel component
app.component('accordionPanel', {
// require the parent component
// In this case parent is an instance of accordion component
require: {
'parent': '^accordion',
template: '<!---accrodion-panel-template-->',
controller: AccordionController
}
My question is would it be better to nest all the according panels within the parent using transclude or alternatively pass in a data array to the parent which this loops out the required number of panels based on the array passed inside using a binding.
Thanks
// added
Many thanks for your reply, an example I have of transclude possibly being necessary is in the following bit of code
<modal modal-id="editCompany" title="Edit Company"> <company-form company="$ctrl.company"></company-form> </modal>
Here we have a modal component which may have a variety of other components used within it, on the example above I am adding the company form, but this could we be an contact form. is there an alternative way?
I've worked with angular pretty extensively. Two enterprise tools managing and displaying large amounts of data, dozens of interactive widget modules, all that.
Never, once, have I had anything to do with transclude. At work we are explicitly told not to use it (link functions too). I thought this was a good thing, and the way Angular 2 turned out it seemed that thinking wasn't totally without reason.
I would go with the iteration to lay out the required number of items. At work this wouldn't be a choice because transclude wouldn't be an option.
The thing with using transclude in a component architecture is that it visually breaks the idea of single responsibility and messes with the architecture.
<html>
<navigation></navigation>
<accordion>
<accordion-panel></accordion-panel>
</accordion>
<footer></footer>
</html>
In this example you know your page has a navigation menu, an accordion and a footer. But at the index level (or root component) you don't want to know / see what the accordion contains.
So the accordion-panel component should only appear in its direct parent component.
As for your other question, through the use of require or attributes you pass an array of panels that you iterate using ng-repeat inside the accordion component.
app.component('accordion', {
template: `<accordion-panel ng-repeat="..."></accordion-panel>`,
controller: AccordionController
}

replace component with its content - angular 2 [duplicate]

This question already has answers here:
Remove the host HTML element selectors created by angular component
(5 answers)
Closed 7 years ago.
I am looking for a way to have a component that is rendered only with its content. For example, Given the component:
#Component({selector: 'my-cmp', template: '<div> my-cmp </div>'})
class MyComponent {
}
rendering it with angular2 will add the following to the DOM:
<my-cmp>
<div> my-cmp </div>
</my-cmp>
While I want to find a way to render it directly as:
<div> my-cmp </div>
Sadly since angular 2 is relatively new (just went beta) - Google is not so helpful, not to mention how lacking the current documentation is..
Sort of the same can be achieved by using an attribute selector
camelCasing is mandatory for attributes since alpha.52
#Component({
selector: '[myCmp]', //attribute selector
template: 'my-cmp'
})
class MyComponent {
}
And use this to call it:
<div myCmp></div>
You can do somethink like the following:
#Component({selector: 'div.my-cmp', template: 'my-cmp'})
class MyComponent {
}
and in your HTML:
<div class="my-cmp" />
According to documentation of angular 1.x
Replace the directive's element itself (if replace is true -
DEPRECATED).
So i think this feature won't be available it angular 2
I remember asking a similar question for Angularjs a few years ago. The concensus seemed to be then, as it is might be now is you probably don't want to do that.
It breaks semantics: a <video/> or a <input/> does not decompose into its contents. Your Angular components should not either.
You can probably still achieve what you want by thinking more deeply about your markup/css? Perhaps if you flesh out your use case we might be able to give you an alternative?

How does ng-cloak work?

I've been looking at the ng-cloak source code
https://github.com/angular/angular.js/blob/master/src/ng/directive/ngCloak.js
It looks like it strips away the ng-cloak attribute during the compile phase of the directive. But when when I try
console.log(element.html())
during the compile function of a directive, the expressions have still not been evaluated, so I get an output like
<my-directive ng-cloak> {{foo}} </my-directive>
Given that ng-cloak will remove the ng-cloak attribute and the corresponding display:none, wouldn't it show {{foo}}? I'm confused here. Whend do Angular expressions get evaluated? It doesn't look like it gets evaluated in the compile function. When is the DOM updated?
The ngCloak directive is used to prevent the Angular html template from being briefly displayed by the browser in its raw (uncompiled) form while your application is loading. Use this directive to avoid the undesirable flicker effect caused by the html template display.
The directive can be applied to the element, but the preferred usage is to apply multiple ngCloak directives to small portions of the page to permit progressive rendering of the browser view.
ngCloak works in cooperation with the following css rule embedded within angular.js and angular.min.js. For CSP mode please add angular-csp.css to your html file (see ngCsp).
https://docs.angularjs.org/api/ng/directive/ngCloak
Example index.html
<div id="template1" ng-cloak>{{ 'hello' }}</div>
<div id="template2" class="ng-cloak">{{ 'world' }}</div>
things.js
it('should remove the template directive and css class', function() {
expect($('#template1').getAttribute('ng-cloak')).
toBeNull();
expect($('#template2').getAttribute('ng-cloak')).
toBeNull();});
Or you can use in other way
it might not be enough to add the display: none; rule to your CSS. In cases where you are loading angular.js in the body or templates aren't compiled soon enough, use the ng-cloak directive and include the following in your CSS:
[ng\:cloak], [ng-cloak], .ng-cloak {
display: none !important;}
Angularjs - ng-cloak/ng-show elements blink
Angular will interpolate the bindings at the end of the $digest cycle so that all of the other modifications have already completed. If they were processed earlier, a directive might later change the binding or scope and cause the DOM to be out-of-date.
You can decorate the $interpolate service so that you can log when Angular interpolates a binding:
.config(function($provide){
$provide.decorator("$interpolate", function($delegate){
function wrap() {
var x = $delegate.apply(this, arguments);
if (x) {
var binding = arguments[0];
return function() {
var result = x.apply(this, arguments);
console.log('binding:', binding, result);
return result;
}
}
}
angular.extend(wrap, $delegate);
return wrap;
});
})
You can then see that all of the directives have been processed and finally, the $interpolate service is called and the DOM is modified accordingly.
Click here for live demo.
ng-cloak also fixes the two times click bug for ng-click reverse. Once listed this instruction will fix the issue in witch the button with ng-click reverse instruction needs two clicks in order to execute.

Compile Angular directive from dynamic html

I can't seem to get this done: I have HTML that is compiled from a ng-repeat, and I'd like to compile the result of that as well. How would I go about that?
I have a dataset containing chunks of text, that have been given a display type. This type is set as the span class. Most types are just triggering CSS rules (for example, comment-style boxes, see screenshot), but others should invoke a directive.
For example, the chunk containing 'named Nicodemus, ' is of type-hidden. I have a directive that collapses the chunk and inserts a little button to expand it.
Code:
<span class="chunk type-{{chunk.type}}" ng-repeat="chunk in verse.chunks">{{chunk.text}}</span>
Results in something like
<span class="chunk type-hidden">named Nicodemus, </span>
If the second would be my source html, it would compile the typeHidden directive just fine. I guess I need to find a way to make angular compile a second time. I can't seem to get it done using $compile (though I guess I don't really understand how that works).
Hope you can help!
Thanks in advance!
Here's a plunker to show how you can get a directive to compile an element and then again.
The code for the lazy:
angular
.module('App')
.directive('compileTwice', compileTwiceFactory);
function compileTwiceFactory($compile) {
return {
restrict: 'AE', // Whatever you want
terminal: true, // Angular should not keep compiling the element
// by itself after encountering this directive!
compile: compile, // Instead, we tell Angular how to compile the rest of the element
priority: 1001, // This directive should get compiled before the others, obviously
};
function compile(element, attrs) {
element.removeAttr('compile-twice');
element.removeAttr('data-compile-twice');
return function postLink(scope, _element, _attrs) {
var compiledTwice = $compile($compile(_element)(scope)[0])(scope)[0];
// do something with compiledTwice
};
}
}
edit: And obviously you can generalize that to compile an arbitrary number of times that you could specify like this:
<div compile-n-times="420"></div>
edit: The plunker doesn't seem to work under Firefox?
I've actually been able to fix this with a workaround. Not as elegant, but it works if I nest my directive within the ngrepeat and hardcode the name, making it visible using ng-if.
<!-- special type hidden -->
<span ng-if="chunk.type=='hidden'">
<span class="type-hidden">
{{chunk.text}}
</span>
</span>

Angular JS - Evaluation Timing

I have the following problem in angularjs. I want to use an UI libary that injects some html code itself (Metro UI CSS) and I have troubles to getting the execution order right.
A simple example: http://metroui.org.ua/hint.html
If I declare in html:
<span data-hint="My hint"></span>
The UIjs will create the html elements needed for the hint display. Nofurther script code has to be added. Well actually when you load the js the following code gets executed: $('[data-hint]').hint();
Since the angular created html doesn't exist when I load the javascript, it doesn't work at first at all.
I believe I need an angular directive to solve the problem (and in parts it does) - I created the fowlling directive:
app.directive('hints', function() {
return {
link: function() {$('[data-hint]').hint()}
};
});
The following does work, even if this is in html created by angular:
<span hints data-hint="the text for my hint">test</span>
The following doesn't work (at least it doesn't behave the way I'd like to):
<span hints data-hint="{{something}}">Test</span>
The hint text will display literally {{something}} and not whatever is behind the angular expression. I tried already to create template like, but the result is still the same:
app.directive('hints', function() {
return {
template: '<span data-hint="{{something}}">Test</span>',
link: function() {$('[data-hint]').hint()}
};
});
Any hints on how to solve that problem would be greatly appreciated.
The main problem seems to be that if you attach the hint() in the link function, jquery takes the old value before angular has evaluated it. One option would be to wrap $timeout(function(){..}) around element.hint(), but I use that hack too much already, and it doesn't solve another problem: the hint needs to update when the $scope changes (if it depends on the $scope). To solve that problem we can add a $watch function and update the hint value when needed.
So, in conclusion:
/* This directive triggers automatically on data-hint as well */
app.directive('hint', function($timeout) {
return {
link: function(scope, element, arguments) {
/* set the value of the hint, then attach the metro-hint widget */
element.data('hint' , arguments.hint).hint();
/* watch for changes in the value so the hint gets updated */
scope.$watch(function(){
return arguments.hint;
}, function() {
element.data('hint' , arguments.hint);
});
}
};
});
(Tested with jquery 1.10.2, jquery-ui 1.10.3 and angular 1.2.6)

Categories

Resources