Angular : Retrieve data from front end in unit test - javascript

I am new to Angular and trying to perform certain task which includes retrieving value from div from front end in ts file. The scenario is as follows :-
I have two drop down on front end and one dropdownToggle button. After inserting and opting for desired operation, the content is showed in a span/div. As part of unit test case, I need to check whether value appeared in div or not. Also, to perform other operation, I am saving that value in an array at *.component.ts file. I have tried several ways to call that array in *.component.spec.ts (test case file) but unable to achieve so.
It would be great if someone can suggest the possible solution to get this resolve.
Code Snippet
Front End
<div class="col">
<div class="btn-group" dropdown >
<button type="button" class="btn" (click)="peformSearch(searchOp)" [disabled]="loading">Search </button>
<button type="button" dropdownToggle class="btn dropdown-toggle " aria-controls="dropdown-split">
</button>
<ul id="dropdown-split" *dropdownMenu class="dropdown-menu" aria-labelledby="button-split">
<li role="menuitem"><a class="dropdown-item" >Add</a></li>
<li role="menuitem"><a class="dropdown-item" >Sub</a></li>
</ul>
</div>
</div>
</div>
<div class="form-group row">
<span class="btn" *ngFor="let e of searchTerms" >{{ e.key }}: {{ e.value }}</span>
</div>
2 . Test Case File (***.component.spec.ts)
const component = fixture.componentInstance;
component.ngOnInit();
fixture.detectChanges();
const one = component.searchForm.controls['one'];
const two = component.searchForm.controls['two'];
// print div or selected value here

you can access the elements like
// print div or selected value here
const div = fixture.nativeElement.querySelector('div.form-group');
console.log(div.innerHTML);
it might be helpful to give more specific css-classes to the elements e.g. search-term-list

import { ComponentFixture } from '#angular/core/testing';
let fixture: ComponentFixture<ComponentName>;
beforeEach(() => {
fixture = TestBed.createComponent(ComponentName);
})
de = fixture.debugElement.query(By.css('.classname-of-the-tag-whose-value-is-to-be-fetched'));
console.log(de);
de.textContent on innerHtml should return the required value.
inside test-suite various options are available By.css('.class'), By.id('#id'), By.tagName('div') etc

Related

How to dynamically add a component on link click in my Angular application

I have three components: (1) a navigation bar, (2) home page with a left and right div, and (3)view-associates. On link from the navbar, I want to dynamically add the view-associates component into the home's right div. I have already implemented the following code (in the traditional JavaScript fashion) into the navbar-component.ts file:
addTemplateTag(){
const link = document.querySelector('.nav-link');
const showArea = document.getElementById('showArea');
console.log(link);
console.log(showArea);
// check for specific class name to get appropriate template tag
if (link.classList.contains('view-associates')){
console.log('Found view-associates class in link. Getting tag...');
// NOTE: the below two lines did work BUT still did not show component
const templateTag = document.createElement('app-view-associates');
showArea.appendChild(templateTag);
}
}
Here is the HTML code with the navbar and home components, respectively:
navbar.component.html
<nav class="nav flex-column">
<a class="nav-link view-associates" (click)="addTemplateTag()">View My Associates</a>
</nav>
home.component.html (Before link click)
<div class="container-fluid">
<div class="float-left left">
<h1 class="title">Welcome</h1>
<app-nav-bar></app-nav-bar>
<button class="btn btn-primary logout-btn" (click)="logOut()">Log Out</button>
</div>
<div id="showArea" class="float-left right">
</div>
</div>
home.component.html (After link click)
<div class="container-fluid">
<div class="float-left left">
<h1 class="title">Welcome</h1>
<app-nav-bar></app-nav-bar>
<button class="btn btn-primary logout-btn" (click)="logOut()">Log Out</button>
</div>
<div id="showArea" class="float-left right">
<app-view-associates></app-view-associates>
<-- ^^^ appended but component not showing -->
</div>
</div>
Here's the images of the home page before and after the link click:
Before (with browser console)
After (with browser console)
This above code did work but still did not show the view-associates component at all. How do I resolve this issue? Any advice is appreciated.
Use ngIf and ngSwitch to show/ hide components dynamically. For example : -
in .html
<app-form></app-form>
<some-component *ngIf="isLoggedIn"></some-component>
<some-component *ngIf="!isWorking"></some-component>
<another-cool-component *ngIf="!isLoggedIn"></another-cool-component>
in .ts
export class MyFunnyComponent implements OnInit {
isLoggedIn = false;
cartValue: number;
constructor() {
}
ngOnInit(): void {
}
}
Obviously there are some other ways to handle these scenarios but, for the start it might be enough. We might also have to pass data from child to parent and vise versa.

Pre-populate current value of WTForms field in order to edit it

I have a form inside a modal that I use to edit a review on an item (a perfume). A perfume can have multiple reviews, and the reviews live in an array of nested documents, each one with its own _id.
I'm editing each particular review (in case an user wants to edit their review on the perfume once it's been submitted) by submitting the EditReviewForm to this edit_review route:
#reviews.route("/review", methods=["GET", "POST"])
#login_required
def edit_review():
form = EditReviewForm()
review_id = request.form.get("review_id")
perfume_id = request.form.get("perfume_id")
if form.validate_on_submit():
mongo.db.perfumes.update(
{"_id": ObjectId(perfume_id), <I edit my review here> })
return redirect(url_for("perfumes.perfume", perfume_id=perfume_id))
return redirect(url_for("perfumes.perfume", perfume_id=perfume_id))
And this route redirects to my perfume route, which shows the perfume and all the reviews it contains.
This is the perfume route:
#perfumes.route("/perfume/<perfume_id>", methods=["GET"])
def perfume(perfume_id):
current_perfume = mongo.db.perfumes.find_one({"_id": ObjectId(perfume_id)})
add_review_form = AddReviewForm()
edit_review_form = EditReviewForm()
cur = mongo.db.perfumes.aggregate(etc)
edit_review_form.review.data = current_perfume['reviews'][0]['review_content']
return render_template(
"pages/perfume.html",
title="Perfumes",
cursor=cur,
perfume=current_perfume,
add_review_form=add_review_form,
edit_review_form=edit_review_form
)
My issue
To find a way to get the review _id in that process and have it in my perfume route, so I can pre-populate my EditReviewForm with the current value. Otherwise the form looks empty to the user editing their review.
By hardcoding an index (index [0] in this case):
edit_review_form.review.data = current_perfume['reviews'][0]['review_content']
I am indeed displaying current values, but of course the same value for all reviews, as the reviews are in a loop in the template, and I need to get the value each review_id has.
Is there a way to do this, before I give up with the idea of allowing users to edit their reviews? :D
Please do let me know if my question is clear or if there's more information needed.
Thanks so much in advance!!
UPDATE 2:
Trying to reduce further my current template situation to make it clearer:
The modal with the review is fired from perfume-reviews.html, from this button:
<div class="card-header">
<button type="button" class="btn edit-review" data-perfume_id="{{perfume['_id']}}" data-review_id="{{review['_id']}}" data-toggle="modal" data-target="#editReviewPerfumeModal" id="editFormButton">Edit</button>
</div>
And that opens the modal where my form with the review is (the field in question is a textarea currently displaying a WYSIWYG from CKEditor:
<div class="modal-body">
<form method=POST action="{{ url_for('reviews.edit_review') }}" id="form-edit-review">
<div class="form-group" id="reviewContent">
{{ edit_review_form.review(class="form-control ckeditor", placeholder="Review")}}
</div>
</form>
</div>
Currently this isn't working:
$(document).on("click", "#editFormButton", function (e) {
var reviewText = $(this)
.parents(div.card.container)
.siblings("div#reviewContent")
.children()
.text();
$("input#editReviewContent").val(reviewText);
});
and throws a ReferenceError: div is not defined.
Where am I failing here? (Perhaps in more than one place?)
UPDATE 3:
this is where the button opens the modal, and underneath it's where the review content displays:
<div class="card container">
<div class="row">
<div class="card-header col-9">
<h5>{{review['reviewer'] }} said on {{ review.date_reviewed.strftime('%d-%m-%Y') }}</h5>
</div>
<div class="card-header col-3">
<button type="button" class="btn btn-success btn-sm mt-2 edit-review float-right ml-2" data-perfume_id="{{perfume['_id']}}" data-review_id="{{review['_id']}}" data-toggle="modal" data-target="#editReviewPerfumeModal" id="editFormButton">Edit</button>
</div>
</div>
<div class="p-3 row">
<div class=" col-10" id="reviewContent">
<li>{{ review['review_content'] | safe }}</li>
</div>
</div>
</div>
You can do this with jQuery as when you open the form, the form will automatically show the review content in there. It will be done by manipulating the dom.
Also, add an id to your edit button, in this example, I have given it an id "editFormButton".
Similarly, add an id to the div in which review content lies so that it is easier to select, I have given it an id "reviewContent"
Similarly, add an id to edit_review_form.review like this edit_review_form.review(id='editReviewContent')
<script>
$(document).on("click", "#editFormButton", function (e) {
var reviewText = $(this)
.parents("div.row")
.siblings("div.p-3.row")
.children("div#reviewContent")
.children()
.text();
$("input#editReviewContent").val(reviewText);
});
</script>
Don't forget to include jQuery.
Also, you can do it with pure javascript. You can easily search the above equivalents on google. This article is a good start!

AngularJS: how to initiate a ng-click on load

We have a widget in ServiceNow where we have buttons that show pre-configured filters. The buttons work great onClick, but we would like the first button to be clicked and the first filter to be applied on load. Is there a way to initiate this on load? We tried using ng-init, but we couldn't get that to work.
<p><strong>Filters:</strong></p>
<button ng-if="options.show_preconfigured_filters=='true'"
class="btn btn-outline-primary pull-left m-r-sm m-b-sm"
ng-repeat="filters in data.preconfigured_filters | orderBy : 'order' track by $index"
ng-click="c.applyFilter(filters);">
<i class="glyphicon glyphicon-filter m-r-sm"></i>{
{filters.title}}
</button>
<div class="clearfix"></div>
c.applyFilter = function(filter) {
$scope.data.filter = filter.filter;
c.appendQuery($scope.data.filter);
}
One approach is to compute the ordered list in the controller:
var orderedList = $filter('orderBy')($scope.data.preconfigured_filters, 'order');
c.applyFilter(orderedList[0]);
This executes the same function that a user invokes by clicking the first item.

Pipe to allow multiple filter values simultaneously - Angular

Buttons are generated using *ngFor bringing back as many different types of values available to filter by. I'm filtering on a key of 'location', therefore if there are locations of 'west' and 'england' then two buttons of 'west' and 'england' are available to filter by.
What I want to be able to do is select more than one filter. If I click 'england' all the results for 'england' come back, then if I click 'west' then 'west' comes back as well as 'england' still being "active". Currently, I can only click one filter at a time.
I think I need to assign an active class to my button then this will push an array of whats active to send to my pipe to do the filtering... ?
My filter Button
<div ngDefaultControl [(ngModel)]="filteredLocation" name="locationFilter" id="locationFilter">
<button value="All" class="m-2 btn btn-primary" type="button">All</button>
<button [class.active]="selectedIndex === i" (click)="filteredLocation = entry.location" class="m-2 btn btn-primary" type="button" *ngFor="let entry of timeLine | filterUnique; let i = index">{{entry.location}}</button>
</div>
single filter on results
<div class="timeline">
<my-timeline-entry *ngFor="let entry of timeLine | filter:filteredLocation:'location'" timeEntryHeader={{entry.year}} timeEntryContent={{entry.detail}} timeEntryPlace={{entry.place}} timeEntryLocation={{entry.location}}></my-timeline-entry>
</div>
I have created a stackBlitz of what I've got - try clicking on a filter you will see only one filter can be applied at one time. https://stackblitz.com/edit/timeline-angular-7-nve3zw
Any help here would be awesome. Thanks
There can be multiple ways to do that one would be to attach some property to determine if location is active for example isLocationActive
Then toggle this flag as per your need and to apply active class also you don't need filter pipe in that case
So your html will look like
<div class="form-group row">
<div ngDefaultControl [(ngModel)]="filteredLocation" name="locationFilter" id="locationFilter">
<button value="All" class="m-2 btn btn-primary" (click)="activeAllEntries()" type="button">All</button>
<button [class.active]="entry.isLocationActive" (click)="entry.isLocationActive = !entry.isLocationActive" class="m-2 btn btn-primary" type="button" *ngFor="let entry of timeLine | filterUnique; let i = index">{{entry.location}}</button>
</div>
</div>
<div class="timeline">
<ng-container *ngFor="let entry of timeLine">
<my-timeline-entry *ngIf="entry.isLocationActive" timeEntryHeader={{entry.year}} timeEntryContent={{entry.detail}} timeEntryPlace={{entry.place}} timeEntryLocation={{entry.location}}></my-timeline-entry>
</ng-container>
</div>
TS function
activeAllEntries() {
this.timeLine.forEach(t=> t.isLocationActive=!t.isLocationActive)
}
working Demo

How can I prevent Angular from re-sorting my list while editing?

I created a little application using Angular to manage Todolists. Each list has a number of todos. Each todo has attributes name, value1 and value2.
Each list should be sorted automatically by Angular, so I used ng-repeat="todo in selectedList.todos | orderBy: todoOrderFilter":
<ul class="list-group">
<li class="list-group-item" ng-repeat="todo in selectedList.todos | orderBy: todoOrderFilter">
<div>
<span>{{todo.name}} (Value1: {{todo.value1}}, Value2 {{todo.value2}})</span>
<button type="button" class="btn btn-warning btn-xs" ng-click="editTodo(todo)"><i class="icon-trash"></i> Edit</button>
<button type="button" class="btn btn-danger btn-xs floatright" ng-click="deleteTodo(todo)"><i class="icon-trash"></i> Delete</button>
</div>
</li>
</ul>
In my controller I defined my order filter like this:
$scope.todoOrderFilter = function (todo) {
return todo.value1 * todo.value2;
};
This works well so far until I tried to make each row editable. To accomplish this, I added an additional <div> with input elements to edit the values inside each <li> and also added ng-hide="todo.editing" and ng-show="todo.editing" to be able to turn on/off edit mode by simply setting todo.editing=true or false;
Full HTML looks like this:
<ul class="list-group">
<li class="list-group-item" ng-repeat="todo in selectedList.todos | orderBy: todoOrderFilter">
<div ng-hide="todo.editing">
<span>{{todo.name}} (Value1: {{todo.value1}}, Value2 {{todo.value2}})</span>
<button type="button" class="btn btn-warning btn-xs" ng-click="editTodo(todo)"><i class="icon-trash"></i> Edit</button>
<button type="button" class="btn btn-danger btn-xs floatright" ng-click="deleteTodo(todo)"><i class="icon-trash"></i> Delete</button>
</div>
<div ng-show="todo.editing">
<input id="todoname" ng-model="todo.name" ng-enter="updateTodo(todo)" type="text" class="form-control marginBottom" placeholder="Todo speichern" aria-describedby="basic-addon2"></input>
Value1: <input ng-model="todo.value1" ng-enter="updateTodo(todo)" type="text" class="form-control marginBottom" placeholder="Value1" aria-describedby="basic-addon2"></input>
Value2: <input ng-model="todo.value2" ng-enter="updateTodo(todo)" type="text" class="form-control marginBottom" placeholder="Value2" aria-describedby="basic-addon2"></input>
<button type="button" class="btn btn-default" ng-click="updateTodo(todo)">Save</button>
<button type="button" class="btn btn-danger" ng-click="cancelUpdateTodo(todo)">Cancel</button>
</div>
</li>
</ul>
Edit button handler:
$scope.editTodo = function(todo) {
todo.editing = true;
};
This kinda works but while I edit input fields for value1 or value2 my sort function is automatically triggered which causes the <li> elements to jump up and down which is really bad.
So what I basically want is that my auto sort filter is disabled while todo.editing=true.
So far I found these similar questions on SO but they weren't really helpful:
Disabling orderBy in AngularJS while editing the list (Don't understand how to apply the answer there to my code)
https://stackoverflow.com/questions/30845516/stop-ng-repeat-auto-sorting-your-objects-while-editing-text-box-and-checkbox-in (No answer)
Question: How can I prevent Angular from resorting the todo list while todo.editing=true?
The solution was to edit a copy of the object instead of directly editing it. Then, replace the original object with the copy when the user finished editing.
I firmly believe in utilising code that has already been written for us by the Angular team, and building upon it. As such, I think this is a perfect scenario for decorating the built-in orderBy filter to accept a fourth argument (ignore).
I haven't tested this myself very thoroughly but it should do the trick;
app.config(function ($provide) {
$provide.decorator('orderByFilter', function ($delegate) {
// Store the last ordered state.
var previousState;
return function (arr, predicate, reverse, ignore) {
// If ignore evaluates to a truthy value, return the previous state.
if (!!ignore) {
return previousState || arr;
}
// Apply the regular orderBy filter.
var order = $delegate.apply(null, arguments);
// Overwrite the previous state with the most recent order state.
previousState = order;
// Return the latest order state.
return order;
}
});
});
Usage:
<div ng-repeat="d in data | orderBy:predicate:reverse:ignore">
<!-- in your case -->
<div ng-repeat="todo in selectedList.todos | orderBy:todoOrderFilter:false:todo.editing>
I hope all of that makes sense (maybe even just works™).

Categories

Resources