how to test a component that having a form with form controls as #Input() - angular4-forms

I have 2 components. I declared a form with form controls in parent component. I am passing this form to child component.
Parent component:
this.partyDetailsForm = new FormGroup({
partySymbol: new FormControl('', []),
partyFullName: new FormControl('', Validators.compose([Validators.required,
CustomValidator.nameValidator])),
partyAbbreviation: new FormControl('', Validators.compose([Validators.required,
CustomValidator.nameValidator]))
});
Html:
now I am trying to test the form in the child component.. but it is not coming...
how can I test it
this error is coming
PhantomJS 2.1.1 (Windows 8.0.0) PartyDetailsComponent should create FAILED
[INFO] TypeError: undefined is not an object

I got this answer...
I am declaring the form in the parent so the form is not available in the
child component so that exception is coming...
The solution is we have to declare a form and pass it to the child component
in spec file #Component... like this
describe('CustomComponent', () => {
// let component: CustomComponent;
// let fixture: ComponentFixture<CustomComponent>;
let component: TestHostComponent;
let fixture: ComponentFixture<TestHostComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [CustomComponent, TestHostComponent],
imports: [FormsModule, ReactiveFormsModule, RouterTestingModule,
HttpModule],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestHostComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
#Component({
selector: `host-component`,
template: ` <app-custom [fieldOneName]="'test'" [color]="'red'"
[formName]="custom_Form" [fieldOneDefaultMessage]="'enterUserName'"
[firstFieldType]="'text'"
[fieldOneInvalidMessage]=" 'Person name Start with Alphabeat' "
[fieldOneMandatoryMessage]="'Person Name is Mandatory'"
[label]="'test'" [required]="true">
</app-custom> `
})
class TestHostComponent {
custom_Form: FormGroup
ngOnInit(){
this.custom_Form = new FormGroup({
test: new FormControl('', Validators.compose([Validators.required,
CustomValidator.emailValidator]))
});
}
}
});

Related

How to use setValue and patchValue in testing angular 2 reactive forms

I can't seem to get the correct dependencies in my spec file for testing a new form I've created.
I have 2 passing tests but the 3rd comes to a hault when I use setValue or patchValue to setup my test form data.
Karma in browser gives: TypeError: Cannot read property 'patchValue' of undefined
import { TestBed, async } from '#angular/core/testing';
import { Component, ViewChild } from '#angular/core';
import { MyComponent } from './my.component';
import { NgForm, ReactiveFormsModule, FormGroup, FormControl, FormArray } from '#angular/forms';
describe('My component test: ', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [ReactiveFormsModule],
providers: [] // which provider for setValue/patchValue?
});
let fixture = TestBed.createComponent(MyComponent);
let myComponent = fixture.debugElement.componentInstance;
});
it('sending form values to the service onSubmit method', () => {
// TypeError: Cannot read property 'patchValue' of undefined
this.jobForm.patchValue({'title': 'a spec title'});
});
The question is: How do I use setValue/patchValue to setup my form object for a test form submission?
If you're using reactive forms, then you should have the FormGroup as a member of the component. This is what you need to access
let component;
let fixture;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [ReactiveFormsModule],
providers: [] // which provider for setValue/patchValue?
});
fixture = TestBed.createComponent(MyComponent);
component = fixture.debugElement.componentInstance;
fixture.detectChanges()
});
it('sending form values to the service onSubmit method', () => {
// assuming the property on the component is named jobForm
const form = component.jobForm;
form.patchValue({'title': 'a spec title'});
});

RouterConfiguration and Router undefined in aurelia

I am very new to Aurelia and just trying to apply navigation to my project.Though i import aurelia-router still it says RouterConfiguration and Router are undefined in constructor
import {Todo} from './ToDo/todo';
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
heading = "Todos";
todos: Todo[] = [];
todoDescription = '';
router :any;
list: any[];
constructor(RouterConfiguration: RouterConfiguration, Router: Router) {
this.todos = [];
this.configureRouter(RouterConfiguration, Router);
//console.log("klist", this.list);
}
//config.map() adds route(s) to the router. Although only route, name,
//moduleId, href and nav are shown above there are other properties that can be included in a route.
//The class name for each route is
configureRouter(config: RouterConfiguration, router: Router): void {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: '', name: 'home', moduleId: 'home/home', nav: true, title: 'Home' },
{ route: 'users', name: 'users', moduleId: './Friends/Friends', nav: true },
//{ route: 'users/:id/detail', name: 'userDetail', moduleId: 'users/detail' },
//{ route: 'files/*path', name: 'files', moduleId: 'files/index', href: '#files', nav: 0 }
]);
}
addTodo() {
if (this.todoDescription) {
this.todos.push(new Todo(this.todoDescription));
// this.todoDescription = '';
}
}
}
By convention, Aurelia looks in the initial class that loads (App) for the configureRouter() function and executes it. This means, you do not have to inject anything in the constructor.
It looks like you've simply added too much. I think fixing your sample seems to be as easy as removing some stuff, like so:
import { Todo } from './ToDo/todo';
import { RouterConfiguration, Router } from 'aurelia-router';
export class App {
heading = "Todos";
todos: Todo[] = [];
todoDescription = '';
list: any[];
constructor() {
// note: removed routing here entirely (you don't need it)
// also, you've already declared this.todos above, so no need to do it here again
}
configureRouter(config : RouterConfiguration, router : Router): void {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: '', name: 'home', moduleId: 'home/home', nav: true, title: 'Home' },
{ route: 'users', name: 'users', moduleId: './Friends/Friends', nav: true }
]);
}
addTodo() {
// removed this for brevity
}
}
This should resolve your 'undefined' errors on Router and RouteConfiguration. As an additional note, don't forget to add the <router-view> to your html template as well. Otherwise, you'll get no errors but the views won't show up either:
<template>
<div class="content">
<router-view></router-view>
</div>
</template>
Great documentation on this can be found at the Aurelia Docs - Routing.

Can the Polymer paper-dropdown-menu be bound using ngControl?

I am trying to create a form in Angular2 using the Polymer paper-dropdown-menu control. Is there a way to bind the selected value of the dropdown to the control in my component? I have tried everything with no luck. Has anyone gotten over this hurdle?
An example of a working paper-input is:
template:
<paper-input type="password"
ngControl="password"
ngDefaultControl>
</paper-input>
component:
constructor(private fb:FormBuilder) {
this.loginForm = fb.group({
password: new Control("")
});
}
Is there something similar for paper-dropdown-menu? Either binding to the value or the text itself would be fine. Thanks!
You need a custom ControlValueAccessor. I didn't succeed using a ControlValueAccessor for the paper-dropdown-menu itself but for the paper-menu or paper-listbox inside the paper-dropdown-menu like
const PAPER_MENU_VALUE_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => PaperMenuControlValueAccessor), multi: true});
#Directive({
selector: 'paper-listbox',
host: {'(iron-activate)': 'onChange($event.detail.selected)'},
providers: [PAPER_MENU_VALUE_ACCESSOR]
})
export class PaperMenuControlValueAccessor implements ControlValueAccessor {
onChange = (_:any) => {
};
onTouched = () => {
};
constructor(private _renderer:Renderer, private _elementRef:ElementRef) {
console.log('PaperMenuControlValueAccessor');
}
writeValue(value:any):void {
//console.log('writeValue', value);
this._renderer.setElementProperty(this._elementRef.nativeElement, 'selected', value);
}
registerOnChange(fn:(_:any) => {}):void {
this.onChange = fn;
}
registerOnTouched(fn:() => {}):void {
this.onTouched = fn;
}
}
See also
ngModel Binding on Polymer dropdown (Angular2)
Bind angular 2 model to polymer dropdown

AngularJS 2 Typescript interface

I have a service for handling users operations and an interface for the user object.
user.service.ts
import {Injectable} from 'angular2/core';
export interface User {
name: string;
email?: string;
picture?: string;
}
#Injectable()
export class UserService {
me: User;
constructor() {
}
setUser(user: User) {
this.me = user;
}
}
In my login component I try to set the user with the profile returned from the login service but I get this error:
Property 'firstName' does not exist on type '{}'.
login.component.ts
import {Component} from 'angular2/core';
import {User, UserService} from './services/user.service';
import {LinkedinService} from './services/linkedin.service';
declare const IN: any;
console.log('`Login` component loaded asynchronously');
#Component({
selector: 'Login',
providers: [
UserService,
LinkedinService
],
template: require('./login.html')
})
export class LoginComponent {
me: User;
constructor(public linkedinService: LinkedinService, public userService: UserService) {
this.me = userService.me;
}
ngOnInit() {
console.log('hello `Login` component');
}
login() {
this.linkedinService.login()
.then(() => this.linkedinService.getMe()
.then(profile => this.userService.setUser({ name: profile.firstName })));
}
}
linkedin.service.ts
import {Injectable} from 'angular2/core';
declare const IN: any;
#Injectable()
export class LinkedinService {
constructor() {
IN.init({
api_key: 'xxxxxxxxxxx',
authorize: true
});
}
login() {
return new Promise((resolve, reject) => {
IN.User.authorize(() => resolve());
});
}
getMe() {
return new Promise((resolve, reject) => {
IN.API.Profile('me').result((profile) => resolve(profile.values[0]));
});
}
}
I'm trying to import the User interface from UserService and use inside the LoginComponent but I don't know what I'm doing wrong. Any idea? I am not sure if I have to use the User interface inside the LoginComponent, is that right?
Narrow in on the code :
.then(() => this.linkedinService.getMe())
.then(profile => this.userService.setUser({ name: profile.firstName })));
The type of profile is driven by the response of this.linkedinService.getMe(). Seems like it is something like Promise<{}>. It does not have the member firstName. Hence the error:
Property 'firstName' does not exist on type '{}'.
Fix
Check to the code / signatures of linkedinService. This has nothing to do with the user.service.ts file that the question contains 🌹
Update
Focus in on the code:
getMe() {
return new Promise((resolve, reject) => {
IN.API.Profile('me').result((profile) => resolve(profile.values[0]));
});
}
The value returned is driven by what is being passed to resolve. So make sure profile.values[0] has the right type. Alternatively provide the hint to the compiler:
getMe() {
return new Promise<{firstName:string}>((resolve, reject) => {
IN.API.Profile('me').result((profile) => resolve(profile.values[0]));
});
}

how to access super component class variable into sub component Class?

How i access super component class variable into sub component in Angular2?
super Component Article.ts
#Component({
selector: 'article'
})
#View({
templateUrl: './components/article/article.html?v=<%= VERSION %>',
styleUrls : ['./components/article/article.css'],
directives: [CORE_DIRECTIVES, AmCard, NgFor]
})
export class Article{
articleArr : Array;
constructor() {
this.articleArr = new Array();
}
articleSubmit(articleSubject, articleName, articleUrl)
{
this.articleArr.push({title: articleSubject.value, user : articleName.value, url : articleUrl.value});
}
}
super Component article.html
<div *ng-for="#item of articleArr">
<am-card card-title="{{item.title}}" card-link="{{item.url}}" card-author="{{item.user}}"></am-card>
</div>
sub component amcard.ts
#Component({
selector: 'am-card',
properties : ['cardTitle', 'cardLink', 'cardAuthor']
})
#View({
templateUrl: './components/card/card.html?v=<%= VERSION %>',
styleUrls : ['./components/card/card.css'],
directives: [CORE_DIRECTIVES]
})
export class AmCard {
constructor() {
}
}
sub Component amcard.html
<div class="card">
...
</div>
So my question is how to access articleArr of Article Class in AmCard class ?
advanced
Thanks for helping me.
You can inject a parent component into a child using angular2 Dependency Injection. Use #Inject parameter decorator and forwardRef to do it (forwardRef allows us to refer to Article which wasn't yet defined). So your AmCard component will look like (see this plunker):
#Component({
selector: 'am-card',
template: `
<span>{{ articleLength }} - {{ cardTitle }}<span>
`
})
export class AmCard {
#Input() cardTitle: string;
#Input() cardLink: string;
#Input() cardAuthor: string;
constructor(#Inject(forwardRef(() => Article)) article: Article) {
// here you have the parent element - `article`
// you can do whatever you want with it
this.articleLength = article.articleArr.length;
setTimeout(() => {
article.articleSubmit({ value: Math.random() }, {}, {});
}, 1000)
}
}
But, IMHO, it's a bad pattern. If possible, it's much better to use output property (event binding) to pass message to a parent component and in a parent component handle that message. In your case it would look like (see this plunker):
#Component({ /* ... component config */})
class AmCard {
// ... input properties
#Output() callSubmit = new EventEmitter();
constructor() {
setTimeout(() => {
// send message to a parent component (Article)
this.callSubmit.next({ value: Math.random() });
}, 1000)
}
}
#Component({
// ... component config
template: `
<h3>Article array:</h3>
<div *ng-for="#item of articleArr">
<am-card
[card-title]="item.title"
[card-link]="item.url"
[card-author]="item.user"
`/* handle message from AmCard component */+`
(call-submit)=" articleSubmit($event, {}, {}) "
></am-card>
</div>
`
})
class Article{
// ... properties and constructor
articleSubmit(aa, an, au) {
this.articleArr.push({ title: as.value, user: an.value, url: au.value });
}
}