Angular testing directive inputs are not passed to directive - karma-jasmine

I have the following custom directive in Angular11.None of the variables are replaced in the test component and the [rowClick] is taking value as string instead of an object. not sure i am not able to pass object as directive input parameter.
#Component({
selector: 'fake-component',
template: `<table><tr
[rowClick] = "{commands: ['/enhanced-audits/builder/detail/123'], externalUrl: false}"
[rowClickGate]="testObservable$"
[rowClickData]="testData"
(onRowClicked)="rowClickHandler($event)"><td>Row 1 Col 1</td></tr></table>`
})
class RowClickTestComponent {
public selectedWorkflowOwnerId: number;
public selectedSectionName: string;
public isWorkflowTask: boolean;
public testData = {
ownerId: 123,
sectionName: 'test section name',
isWorkflowTask: true
}
rowClickHandler(rowClickData: RowClickData): void {
}
testObservable$ = new Observable((subscriber) => {
subscriber.next(true);
subscriber.complete();
});
}
Directive:
export class RowClickDirective implements OnInit {
#Input('rowClick')
options: RouterNavigateParams;
#Input('rowClickGate')
gate$?: Observable<boolean>;
#Input() rowClickData: RowClickData;
#Output() onRowClicked: EventEmitter<RowClickData> =
new EventEmitter<RowClickData>();
}

Related

Angular 5 BehaviorSubject data not update UI

I use to change dynamic child component in body and keep static header, bottom and menu.
My problem: When use BehaviorSubject as shared-data between components, then UI (*ngFor) not be updated event shared-data transferred well. I am using Angular 5.2.0, RxJs 5.5.6
My app has flow:
user click search button on Layout-top.component.ts -> fetch data from Backend server by Home.service.ts-> set data in BehaviorSubject object.
On Home.component.ts constructor always subscribe shared-data from Home.service.ts -> change data of Home.component.ts -> display them.
1. App.compoenet.ts
#Component({
selector: 'xxx',
template:
`
<gotop position="200"></gotop>
<layout-top></layout-top>
<router-outlet></router-outlet>
<layout-bottom></layout-bottom>
`
})
export class AppbComponent implements OnInit, AfterViewInit{
public ngAfterViewInit(): void {
this.spinner.hide();
}
message:string;
constructor(private spinner:Spinner){
}
public ngOnInit(){
this.spinner.show();
}
}
Layout-top.component.ts
public doSearch(){
let filter = {
xx:'XXX'
};
this.homeService.setData(filter);
}
3.Home.service.ts
#Injectable()
export class HomeService extends BaseService{
public data =new BehaviorSubject<DataType>(<DataType>{});
public eventFilter: EventEmitter<{}> = new EventEmitter();
public constructor(private http: HttpClient,
private _const: Const,
private util:Util,
private appref: ApplicationRef) {
super(_const, util);
}
public listProduct(filter):Observable<any>{
const url = url to my backend api
let headers:HttpHeaders = this.util.header(this._const, null, 'application/json');
return this.http.post(
url,
filter,
{headers})
.map(res => {
return res;
});
}
public getData():Observable<DataType>{
return this.data.asObservable();
}
public setData(filter:any):void {
const listProduct$ = this.listProduct(filter);
listProduct$.subscribe(res => {
this.data.next({res:res, filter:filter});
});
}
public cleanData() {
this.data.next(null);
}
}
layout-top.html
5.home.html
<div class="product-item"
*ngFor="let item of listProducts">
<!--display some thing here-->
</div>
6.home.component.ts
constructor(private service: HomeService,
private cdRef:ChangeDetectorRef,
private zone:NgZone,private appref: ApplicationRef ){
this.subsListProduct = this.service.getData().subscribe(obj=>{
this.zone.run(()=>{
$("#in-blur").css("display", "block");
if(!obj){
return;
}
const res = obj.res;
const filter = obj.filter;
if(res && filter){
this.listProducts = res.list;
this.cdRef.detectChanges();
}
});
setTimeout(()=>{
$("#in-blur").css("display", "none");
}, 1000);//for test loading spinner. will be remove in product
});
}
"this.listProducts = res.list;" work fine, ther listProducts be updated, but UI is not any change.
Many people advised use zone.run() or ChangeDetectorRef.detectChanges() but not work in my app. Plz support me.
The best way to map observable data to views is to use the async pipe - https://angular.io/api/common/AsyncPipe - this way all the change management and unsubscribing form the observable is handled for you. So in your example:
The view:
<div class="product-item"
*ngFor="let item of (listProducts | async)?.list">
<!--display some thing here-->
</div>
The ? before .list makes the property optional, so nothing will break if list is is null or undefined
The home component:
constructor(private service: HomeService,
private appref: ApplicationRef ){
this.listProducts = this.service.getData();
}
If you need to manipulate any of the data from the observable before displaying in the view do that in a .map, eg.
constructor(private service: HomeService,
private appref: ApplicationRef ){
this.subsListProduct = this.service.getData()
.map(obj => {
if (obj.list && obj.filter) {
return obj;
} else {
return null;
}
});
}
You should also consider dropping jquery for doing your css changes and use ngClass in your view to do this instead - https://angular.io/api/common/NgClass

Aurelia: Declaring custom element at top-level and access across application

I have a custom element called loading-bar which is used in a number of pages in my app. It's purpose is to show a status message while loading, download of content, and give response messages for backend actions.
loading-bar.html:
<template show.bind="visible">
<i class="fa fa-circle-o-notch fa-spin fa-fw" if.bind="type==1"></i>
<i class="fa fa-check fa-fw" if.bind="type==2"></i>
<span id="loading-text">${message}</span>
</template>
loading-bar.ts:
import { customElement, bindable, bindingMode, inject } from 'aurelia-framework';
#customElement('loading-bar')
export class LoadingBarCustomElement {
private visible = false;
#bindable({ defaultBindingMode: bindingMode.twoWay }) message;
#bindable({ defaultBindingMode: bindingMode.twoWay }) type = 1;
constructor() {
this.visible = false;
}
messageChanged(newValue, oldValue) {
if (newValue && newValue !== '')
this.visible = true;
else
this.visible = false;
}
}
At the moment all pages using the loading bar have this element declared in its html:
<loading-bar message.bind="loadMsg" type.bind="loadType"></loading-bar>
The loading-Bar is controlled by changing the loadMsg and loadType local variables in every page.
What I would like to do is to declare the loading-bar html at just on place, and be able (from any page) to call a method like "showBar(msg, type)" that will affect the globally declared loading-bar.
My first though is to declare the loading-bar in app.html, (just like my nav-bar is declared here) and inject a class (in all the pages' ViewModel), which contain the showBar(msg, type) method that will control the loading-bar.
I am not sure if this is the correct way forward, or how it's best implemented, and would appreciate some help.
You can use the EventAggregator to enable what you want to do.
loading-bar.ts
import { customElement, bindable, bindingMode, autoinject } from 'aurelia-framework';
import { EventAggregator, Subscription } from 'aurelia-event-aggregator';
#customElement('loading-bar')
#autoinject()
export class LoadingBarCustomElement {
private visible = false;
private sub1: Subscription;
private sub2: Subscription;
#bindable({ defaultBindingMode: bindingMode.twoWay }) message;
#bindable({ defaultBindingMode: bindingMode.twoWay }) type = 1;
constructor(private ea : EventAggregator ) {
this.ea = ea;
this.visible = false;
}
attached() {
this.sub1 = this.ea.subscribe('show-loading', ({ message, type }) => {
this.type = type;
this.message = message;
});
this.sub2 = this.ea.subscribe('hide-loading', () => {
this.message = '';
});
}
detached() {
this.sub1.dispose();
this.sub2.dispose();
}
messageChanged(newValue, oldValue) {
if (newValue && newValue !== '')
this.visible = true;
else
this.visible = false;
}
}
and then publish the events elsewhere in your app like this:
import {autoinject} from 'aurelia-framework';
import {EventAggregator} from 'aurelia-event-aggregator';
#autoinject()
export class ExamplePage {
constructor(private ea: EventAggregator){
...
}
async methodUsingTheLoadingBar(){
...
this.ea.publish( 'show-loading, { message: 'Loading...', type: 1 });
const foo = await getData();
...
...
this.ea.publish('hide-loading');
}
}
Decided to solve it like this:
<loading-bar message.bind="lbc.loadMsg" type.bind="lbc.loadType"></loading-bar>
was added to app.html
Created a controller class which will be used as a singleton:
export class LoadingBarController {
private loadMsg = '';
private loadType = 1;
public showBar(message, type) {
this.loadType = type;
this.loadMsg = message;
}
public hideBar() {
this.loadMsg = '';
}
}
This is injected in app.ts (with alias "lbc") where it actually is connected to the loading-bar element, as well injected in every viewModel of pages that wants to use the loading bar.
The loading-bar is then controlled through the injected controller like this:
#inject(LoadingBarController)
export class ExamplePage {
constructor(private lbc: LoadingBarController){
...
}
methodUsingTheLoadingBar(){
...
this.lbc.ShowBar('Loading...', 1);
...
...
this.lbc.HideBar();
}
}

Binding and Displaying Data With Aurelia

I am new to Aurelia. I have a WebApi that will return some data that I would like to populate into my exported model and then display the info on the screen.I'm thinking it would go into my run event but am not sure. Can anybody tell me how to do this . Any information would be most appreciated. My code is below.
--Jason
import 'fetch';
import {HttpClient, json} from 'aurelia-fetch-client';
import {inject} from 'aurelia-dependency-injection';
declare var window: { wcApiUrl: string, wcAmtInstanceId: string };
#inject(HttpClient)
export class BureauModUpdate {
files: string;
constructor(private http: HttpClient) {
http.configure(x => {
x.defaults.headers = { 'Authorization': 'Basic ' + window.wcAmtInstanceId }
});
}
public run(): void {
//Would I put it here ??
}
upload(): void {
var form = new FormData()
for (var i = 0; i <= this.files.length; i++) {
form.append('file', this.files[i])
this.http.fetch(window.wcApiUrl + '/Lookup/BureauModUpdate/CreateBureauModUpdates', {
method: 'post',
body: form
})
}
}
}
export class BureauModUpdateHistory {
public IndexId: number;
public UploadID:number;
public EmployeeNum: number;
public filename: string;
public Bureau: string;
public UploadedDate: Date;
public UploadedStatus: string;
public ErrorInfo: string;
public RecordCount: number;
}
To make something happen when the page loads, use the attached() Aurelia component lifecycle method, like this:
attached() {
// do something here
}
For more information on the Component Lifecycle, see the documentation on Aurelia's DocHub.
Example using Fetch to get data:
For getting HTTP data using fetch (or any other web service), you need to use an async call (using .then to chain the next event). For example:
this.http.fetch(url).then(response => {
this.data = response;
}
Then, just bind your data to this.data (depending on what type of data you're getting). For example:
<template>
Hello, ${data.fname}!
</template>

Aurelia Custom element with dialog

I am having trouble with creating a custom element that will be used like
<shimmy-dialog type="video" href="/test">Hi</shimmy-dialog>
The custim element will replace this code with a href that when clicked should popup a dialog of a particular type.
Everything seems to work up until the point I try to open the dialog.
This is when I get the error
Unhandled rejection TypeError: Cannot set property 'bindingContext' of null
I do sometimes find the Aurelia errors a little cyptic.
I suspect it has something todo with the element not having a view.
The code is as follows
enum DialogType {
video = 1,
iframe
};
#inject(Bcp, DialogController)
export class ShimmyDialogModel {
private type : DialogType;
constructor(private bcp: Bcp, private controller : DialogController){
console.log("here");
}
async activate(state){
this.type = state['type'];
}
get isVideo() : boolean {
return this.type == DialogType.video;
}
get isIframe() : boolean {
return this.type == DialogType.iframe;
}
}
#noView
#processContent(false)
#customElement('shimmy-dialog')
#inject(Element, App, Bcp, DialogService)
export class ShimmyDialog {
#bindable public type : string;
#bindable public href;
#bindable public name;
private originalContent : string;
constructor(private element: Element, private app: App, private bcp: Bcp,
private dialogService: DialogService) {
this.originalContent = this.element.innerHTML;
}
bind() {
this.element.innerHTML = '' + this.originalContent + '';
}
attached() {
let self = this;
this.type = this.element.getAttribute("type");
let dialogType = DialogType[this.type];
this.element.children[0].addEventListener("click", function(){
if(dialogType == DialogType.iframe) {
self.dialogService.open({ viewModel: ShimmyDialogModel, model: {'type' : dialogType}}).then(response => {
});
}
else if(dialogType == DialogType.video) {
self.dialogService.open({ viewModel: ShimmyDialogModel, model: {'type' : dialogType}}).then(response => {
});
}
return false;
});
}
async typeChanged(newValue) {
this.type = newValue;
}
async hrefChanged(newValue) {
this.href = newValue;
}
}
The template for the dialog is below.
<template>
<require from="materialize-css/bin/materialize.css"></require>
<ai-dialog>
<ai-dialog-header>
</ai-dialog-header>
<ai-dialog-body>
<div if.bind="isVideo">
Video
</div>
<div if.bind="isIframe">
IFrame
</div>
</ai-dialog-body>
<ai-dialog-footer>
<button click.trigger="controller.cancel()">Close</button>
</ai-dialog-footer>
</ai-dialog>
</template>
Thanks for any help.
I solved this by seperating the classes into their own files.
Aurelia did no like having two export classes there.

Binsor: How to use dictionaries whose elements are components in the container

Any idea how to configure SortedList whose elements are components register on the container in binsor.
Crud:ICrud
{
public SortedList<string, ICrudTransfer> Proxies
{
get { return _proxies;}
set { _proxies = value; }
}
}
I would like to do something like:
component ‘proxy1′, ICrudTransfer
component ‘proxy2′, ICrudTransfer
_proxies = SortedList[of string, ICrudTransfer]()
_proxies['url1']=#proxy1
_proxies['url2']=#proxy2
component ’service’, ICrud, Crud:
Proxies = _proxies
But it doesn't work
I would like to use such as properties, arrays or list. That works
component ’service’, ICrud, Crud:
CrudProxy = #proxy3
CrudProxies = (#proxy1 , #proxy2)
Thanks
Solution:
public class TestClass:ITestClass
{
IDictionary<string, IClass1> _dictGen = null;
IList<IClass1> _listGen = null;
public IList<IClass1> ListGeneric
{
get { return _listGen; }
set { _listGen = value; }
}
public IDictionary<string, IClass1> DictionaryGeneric
{
get { return _dictGen; }
set { _dictGen = value; }
}
}
component 'c1', IClass1, Class1
component 'c2', IClass1, Class1
component 'd', ITestClass, TestClass:
DictionaryGeneric = {key1:#c1,key2:#c2}
ListGeneric = (#c1,#c2)
Without generic (IList or IDictionary) seem not to work.