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

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 });
}
}

Related

Angular 2 | How to handle input type file in FormControl?

Good day,
How can i handle input type file in formControl? im using reactive form but when i get the value of my form it returns null value on my <input type="file">??
You need to write your own FileInputValueAccessor. Here is the plunker and the code:
#Directive({
selector: 'input[type=file]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: FileValueAccessorDirective,
multi: true
}
]
})
export class FileValueAccessorDirective implements ControlValueAccessor {
onChange;
#HostListener('change', ['$event.target.value']) _handleInput(event) {
this.onChange(event);
}
constructor(private element: ElementRef, private render: Renderer2) { }
writeValue(value: any) {
const normalizedValue = value == null ? '' : value;
this.render.setProperty(this.element.nativeElement, 'value', normalizedValue);
}
registerOnChange(fn) { this.onChange = fn; }
registerOnTouched(fn: any) { }
nOnDestroy() { }
}
And then you will be able to get updates like this:
#Component({
moduleId: module.id,
selector: 'my-app',
template: `
<h1>Hello {{name}}</h1>
<h3>File path is: {{path}}</h3>
<input type="file" [formControl]="ctrl">
`
})
export class AppComponent {
name = 'Angular';
path = '';
ctrl = new FormControl('');
ngOnInit() {
this.ctrl.valueChanges.subscribe((v) => {
this.path = v;
});
}
}
there is no way to handle this in angular form-control.
we can provide some hack to make this work if you want to upload the image.
just add the <input type=file> as form control on which user can add the file and acter grabbing the file we can change it to the base64code and then assign that value to the hidden field of our main form.
then we can store the image file in that way.
else you can go for the ng-file-upload moduleng file upload

Angular 2: Custom Input Component with Validation (reactive/model driven approach)

I have to create a component with custom input element (and more elements inside the component, but its not the problem and not part of the example here) with reactive / model driven approach and validation inside and outside the component.
I already created the component, it works fine, my problem is that both formControl's (inside child and parent) are not in sync when it comes to validation or states like touched. For example if you type in a string with more then 10 characters, the form control inside the form is stil valid.
Plunkr
//our root app component
import {Component, Input} from '#angular/core'
import {
FormControl,
FormGroup,
ControlValueAccessor,
NG_VALUE_ACCESSOR,
Validators
} from '#angular/forms';
#Component({
selector: 'my-child',
template: `
<h1>Child</h1>
<input [formControl]="childControl">
`,
providers: [
{provide: NG_VALUE_ACCESSOR, useExisting: Child, multi: true}
]
})
export class Child implements ControlValueAccessor {
childControl = new FormControl('', Validators.maxLength(10));
writeValue(value: any) {
this.childControl.setValue(value);
}
registerOnChange(fn: (value: any) => void) {
this.childControl.valueChanges.subscribe(fn);
}
registerOnTouched() {}
}
#Component({
selector: 'my-app',
template: `
<div>
<h4>Hello {{name}}</h4>
<form [formGroup]="form" (ngSubmit)="sayHello()">
<my-child formControlName="username"></my-child>
<button type="submit">Register</button>
</form>
{{form.value | json }}
</div>
`
})
export class App {
form = new FormGroup({
username: new FormControl('username', Validators.required)
});
constructor() {
this.name = 'Angular2';
}
sayHello() {
console.log(this.form.controls['username'])
}
}
I have no clue how to solve this problem in a proper way
There exists a Validator interface from Angular to forward the validation to the parent. You need to use it and provide NG_VALIDATORS to the component decorator's providers array with a forwardRef:
{
provide: NG_VALIDATORS,
useExisting: CustomInputComponent,
multi: true
}
your component needs to implement the Validator interface:
class CustomInputComponent implements ControlValueAccessor, Validator,...
and you have to provide implementations of the Validator interfaces' methods, at least of the validate method:
validate(control: AbstractControl): ValidationErrors | null {
return this.formGroup.controls['anyControl'].invalid ? { 'anyControlInvalid': true } : null;
}
there is also another to sync the validators when their inputs change:
registerOnValidatorChange(fn: () => void): void {
this.validatorChange = fn;
}

Applying a pipe or transform to a Reactive Form value

I'm building a simple reactive form. For simplicity, lets say the only data I want to display is a date.
test.component.html
<form novalidate [formGroup]="myForm">
<input type="date" formControlName="date">
</form>
test.component.ts
private date: Date = Date.now();
ngOnInit() {
this.myForm = this.fb.group({
date: [this.date, [Validators.required]]
});
}
The input type=date field on the template requires the date to be in the format of 'yyyy-MM-dd'. The value in event is a JavaScript Date object.
How can I modify the data at the template level so the input value is correct?
What I've tried:
One way to do this would be to inject the DatePipe into my component and apply the conversion in code.
date: [datePipe.transform(this.event.date, 'yyyy-MM-dd'), [Validators.required]]
But this ties the implementation detail of the template to the component. For example, what if a NativeScript template requires the date to be in the format MM/dd/yyyy? The formGroup is no longer valid.
The only way I've been able come up, with the help of #n00dl3 is to wrap the md-input component and provide the proper value via a ControlValueAccessor
import { Component, Input, ViewChild } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
import { DatePipe } from '#angular/common';
import { MdInput } from '#angular/material';
#Component({
selector: 'md-date-input',
template: `
<md-input [placeholder]="placeholder"
type="date"
(change)="update()"
[value]="dateInput">
</md-input>`,
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: DateInputComponent, multi: true }]
})
export class DateInputComponent implements ControlValueAccessor {
#Input() placeholder: string;
#ViewChild(MdInput) mdInput: MdInput;
dateInput: string;
onChange: (value: any) => void;
constructor(private datePipe: DatePipe) {
}
writeValue(value: any) {
this.dateInput = value == null ? '' : this.datePipe.transform(value, 'yyyy-MM-dd');
}
registerOnChange(fn: (value: any) => void) {
this.onChange = fn;
}
registerOnTouched(fn: (value: any) => void) {
}
update() {
this.onChange(this.mdInput.value ? new Date(this.mdInput.value) : '');
}
}

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

Angular 2 How to add click event with empty template?

I have the following Component:
import {Component, ElementRef} from 'angular2/core';
#Component({
selector: 'test',
template: ''
})
export class TestComponent {
el;
constructor(public elementRef: ElementRef) {
this.el = elementRef.nativeElement;
this.renderer = new THREE.WebGLRenderer();
this.el.appendChild(this.renderer.domElement);
}
onClick() {
console.log(this);
}
}
Since the template is empty, how can I add a click event to the component? Note that
this.el.addEventListener('click', this.onClick, false);
won't work because the click event is added to this.el, instead of the component itself (console log returns < test >< /test > rather than TestComponent itself).
You can use host-listener:
#Component({
selector: 'test',
template: ''
host: {'(click)':'clickHandler($event)'}
})
export class TestComponent {
// #HostListener('click') // alternative way to the decorator argument
clickHandler() {
doSomething();
}
}