Automatic click element after all data loaded in Angular 2 - javascript

I am trying to figure out how to automatic trigger a click event on certain element after all data are loaded.
the code is like this in html file:
<div *ngFor="let location of locations; let i = index;" class="location-wrapper" (click)="onSelect($event, i); $event.stopPropagation();">
<div class="location">{{ location }}</div>
</div>
the onSelect() method is doing some expansion of something that related to current location.
What I am trying to achieve is that I want the very first element of the *ngFor can be automatically clicked to show the things that related to it every time I get to this page.
Or maybe we can achieve it using other similar approach?
I have tried several ways to do this,
like putting some code in window.on('load', function() { // blablabla });
or using ngAfterViewInit() and ngAfterViewChecked(), both not work well.

You can do this in at least 2 ways. The first one would be old-fashioned javascript click(). The second would be just using component logic, just create an variable like selectedLocation which would hold current index or Object that is currently expanded. Don't forget to add initial value to it to make it after load page visible.
Javascript dispatchEvent (not Angular friendly solution)
Simply we just need to grab our item and use click() function. That's it. To grab an element we can use basic javascript method document.getElementById(elementId)" or with template variable.
<div
[id]="'location_' + i" <!-- For biding with document.getElementById -->
#locationPanel <!-- For biding with template variable -->
*ngFor="let location of locations; let i = index;" class="location-wrapper" (click)="onSelect($event, i); $event.stopPropagation();">
<div class="location">{{ location }}</div>
</div>
With Id it would look like document.getElementById("location_0").click() this gonna dispatch click event on element.
For template variable in your component you need to add
#ViewChildren locationPanel: QueryList<any>;
openFirstLocation() {
if (this.locationPanel && this.locationPanel.first)
this.locationPanel.first.nativeElement.click();
}
And in afterViewInit just call this.openFirstLocation();
Please note that it's not Angular friendly because Angular does not like when you interfere directly with DOM. However as long we don't change anything in structures then everything should be fine, but we should avoid manipulating dom with plain javascript.
Please note that too about using #ViewChild and document.* methods.
Use this API as the last resort when direct access to DOM is needed. Use templating and data-binding provided by Angular instead. Alternatively you can take a look at Renderer2 which provides API that can safely be used even when direct access to native elements is not supported.
Relying on direct DOM access creates tight coupling between your application and rendering layers which will make it impossible to separate the two and deploy your application into a web worker.
From Angular docs link

Related

Getting The Source Element From Click Event Handler In Angular

I am still new to Angular and I'm struggling to get the DOM Element of an Angular Click Listener.
What I have is the following component HTML:
<div *ngFor="let menuItem of menu; index as itemId" class="menuItem">
<div class="menuItem__top" (click)="itemClicked($event, !!menuItem.submenu)">
<!-- Additional divs inside... -->
</div>
</div>
I would like to toggle a class of "menuItem__top" when it is clicked. My approach was to use a click event listener but I can't get the source Element to apply the class on.
itemClicked(event, hasSubmenu){
console.log(this) //is the component
let target = event.target || event.srcElement || event.currentTarget;
if(hasSubmenu){
console.log(target);
}
}
It could be done by getting the target of the $event but this way I would need to check the target and move with closest(".menuItem__top") up to the correct source element.
Is there an easy way in Angular to pass the source element of the click listener to the click function or a way to get it inside the click function?
In vanilla JS it would be as easy as using "this" inside the click function, but in Angular this is bind to the component. (In this case, it would be ok to loose the binding to the component if this is the only way.)
I thought about two ways:
Assigning a dynamic reference containing some string and the itemId, passing the itemId and retrieving the reference object based on the itemId in the listener.
Using a #HostListener to listen on every "menuItem__top" click and toggling the class every time.
What do you think is the best way? I feel like I am missing something simple here.
Go the other way around. People are used to jQuery and the way it works (selecting elements already present in the DOM, then adding them a class). So in Angular, they try to do the same thing and grab the element that was clicked, to apply it a class. Angular (and React and others) work the other way around. They construct the DOM from data. If you want to modify an element, start with modifying the data it was generated from.
This <div class="menuItem__top"> that you click on is constructed from let menuItem. All you have to do is add a property, say "isClicked" to menuItem.
<div *ngFor="let menuItem of menu; index as itemId" class="menuItem">
<div class="menuItem__top"
[class.isClicked]="menuItem.isClicked"
(click)="menuItem.isClicked = true">
<!-- Additional divs inside... -->
</div>
</div>

Applying a javascript function on a div

I want to put a variable on a div and to be applied and inherited by all the dojo widgets under this div.
Is this feasible ?
For example
<div shaper="Contextual">
<textarea ..../>
<select multiple data-dojo-type="dijit/form/MultiSelect">
....
</div>
I want the functionality supported by the shaper to be applied to all the widgets included in the div.
p.s.: "shaper" is a custom module created to do numeric shaping for Arabic numbers.
It's possible, but not out of the box.
You can write something like this:
require(["dojo/query", "dojo/domReady!"], function(query) {
query("[shaper]").forEach(function(shaper) {
});
});
This will query all elements with a shaper attribute and loop over it. Inside the loop, you will have to retrieve the value of the shaper attribute (for example Contextual), you can do that with the getAttribute() function, for example:
var shaperModule = shaper.getAttribute("shaper");
Now you have the name of the module to load, so you can write something like this inside the loop:
require([shaperModule], function(shaperModule) {
});
This will use AMD to retrieve the Contextual module. Now all that's left is to include the shaper functionality into all widgets inside your <div>.
First of all, with dijit/registry::findWidgets() you can retrieve all widgets inside a specific DOM node, you can use this to retrieve your dijit/form/MultiSelect widget in this case:
registry.findWidgets(shaper);
Then you can loop over the array of widgets that are found and use dojo/_base/lang::mixin() to extend an object with the contents of another object, for example:
registry.findWidgets(shaper).forEach(function(widget) {
lang.mixin(widget, shaperModule);
});
For example: http://jsfiddle.net/zLv7cvzt/
Though this might not work entirely (what if the module does not exist or what about widgets inside widgets, which dijit/registry::byId() does not detect), it does give you an idea of how to achieve it.
To answer your second question, if it's feasible or not, I would say that it depends. If you extend a widget with another widget like this, it could really end up with really weird things, because all widgets extend from dijit/_WidgetBase, which provides the widget lifecycle, you could mix both widgets their lifecycle.
Also, if you end up doing this and you get an error, it will be really hard to debug this if you're not familiar with the custom code.

Bind data to content element in Polymer

I wanted to ask if it is possible to bind data to the element and access it later inside the actual content. Here is an example: I want to create a list component, however let the user define how to render every entry. Here is my current code:
List Element:
<template repeat="{{item in items}}">
<content></content>
</template>
User using it:
<ak-list items="{{items}}">
{{item.name}}
</ak-list>
However, this does not work
I suppose - you won't get access to data model from inside HTML portion in the web components.
You need to be defining the data in the template. I guess you might be already aware of that.
http://jsbin.com/yadazo/1/edit?html,output
A bin with how it could work.
Also, you can control the presentation by passing in an additional data which you can then use in your template -
An example of the same is below. -
http://jsbin.com/yateka/1/edit?html,output
both the list and how you want it can be supplied and then template created with that stuff.

Knockout template and unique ID within complex web app causing problems

Knockout templating system is great, however in a web app where there are several separated contexts ("views") that are loaded by ajax, one issue appears:
Templates rely on ID
This means that if my chance you have one template with the same name on a view that on another view loaded previously and still existing in the webapp context, knockout (because the browser does this) will take the first matching #templateId element.
On our webapp, we eliminated the ID of all our elements, and when it really needs to be used, it's an ID that is javascript determined to not have duplicates.
Some views can be loaded multiple times in the lifetime of the app, so
no, we can't say "simply check if the id is already used before making your html code" to our team members.
The only other thing we could do would be to check if a specific template is loaded, and if not load it in async, then apply bindings. But for simplicity purpose and the way our project is set up right now, we can't apply an js AMD-like dependency manager.
Questions
Is that possible to specify directly the DOM reference to the template directly?
data-bind="template:function(){ return $('yourSelectorToTheTemplate')[0]; }"
I've looked knockout code and it's weird because we have this:
templateDocument = templateDocument || document;
var elem = templateDocument.getElementById(template);
if (!elem)
throw new Error("Cannot find template with ID " + template);
return new ko.templateSources.domElement(elem);
This means that it really use the DOM element, so why being forced to give an ID for it if we already have the ID?
How do we retrieve dynamically applied IDtemplate, that is also calling another dynamically applied ID (template recursively calling itself for example)?
Setting ID from a binding handler may be wrong: it may set the ID after other data bound elements referred to it, but it would be simpler to have to.
The best solution found for the moment:
Place templates (script element) at the top of the html view.
Use a bindingHandler that does initialization (I called mine "init" as you can see in the example below) to set the ID of the script element
Store that ID inside the $root context so it can be reused by other elements
The result looks like this:
<script type="text/html" data-bind="init: function(){ $rawData.folderItemTemplate = functionThatSetsAndReturnsUniqueId($element, 'folderItemTemplate'); }">
<li>
Some item
<ul data-bind="template: { name: $rawData.folderItemTemplate, foreach: children }"></ul>
</li>
</script>
As you can see we can use this template binding with template: { name: $rawData.yourPropertyName, foreach:... }

Getting an Element in AngularJS

It seems that getting an element in AngularJS is a bad idea, i.e. doing something like:
$('.myElement')
in say, a controller is not an angular way of doing things.
Now my question is, how should I get something in angular?
Right now, what I'm doing (and is an accepted way of doing it) is by watching a variable, and my directive does something based on it.
scope.$watch('varToWatch', function (varToWatch) {
if(attrs.id == varToWatch)
{
//Run my Directive specific code
}
});
However, while this particular design works for most cases, watch is an expensive operation, and having lots of directives watching can really slow down your application.
TL:DR - What is an angular way of getting a directive based on a variable on the directive? (like the one above)?
If you want to get/set values you don't need to fetch the element using jQuery. Angular data binding is the way to do it.
directives is the way to go if you want to do animations or any kind of element attributes and DOM manipulation.
Your code is basically right; the directive should watch something in the $scope and perform it's logic when that thing changes. Yes, watch statements are expensive, and that is a problem once your number of watches start to approach ~2000.
Looking at your code though, I see one problem:
The variable $scope.varToWatch references an id in the template.
When this variable changes, you want something to happen to the element which has this id.
The problem here is in the first point: The controller should know nothing about the DOM, including the id of any element. You should find another way to handle this, for example:
<div my-directive="one"> ... </div>
<div my-directive="two"> ... </div>
<div my-directive="three"> ... </div>
...etc
And in your directive:
scope.$watch('varToWatch', function (varToWatch) {
if(attrs.myDirective == varToWatch)
{
// Run my Directive specific code
}
});
You are very vague as to what you're trying to achieve, but I'll try to answer in context of your last comment.
I have a lot of the same directives (therefore the code will run on all of them), but I need to get only one directive from the lot.
You talk a lot about getting the right element. The directive element is passed to the link function in the directive. If you are not using this element (or children of it) directly, but rather trying to search for the element you want somehow, you are most likely approaching the problem the wrong way.
There are several ways to solve this, I'm sure. If you're thinking about animations, there is already support for that in Angular, so please don't try reinvent the wheel yourself. For other logic, here are two suggestions:
Secondary directive
If the logic you want to apply to this directive is generic, i.e. it could be applied to other directives in your application, you could create a new directive which works together with directives. You can set prioritization in directive in order to control which directive is executed first.
<main-directive ... helper-directive="{{condition_for_applying_logic}}"></main-directive>
jsFiddle example
Expanding main directive
If the logic is tightly coupled to this directive, you can just create a new attribute, either dynamic or static, and bind to it in the directive. Instead of checking 'attrs.id == varToWatch', you check if $scope.apply-logic === 'true' and apply the logic then.
<main-directive ...></main-directive> <!-- Not applied here -->
<main-directive apply-logic="true" ...></main-directive> <!-- Applied here -->
<main-directive apply-logic="{{some.varOnScope}}"...></main-directive> <!-- Conditional -->
Please comment if something is unclear.

Categories

Resources