If I have a simple button with a click handler and a custom attribute directive like so:
<button (click)="save()" attributedirective="project saved">Save</button>
And in my attribute directive I'm using the hostlistener decorator to listen to the click event:
#Directive({
selector: `[attributedirective]`
})
export class AuditPusher {
#Input('attributedirective') attributedirective: string = 'Missing message!';
#HostListener('click', ['$event'])
pushAudit() {
console.log('text:'+this.attributedirective.toString());
}
}
Which of my code will fire first? The save() on the click event or the code in my attribute directive? - And: Imagine having two attribute directives. Which of those will fire first? In Angular 1 there was something like directive priorities, how is this done in Angular 2? I find it difficult to find documentation on this.
As far as I know the order of execution is undefined. You shouldn't depend on a specific order.
I think priority concept is yet not there in Angular2. (If it is already i'm not aware of it yet) but One should not depend on a specific order as already said.
But as you have asked specifically. Order would be,
1)when page or a component is loaded, <button (click)="save()" attributedirective="project saved">Save</button> is loaded too and because of directive**(attributedirective) is attached to button, Angular2 initializes directive(attributedirective) and binds it to button.
2) As Save() is a function attached to native click (Angular2's way) event of button if you click it, it will call save() first and then it will look for other binding's events(if any) attached to it (eg.pushAudit)
A quick and dirty way around this when I had two attribute directives and Angular was executing DirectiveB before DirectiveA but I needed to have it the other way around was to delay the stuff I needed to execute last:
export class DirectiveA {
ngOnInit() {
doStuff();
}
}
export class DirectiveB {
ngOnInit() {
setTimeout(() => {
doMoreStuff();
}, 0);
}
}
When you do setTimeout(0) it will defer execution to the next Angular processing cycle, which is just what I needed in my case for everything in DirectiveA to be set up in time in order to handle events coming from DirectiveB.
I might be wrong but for me it worked like this:
I have an svg:g element and added 2 directives like this:
In order to execute directiveB first in the module at the exports section I added directiveB first and after directiveA.
Note that for svg the z-index is defined by the order the element appears in the document. How to use z-index in svg elements?.
I know you have a click there and a custom attribute but if you have 2 custom attributes this can work!
Related
Knockout version: 3.5.1
I have a custom element on my main .html file like so:
<my-component></my-component>
Inside I have a form to which I am attaching jQuery validation which is not working because when the line $("#myForm").validate is called, the form is not yet in the DOM.
I created a very basic jsFiddle to illustrate the issue: https://jsfiddle.net/sb1of04g/
Notice how if you remove the timeout, the result is 0. I get the same result if I move the alert below ko.applyBindings(new Test());
I want my jQuery validation code to run after the component is 100% loaded.
In my project I'm using custom elements and requirejs/text so to fix the issue I tried the below approach:
<my-component data-bind="event: { descendantsComplete: $root.OnComponentReady() }"></my-component>
self.OnComponentReady = function()
{
$("#myForm").validate ...
}
But no luck, OnComponentReady is executed correctly but the form is still not in the DOM.
This is when things got funny. Inside my-component I have another component, footer-component which is at the very bottom of the parent component and if I change my code to the below, I get the correct behaviour.
<my-component></my-component>
<footer-component data-bind="event: { descendantsComplete: $root.OnComponentReady() }"></footer-component>
Like this, when OnComponentReady is executed the form is available and I can attach jQuery validation to it. It also works if I move footer-component at the top of my-component.
I was under the impression that descendantsComplete fires after the component finished loading, similarly to jQuery's $(document).ready. Is this not the case? I'm not entirely sure I will have a child component in the future so ideally descendantsComplete is attached to the parent component.
Edit: I just realised that if I move the .validate code from my main ViewModel($root) to the Component ViewModel, I get the correct behaviour without the descendantsComplete event.
Hi I am using an UI Library (forced to, company issue..) which provides an Angular component, which renders a form.
Now I want to disable all of the input fields an buttons inside this form. But the component of the library doesn't provide me the possibility to pass a parameter to change the status to read only.
Now I have no other option to do dirty DOM hacking. However it doesn't seem to work.
Here is my HTML of my own component, where I render the Library Component:
<component-of-the-library #formComponent></component-of-the-library>
Now inside my own components class I reference it:
#ViewChild('formComponent', {read: ElementRef}) formComponent: ElementRef;
However when I use the nativeElement feature and the querySelectorAll() function I don't see the button elements:
ngAfterViewInit() {
console.log(this.formComponent.nativeElement);
console.log(this.formComponent.nativeElement.querySelectorAll('button'))
}
The first line outputs the DOM of the library component. There I also see the buttons.
However the second line just returns an empty NodeList.
Am I missing something?
Instead of doing all these, come up with a div overlay and with the size of your form and make show or hide it based on your needs.
It will be easier than disabling each form inputs and buttons. Also the overlay is not the component but your div.
I able to read the DOM Nodes present in the child component from the parent Component using ViewChild() https://stackblitz.com/edit/angular-edmyur?file=src%2Fapp%2Fapp.component.ts
EDIT: I see another problem. AfterViewChecked gets called multiple times...
I found the answer myself. The problem is the LifeCycleHook. AfterViewInit works for your own component, but it doesn't wait for the child components to finish rendering.
When I use AfterViewChecked it works!
However I am still puzzled, that logging the nativeElement has always given me the correct DOM, even though it's still not rendered.
When having an angular directive that inside its template has a button with ng-click attribute, should I test the effect of actual action taken (clicking) or of, an attached to ng-click attribute, function call?
In other words, for a directive, that has a template like this:
<div>
<button ng-click="game.start()">start</button>
</div>
should I test what clicking does or what game.start function does?
I am aware that later the function game.start can be changed for a different one. Also, ng-click can be removed and click event can be caught in a different way.
Unit testing with triggerHandler('click') is tempting but directive's template can also change. That would mean that I need some "proven" access to DOM nodes, namely through id attributes.
Should I attach id attributes on nodes just for unit testing sake or should I take a different aproach to the problem?
In the case of a directive I usually go through the dom to click the element. in your case here:
it("performs some action when clicked", function() {
element.find("button").click();
assert(something);
});
Calling the handler directly will get you a passing test even if you forget to add the ng-click directive, which is not what I usually want. It also gives you the freedom to rename the handler method without having to adjust the test, thus making it a little less brittle.
If the template is getting very complex and you are tempted to add ids just for testings sake, you might want to consider breaking the directive into multiple ones.
I'm creating a custom Angular directive for a slide in menu which needs to watch a couple of attributes and one of those attributes needs to be two way bound to the main controller scope (sometimes). However, sometimes the attribute will not be added by the developer so it needs to be added automatically and set to the default (false). So, the directive can be used like this.
<slide-menu position="right" is-open="menuIsOpen"></slide-menu>
or like this:
<slide-menu></slide-menu>
When used the first way the main controller will be able to open and close the menu by changing the value of the boolean $scope.menuIsOpen.
When used without supplying the is-open attribute it should default to false and is obviously used internally and by a child toggle directive.
An additional complication is that whether the attribute is supplied by the developer or not it should exist in the DOM. so in the second example above the directive would set itself to false by default and add the attribute is-open="false" to the DOM?
The reason for requiring is-open="false/true" in the DOM at all times is that the menu is actually operated using CSS tansitions which use the following selector:
slide-menu[is-active="true"]{
// Slide the menu in using transforms/transitions
}
There is a jsfiddle here which shows how far I have got.
http://jsfiddle.net/jonhobbs/gEPvE/
Obviously it doesn't work, but it shows how I have tried to set a default and how I have tried to use # and & on the isolated scope for a one time binding (the menu position) and a 2 way bound expression for the is-open variable.
I'm clearly a long way from achieving what I need but any advice would really be appreciated.
Have a look at this fiddle http://jsfiddle.net/gEPvE/38/
I took the one you started and updated it to act like you specified.
You can make a two way binding value optional by adding a ? on the scope definition.
Like this
{
scope: {
'isOpen':'=?'
}
}
Now the is-open attribute is optional.
Then you can set the default value in the directive controller, like you had started to do.
Next, in order to synchronize the DOM attribute with the scope value you can use $watch.
$scope.$watch('isOpen', function(val) {
$element.attr('is-open', val);
});
Finally, I changed the second 'slideMenuToggle' directive to wrap/transclude its element in order to add an ng-click handler. This is mainly to avoid any nastiness with calling $scope.$apply yourself.
Let me know if that works for you.
EDIT
Answering your question in the comment, you can pass a value directly without having it be bound to the scope, you just need to wrap the value in quotes.
For example
<div ng-controller='ctrl'>
<hello world='imOnScope'></hello>
</div>
Assuming 'hello' is a directive with a scope of 'world': '=?' then angular will assign a reference to the parent scope's 'imOnScope' object to the directive's $scope.world member, allowing a two way binding scenario.
To just provide a value directly you may do something like this
<div ng-controller="ctrl">
<hello world="'directValue'"></hello>
</div>
In this scenario angular will just assign 'directValue' to the directive's $scope.world member.
You need to add ngTouch to your module.
var app = angular.module('app', ['ngTouch']);
And add this script:
http://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular-touch.js
The reason for requiring is-open="false/true" in the DOM at all times
is that the menu is actually operated using CSS tansitions which use
the following selector
Forcing directive attributes to be appropriate for css selectors is terrible idea. As you correctly stated, they are for developers. So add a class to the element dynamically.
It seems that you're misusing &, it would be ok to set up a callback, but since you don't do this, in its current state you can end up with one-way # with confidence.
I guess it can be something like this (just added ngTouch and ng-controller for parent scope).
You could replace
$scope.watch('isOpen', function () {
$element.toggleClass('opened', $scope.isOpen);
});
with
$scope.watch('isOpen', function () {
$attrs.isOpen = !!$scope.isOpen;
});
and get the behaviour you're asking for, easy as that. Ok, it is boolean now, and it reflects the scope, and you can use [is-open=true] selector. But guess what will happen with your binding? Broken. Fortunately, you can do
$scope.watch('isOpen', function () {
$element.attr('is-open', !!$scope.isOpen);
});
instead. Voila, we tricked Angular because it doesn't look after jqlite. But what will will happen with the binding when the directive will be re-compiled for any reason? Again, isOpen's binding is non-existing 'true' or 'false' scope variable. Broken.
I'm trying to find out how I can stop a DOM element from binding data from the scope in angular.
I know that you could do this with if statements and all, but is there a genuine & permanent way to stop binding a element in angular but keep the content that was added?
So say i have this
<div ng-bind="content" class="ng-binding">Welcome</div>
And i change the model so that the div changes to this.
<div ng-bind="content" class="ng-binding">Welcome World</div>
Then I click the button that will unbind it, so if I change the model to 'Welcome Universe', I wan't the <div> to be the same as before. This
<div ng-bind="content" class="ng-binding">Welcome World</div>
I know there are many other ways to do this, but i don't know any way to genuinely unbind the element, without cloning it and replacing the old one looping through the attributes and text..ect
Demo thing: http://jsfiddle.net/a9tZY/
So, by doing this, it shouldn't affect the model or other elements that are binding to that model.
Long story short, Tell Angular to leave the element alone forever.
UPDATE
The way to do this is to create a new scope on the element with a directive like so.
yourModule.directive('unbindable', function(){
return { scope: true };
});
And apply it to your element like so
<div unbindable id="yourId"></div>
Then to unbind this element from any updates you do this.
angular.element( document.getElementById('yourId') ).scope().$destroy();
Done, here's a demo.
Demo: http://jsfiddle.net/KQD6H/
So this creates a new scope on the element and only works because all scopes inherit all data from their parent scopes. so the scope is basically the same as the parent scope, but allows you to destroy the scope without affecting the parent scope. Because this element was given it's own scope, when you destroy it it doesn't get the parent scope back like all of the other elements, if that makes sense 0.o
Everything below this line was my original answer,I'll leave it here incase someone prefers this way
I have managed to achieve this genuinely with a unbindable directive.
When you have the unbinable directive set up on the element all that is required to unbind the element is this.
yourElement.attr('unbind', 'true'); // Ref 1
$scope.$broadcast('unbind'); // Ref 2
Here is the directive.
app.directive('unbindable', function(){
return {
scope: true, // This is what lets us do the magic.
controller: function( $scope, $element){
$scope.$on('unbind', function(){ // Ref 3
if($element.attr('unbind') === 'true'){ // Ref 4
window.setTimeout(function(){ $scope.$destroy() }, 0);//Ref 5
}
});
}
}
});
and you set your element up like this.
<h1 unbindable></h1>
So whenever you add the unbind="true" attribute to the h1 and broadcast unbind the element will be unbind-ed
REF-1: Add the unbind true attribute to the element so that the directive knows what element you are unbinding.
REF-2: Broadcast the unbind event across the scopes so that the directive knows that you want to unbind a element - Make sure you add the attribute first. --- Depending on your app layout, you might need to use $rootScope.$broadcast
REF-3: When the unbind event is broadcasted
REF-4: If the element associated with the directive has a true unbind attribute
REF-5: Then destroy the scope made by the directive. We have to use setTimeout because I think angular tries to do something after the $on event and we get a error, so using setTimeout will prevent that error. Although it fires instantly.
This works on multiple elements, here is a nice demo.
Demo: http://jsfiddle.net/wzAXu/2/
This one got me curious, so I did some poking around. At first I tried the "unbind()" method suggested in the other answer, but that only worked with removing event handlers from the element when what you're actually trying to do is remove the angular scope from the element. There may be some neater hidden function in Angular to do this, but this works just fine too:
angular.element(document.getElementById('txtElem')).scope().$destroy();
This retains the model (and updates anything else still bound to it), but removes the binding from the element. Also, in your example above, there is no binding to remove because you aren't binding to any element, just displaying the model expression inline. My example shows this in action: http://jsfiddle.net/3jQMx/1/
You can call the unbind method that stops listening to the element where the ng-model attribute is present. See fiddle: http://jsfiddle.net/jexgF/
angular.element(document.getElementById('txtElem')).unbind()
unbind removes all event listeners, so whenever any changes are made, it wont listen for those and hence not go through the angular loop. I have also assumed that you are not using jQuery, but if you are, you can use a better selector than document.getElementById