Angular 2 - Click event on changing array - arrays

Currently I'm trying to learn some more about Angular 2 by creating the amazing one of a kind shopping list application. However got stuck on creating a click event on my search results.
view.html
<form class="search">
<input type="text" name="query" class="form-control" [(ngModel)]="query" (ngModelChange)="change()" (blur)="onBlur()" />
<ul class="results" [class.hidden]="!showResults">
<li *ngFor="let result of results">
<a (click)="selectResult(result)">
{{ result.title }}
</a>
</li>
</ul>
</form>
view.ts
import { Component, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'search',
templateUrl: 'app/search/view.html',
styleUrls: ['app/search/view.css']
})
export class Search
{
... Some interesting things here
// selectResult
// ------------------------------ -->
selectResult (selected: any)
{
console.log(123);
}
// ------------------------------ -->
... More interesting things here
}
When someone starts typing the results will change dynamically. When someone clicks on a result nothing happens, no console.log(123).
Anyone knows how to attach click to a ngFor that changes?

You forget about one little thing, namely remove default href action. In your HTML file:
<a href="#" (click)="selectResult($event, result)">
{{ result.title }}
</a>
and in your TypeScript file:
selectResult (event: any, selected: any) {
event.preventDefault();
console.log(123);
}
I hope that it will help.

I found a way to solve this problem.
Somehow the click event is not bound when you hide the element. (wtf...?)
This is the final solution:
view.html
<form class="search">
<input type="text" name="query" class="form-control" [(ngModel)]="query" (ngModelChange)="change()" (blur)="onBlur()" />
{{ results.length }}
<ul class="results" *ngIf="results.length != 0">
<li *ngFor="let result of results">
<a href="#" (click)="selectResult(result)">
{{ result.title }}
</a>
</li>
</ul>
</form>

Related

How to add item to array in ng-repeat with lazy loading

I have an array of comments. For each of the comment, I have a hidden form where you can reply. That item in each object of the array is called: comment.showSubCommentForm. When I click on Reply, I want to be able to reply with a comment and the function called: commentOnSubPost.
I load my data in chunks of 10 (lazy loading) which I am doing by using the infinite-scroll-distance Angular module. Every time I reach the end of the page, I get the next 10 items and add them to the $scope.comments array, which you can see below is what I am running my ng-repeat on .
The problem is that, although the commentOnSubPost works really well as in it adds the reply to the comment, I have reload the data so I can get the updated $scope.comments with the new reply. BUT - as I have implemented lazy loading, how do I do that if say I am on the 20th page?
I looked at some similar posts but their approach from mine was very different so am very lost.
My HTML:
<div infinite-scroll='loadMore()' infinite-scroll-distance='1'>
<div class="row">
<div ng-repeat="comment in comments track by $index">
<ul class="comments">
<li class="clearfix">
<a ng-href="#">SomeLink</a>
<div>
<p >{{ comment.date }} <a ng-href="/wall#/profile/main/{{ comment.userID }}">{{ comment.fullname }}</a> says <i class="commentReply"><a href ng-click="comment.showSubCommentForm = !comment.showSubCommentForm"><small>Reply</small></a></i></p>
<p class="mainComment">
{{ comment.comment }}
<p class="meta"><i class="showReply"><a href ng-click="comment.showReplies = !comment.showReplies"><small>Show Replies</small></a></i></p>
</p>
</div>
<div ng-show="comment.showSubCommentForm">
<form ng-submit="commentOnSubPost( post._id, comment._id, subComment)">
<div class="form-group">
<div class="form-group">
<textarea ng-model="subComment" class="form-control" placeholder="Reply to the above comment"></textarea>
</div>
<input type="submit" value="Reply">
</div>
</form>
<div ng-show="comment.showReplies" ng-repeat="subComment in comment.subComments track by $index">
<ul class="comments">
<li class="clearfix">
<a ng-href="/wall#/profile/main/{{ comment.userID }}"><img ng-src="{{ subComment.profilePic }}" class="avatar" style="border-radius: 50%"></a>
<div class="post-subComments">
<p class="meta">{{ subComment.date }}<a ng-href="SomeLink"> {{ subComment.fullname }}</a></p>
<p>
{{ subComment.comment }}
</p>
</div>
</li>
</ul>
</div>
</li>
</ul>
</div>
</div>
I won't add too much information here as I don't want to overload this post but if you need any particular information, please do let me know. I just need to know how do I reload the data (or not if there's another approach) so that I can get the latest data.
I hope this makes sense.
Thanks in advance!
Like you have $scope.comments = []; then on each call you just have to append upcoming data to this array.
Like $scope.comments = $scope.comments.concat(response.data);
In this way lazy loading will work.
I fixed this with making a Node route which gives back the exact comment I have just added. Then, I found that commentID in my $scope.comments and made it's subCommenemts to the one I have locally.
It does the job and the data is real. One drawback is that if at that time, there's another comment, it won't show up until the page's refreshed but I'm not too fussed about that.

How to bind a part of a variable already binded

I have a loop ng-repeat that displays sevral icons.
<div class="box">
<div class="box-body">
<div class="row" >
<div class="col-sm-6" style="margin-bottom: 5px;" ng-repeat="record in newlayout.display" align="center">
<a class="btn btn-app" ng-href="#newlayout/{{newlayout.url}}{{newlayout.itemValue}}" >
<span class="badge bg-yellow" style="font-size:22px;">{{record.numberOfSamples}}</span>
<i class="fa fa-{{newlayout.labStyle}}"></i> {{record.lab}}
</a>
</div>
</div>
</div>
</div>
My issue is that the second part of the binded variable itemValue should be dynamic
In my Js, I have this
newLayout.url = 'sublabs/?labName=';
newLayout.itemValue = 'record.lab';
The URL is dynamic.
When I click on the first displayed Icon, the url should look like this :
But it didn't work as I had a compilation error..
Does someone have an idea how to fix this:
http://localhost:8181/#/newlayout/sublabs?labName=PIA/C1 - Shiftlabo
Where the record value "PIA/C1 - Shiftlabo" change.
So basically here if I change
<a class="btn btn-app" ng-href="#newlayout/{{newlayout.url}}{{newlayout.itemValue}}" >
{{newlayout.itemValue}} by {{record.lab}} it would work..but the {{record.**lab**}} should be dynamic as it will have another value when I click on the icon. It will change to {{record.subLab}}
Thanks
Use property acccessor bracket notation inside the binding:
<div>{{record[labOrSublab]}}</div>
JS
var isSublab = false;
$scope.labOrSublab = "lab";
$scope.clickHandler = function() {
isSublab = !isSublab;
$scope.labOrSublab = isSublab ? 'subLab' : 'lab';
};

Google Analytics how to track search word with Angular filter by search key

I am using ng-repreat to display a number of posts on my site and I also allow users to filter the posts by keying in any search phrase in a search input box. It is working fine but I don't know how to use google analytics to track what the users are searching for because the search term is not reflected on my URL. (i.e. the URL is remains as domain-name/#!/content instead of changing to domain-name/#!/content?q=....+... ).
Not sure how to work around this. Really appreciate if you can point me to some resources. Thank you.
This is a simplified version of my code
<input ng-model="vm.searchKey" type="text" placeholder = "Search..." />
<div class="card" ng-repeat="post in vm.posts | filter: vm.searchKey">
<a ng-href="#!/post/{{lifePost.post_id}}">
<img class="card-img" src="{{post.img_url}}">
<div class="card-img-overlay">
<p class="card-text">{{post.tagline_grp}}</p>
<h4 class="card-title">{{post.post_title}}</h4>
<p class="card-text">{{post.post_excerpt}}</p>
</div>
</a>
</div>
I figure out how to do this by enabling Site search Tracking for GA, detect the change event when user key in a search key and send a virtual pageview over.
HTML code
<input ng-model="vm.searchKey" ng-model-options='{ debounce: 1000 }' ng-change='vm.trackSearchKey()' type="text" placeholder = "Search..." />
<div class="card" ng-repeat="post in vm.posts | filter: vm.searchKey">
<a ng-href="#!/post/{{lifePost.post_id}}">
<img class="card-img" src="{{post.img_url}}">
<div class="card-img-overlay">
<p class="card-text">{{post.tagline_grp}}</p>
<h4 class="card-title">{{post.post_title}}</h4>
<p class="card-text">{{post.post_excerpt}}</p>
</div>
</a>
</div>
JS code
vm.trackSearchKey = function() {
if (vm.searchKey) {
ga('set', 'page', '/?q='+vm.searchKey);
ga('send', 'pageview');
}
}

Protractor: Finding the right element to test

I think I'm misunderstanding how elements works..
HTML code:
<div id="div-item">
A link
<form>
<div>
<select>
<option>1</option>
<option>2</option>
</select>
</div>
</form>
</div>
When I do this:
element(by.tagName('select')).all(by.tagName('option')).count();
This gives me 2, which is correct
When I do this:
element(by.id('div-item')).element(by.tagName('select')).all(by.tagName('option')).count();
This gives me 0. I thought chaining elements finds sub-elements. Is this not correct? How do I restrict the .all(by.tagName('option')) only within this div, rather than the whole page?
This is the xeditable library. My HTML code is:
<div id="div-item" class="col-xs-3">
<a id="xeditable-link" href="#" ng-if="canEdit"
editable-select="user_id"
e-ng-options="user.id as user.name for user in users"
onbeforesave="updateProfile({user_id: $data})">
{{ showNameFromID(user_id) || 'none'}}
</a>
</div>
But this generates a lot of HTML code. It's something like:
<div id="div-item" class="col-xs-3">
<a id="xeditable-link" href="#" ng-if="canEdit"
editable-select="user_id"
e-ng-options="user.id as user.name for user in users"
onbeforesave="updateProfile({user_id: $data})">
{{ showNameFromID(user_id) || 'none'}}
</a>
<form ...>
<div class="xeditable-controle..." ...blah blah>
<select ...ng-options="..." blah blah>
<option>1</option>
<option>2</option>
</select>
<span> ...the buttons... </span>
</div>
</form>
</div>
My test spec:
it('should pass ...', function() {
element(by.id('xeditable-link')).click(); // Click the link to bring up xeditable
element(by.tagName('select')).click(); // Click the select to bring up the popup
var allOptions = element(by.id('div-item')).element(by.tagName('select')).all(by.tagName('option'));
expect(allOptions.count()).toBe(2);
for (var i = 0; i < 2; ++i) {
expect(allOptions.get(i).getText()).toBe(testText[i]);
}
});
Both of the expect statements fail. count is 0, instead of 2 and "NoSuchElementError: No element found using locator: By.tagName("select")"
Try a single css locator
$$('#div-item select [option]').count()
// The same as
element.all(by.css('#div-item select [option]')).count()
Turns out i had a 'div-item' in another .html file. Since AngularJS is a single page application, it was picking up that one instead of the one I wanted.

AngularJS - Show and Hide multiple content

In AngularJS, to simply show a field through an a tag, I would do in this way:
<div ng-show="aField">Content of aField</div>
<a ng-click="aField=true">Show aField</a>
until here, no problem.
I would like now to put more buttons and fields so that, when I click on A it shows the content of A, then when I click on button B, content of A disappears and content of B appears.
How can I do this? Thank you.
UPDATE
Thank you everyone for your solutions, they works! Now, I am doing a template for every content of and because I have much data to show but all in the same structure.
Here the index.html
<div ng-model="methods"
ng-include="'templateMethod.html'"
ng-repeat = "method in methods">
here the script.js:
function Ctrl($scope) {
$scope.methods =
[ { name: 'method1',
description: 'bla bla bla',
benefits: 'benefits of method1',
bestPractices : 'bestPractices',
example: 'example'},
{ name: 'method2',
description: 'bla bla bla',
benefits: 'benefits of method2',
bestPractices : 'bestPractices',
example: 'example'} ];
}
and here the templateMethod.html:
<table>
<tr>
<td>
<div ng-show="toShow=='{{method.name}}Field'">
<h3>{{mmethodethod.name}}</h3>
<p>
<strong>Description</strong>
{{method.description}}
</p>
<p>
<strong>Benefits</strong>
{{method.benefits}}
</p>
<p>
<strong>Best practices</strong>
{{method.bestPractices}}
</p>
<p>
<strong>Examples</strong>
{{method.example}}
</p>
</div>
</td>
<td class = "sidebar">
<ul>
<li><a ng-click="toShow='{{method.name}}Field'" class="{{method.name}} buttons">{{method.name}}</a></li>
</ul>
</td>
</tr>
</table>
It works!
But: if I click the first button and then the second one, the content of the first button do not disappear, it appears under the content of the first button...
Problem with the repetition?
Thanks
It might be better to handle more complex logic in the controller, but in general think about the content of the directive strings as normal js:
<div ng-show="aField">Content of aField</div>
<div ng-show="bField">Content of bField</div>
<a ng-click="aField=true; bField=false">Show aField</a>
<a ng-click="aField=false; bField=true">Show bField</a>
Or use ng-show in concert with ng-hide:
<div ng-show="aField">Content of aField</div>
<div ng-hide="aField">Content of bField</div>
<a ng-click="aField=true">Show aField</a>
<a ng-click="aField=false">Show bField</a>
In the former strategy, nothing shows upon page load. In the latter, the bField content shows by default. If you have more than two items, you might do something like:
<div ng-show="toShow=='aField'">Content of aField</div>
<div ng-show="toShow=='bField'">Content of bField</div>
<div ng-show="toShow=='cField'">Content of cField</div>
<a ng-click="toShow='aField'">Show aField</a>
<a ng-click="toShow='bField'">Show bField</a>
<a ng-click="toShow='cField'">Show cField</a>
I'm guessing that you have a list of items and want to show each item content. Something an accordion component does.
Here is a plunker that shows how you could do it: http://plnkr.co/edit/UTf3dEImiDReC89vULpX?p=preview
Or if you want to display the content on the same place (something like a master detail view) you can do it like this: http://plnkr.co/edit/68DJHL582oY4ecSiiUdE?p=preview
simply use one variable which content is visible. http://jsfiddle.net/gjbw7/
<a ng-click="show='a'">Show aField</a>
.
<div ng-show="show=='a'">Content of aField</div>
I would recommend to create a service in case your fields belong to different controllers.
Service:
App.factory('StateService', function() {
return {
openPanel: ''
};
});
Injecting the service in a Controller:
App.controller('OneCtrl', function($scope, StateService) {
$scope.stateService = StateService;
});
Finally using it a view:
<a ng-click="stateService.openPanel='home'">Home</a>
<div ng-show="stateService.openPanel == 'home'">Content of Home</div>
Demo: http://jsfiddle.net/codef0rmer/BZcdu/
Try this way.
<div>{{content}}</div>
<a ng-click="content='a'">Show aField</a>
<br>
<a ng-click="content='b'">Show bField</a>
<br>
<a ng-click="content='c'">Show cField</a>
<br>
<a ng-click="content='d'">Show dField</a>
<br>
<a ng-click="content='e'">Show eField</a>
<br>
<a ng-click="content='f'">Show fField</a>
Take a look at the ng-switch directive.
<div ng-switch="aField">
<div ng-switch-when="someValue1">
HTML content that will be shown when the aField variable value is equal to someValue1
</div>
<div ng-switch-when="someValue2">
HTML content that will be shown when the aField variable value is equal to someValue2
</div>
<div ng-switch-default>
This is where the default HTML content will go (if aField value is not equal to any of the ng-switch-when values)
</div>
</div>

Resources