I don't even know if this is possible in TypeScript, but I'm trying to inherit a function from a Class, like this:
import {Component, AfterViewInit, ElementRef} from 'angular2/core';
#Component({})
class Class1 {
name: string;
constructor(private el: ElementRef) {}
private setName() {
this.name = "test";
}
ngAfterViewInit() {
this.setName();
}
}
#Component({
selector: 'test'
})
export class Class2 extends Class1 {
ngAfterViewInit() {
super.ngAfterViewInit();
console.log(this.name);
}
}
but I'm getting the following error in console when calling the setName() function:
EXCEPTION: TypeError: this.el is undefined
Why isn't this working?
Constructors are not inherited.
They are. Following sample shows this:
class Parent {
constructor(foo:number){}
}
class Child extends Parent {
}
const child1 = new Child(); // Error!
const child2 = new Child(123); // OKAY!
But this is angular
However they are not analyzed for dependency injection. This means that your child class constructor isn't called with the same parameters as expected by the parent (in your case `el). You need to specify all the DI elements on each child class. So by chance the correct code is the one from the accepted answer:
#Component({
selector: 'test'
})
export class Class2 extends Class1 {
constructor(el: ElementRef) {
super(el);
}
}
Constructors are not inherited. You need to define them in each subclass
#Component({
selector: 'test'
})
export class Class2 extends Class1 {
constructor(el: ElementRef) {
super(el);
}
ngAfterViewInit() {
super.ngAfterViewInit();
console.log(this.name);
}
}
Consider updating el's scope to protected, meaning it can be accessed by both the class where it's declared and any derived classes.
// before
constructor(private el: ElementRef) {}
// after
constructor(protected el: ElementRef) {}
Related
I need to pass one variable, that is inside my child component, to parent page.
This variable that I am trying to pass, is the array result of Barcode Scanner.
And I need to pass it to parent to send to API.
childComponent.ts
this.consultList;
parentComponent.ts
export class ParentComponent implements OnInit {
#Input() consultList: any[] = [];
testCall() {
console.log('Test Consult: ', this.consultList;
}
Here is an example stackblitz project to test parent-child data transfer, using #Input() and #Output()mechanism
import { Component, EventEmitter, Input, Output } from '#angular/core';
#Component({
selector: 'child',
template: `
<h1>Hello {{ name }}! This is child component</h1>
<button (click)="sendEventToParent()">Send data to parent</button>
`,
styles: [
`
h1 {
font-family: Lato;
}
`
]
})
export class ChildComponent {
#Input() name: string;
#Output() eventFromChild: EventEmitter<string> = new EventEmitter();
sendEventToParent(): void {
this.eventFromChild.emit('data from child');
}
}
here is the parent component html called child
<child name="{{ name }}" (eventFromChild)="onEvent($event)"></child>
<h1>This is parent component</h1>
<p>{{dataFromChild}}</p>
and event bindin like that
import { Component, VERSION } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
name = 'Angular ' + VERSION.major;
dataFromChild = '';
onEvent(event): void {
this.dataFromChild = event;
}
}
What you are thinking of is called an abstract class. An abstract class can define abstract properties just like an interface, abstract methods just like an interface, and unlike an interface it can actually implement methods. You cannot initialize an abstract class, but you can inherit code for re-use from it.
https://codesandbox.io/s/patient-breeze-h4s3t?file=/src/index.ts
abstract class Parent {
abstract someProperty: string;
someCall() {
console.log(this.someProperty);
}
}
class ChildOne extends Parent {
someProperty = "I am child one";
}
class ChildTwo extends Parent {
someProperty = "I am child two";
}
const one = new ChildOne();
const two = new ChildTwo();
one.someCall(); // "I am child one";
two.someCall(); // "I am child two";
I'm creating a reusable component which can be shown from any external component, but can be hidden using a function in same component, but somehow the property change in parent component is not updating child.
Here is the stackblitz for the same.
https://stackblitz.com/edit/angular-hfjkmu
I need "Show" button should show the component all the time and I can hide the component using "hide" button any time.
you need sync value from child to parent using Output
#Input()
show = false;
#Output()
showChange = new EventEmitter<boolean>();
constructor() { }
ngOnInit() {
}
hide(){
this.show = false;
this.showChange.emit(this.show);
}
<app-show-hide [(show)]="show"></app-show-hide>
The show property from child do not pointing to same prop in the parent comp, because it's primitive value.
I don't recommend to modify data that not belong to child component (reference type, eg: object, array), it can lead to unexpected behavior.
Online demo with reference type (be careful when modify ref type): https://stackblitz.com/edit/angular-vhxgpo?file=src%2Fapp%2Fshow-hide-obj%2Fshow-hide-obj.component.tsenter link description here
You have the problem because your child component modify Input value within your child component scope so no way parent component know the data is change
Your child component
export class ShowHideComponent implements OnInit {
#Input('show') show: boolean;
#Output() updateShowValue: EventEmitter<any> = new EventEmitter<
any
>();
constructor() { }
ngOnInit() {
console.log(this.show);
}
hide() {
this.updateShowValue.emit(!this.show);
}
}
In the app.component.html
<app-show-hide [show]="show" (updateShowValue)="update($event)"></app-show-hide>
And app.component.ts
export class AppComponent implements OnInit {
show:boolean = false;
ngOnInit() {
this.show = false;
console.log(this.show)
}
showComp(){
this.show = !this.show;
}
update(event) {
this.show = event;
}
}
You need to add an #Output in your child component, when you click the hide button (in the child component) you need to notify your parent component and change the value of show variable to false, this is done with the EventEmitter.
Changes to made are :
ShowHideComponent.ts
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-show-hide',
templateUrl: './show-hide.component.html'
})
export class ShowHideComponent {
#Input('show') show : boolean;
#Output('') hideEE = new EventEmitter();
constructor() { }
hide(){
this.hideEE.emit(false);
}
}
AppComponent.ts
import { Component,OnInit } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html'
})
export class AppComponent {
show:boolean = false;
}
appComponent.html
<button type="button" (click)="show = true">Show</button>
<app-show-hide [show]="show" (hideEE)="show = $event"></app-show-hide>
stackblitz Link
I have the following scenario in my Angular app:
A component MainDashboardComponent that is visible when I have the route /. Obviously I have the <router-outlet> tag in my app.component.html file, which looks like this:
<app-side-menu></app-side-menu>
<div class="main-container">
<div class="content">
<router-outlet></router-outlet>
</div>
</div>
As you can see I have a SideMenuComponent I use to have a side menu on all my routes. In MainDashboardComponent I have a method that for some reason needs to toggle a chat element that is situated on the side menu.
Inside the SideMenuComponent I have a method that handles the visibility toggle for the chat element and it works as expected. How can I call this method from my MainDashboardComponent and toggle the chat element from there?
What I tried with no success
I tried to inject the SideMenuComponent inside my MainDashboardComponent but, though the method toggleChat() is called, the element doesn't change it's visibility. Looks like I have a kind of multiple instance of the same component I guess...
Can you please help me with this? Thank you!
MainDashboardComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-main-dashboard',
templateUrl: './main-dashboard.component.html',
styleUrls: ['./main-dashboard.component.scss']
})
export class MainDashboardComponent implements OnInit {
constructor() { }
ngOnInit() {}
setFocus(id) {
// here I'd like to call SideMenuComponent togglechat() ...
}
}
SideMenuComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-side-menu',
templateUrl: './side-menu.component.html',
styleUrls: ['./side-menu.component.scss']
})
export class SideMenuComponent implements OnInit {
showChat: boolean;
constructor() {
this.showChat = false;
}
ngOnInit() {
}
toggleChat() {
this.showChat = !this.showChat;
}
}
To communicate between different components, there are different ways.
If you want to communicate between parent and child component, you can use EventEmitter to emit event from child component and handle the event in your parent component
If you want to communicate between any components, you can use Service and implement communication with the help of EventEmitter or Subject/BehaviorSubject
In your case, we can create a service, myService.ts and declare and eventEmitter
.service.ts
#Injectable()
export class AppCommonService {
toggle : EventEmitter<boolean> = new EventEmitter<boolean>()
}
mainDashboard.component.ts
constructor(private myService : myService){}
chatStatus : boolean = false;
ngOnInit(){
this.myService.toggle.subscribe(status=>this.chatStatus = status);
}
toggleChat(){
this.myService.toggle.emit(!this.chatStatus);
}
sideMenu.component.ts
constructor(private myService : myService){}
chatStatus : boolean = false;
ngOnInit(){
this.myService.toggle.subscribe(status=>this.chatStatus = status);
}
Generally this is the domain of a service!
Just create a service and add the "showCat" property.
Inject the service into both components
Alter SideMenuComponent to:
toggleChat() {
this.myService.showChat = !this.myService.showChat;
}
Alter MainDashboardComponent, also use this.myService.showChat to show / hide your chat window
Service TS
#Injectable()
export class MyService{
showCat:boolean = true
}
MainDashboardComponent
toggleChat() {
this.myService.showChat = !this.myService.showChat;
}
SideMenuComponent
chatVisiblity = this.myService.showCat //<-- bind this to the element attribute
You could efficiently use child to parent communication in this scenario. You'll need to create a custom event using angular's EventEmitter in your SideMenuComponent and use it in your MainDashboardComponent.
So, here is some code that may help you -
// SideMenuComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-side-menu',
templateUrl: './side-menu.component.html',
styleUrls: ['./side-menu.component.scss']
})
export class SideMenuComponent implements OnInit {
#Output() valueChange = new EventEmitter();
showChat: boolean;
constructor() {
this.showChat = false;
}
ngOnInit() {
}
toggleChat() {
this.showChat = !this.showChat;
this.valueChange.emit(this.showChat);
}
}
// MainDashboardComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-main-dashboard',
template: `<app-side-menu (valueChange)='setFocus($event)'></app-side-menu>`
styleUrls: ['./main-dashboard.component.scss']
})
export class MainDashboardComponent implements OnInit {
constructor() { }
ngOnInit() { }
setFocus(event) {
// check for required input value
console.log(event);
}
}
Refer these tutorials if required -
https://dzone.com/articles/understanding-output-and-eventemitter-in-angular,
https://angular-2-training-book.rangle.io/handout/components/app_structure/responding_to_component_events.html
I'm implemeting dynamic form system in my current project using ANgular 2, and so far is going good but I found the following problem:
I have two components that represent a form control like for example:
#Component({
selector: 'app-text-area',
templateUrl: './text-area.component.html',
styleUrls: ['./text-area.component.css']
})
export class TextAreaComponent implements OnInit {
label: string;
formGroup: FormGroup;
formControlName: string;
constructor(private injector: Injector) { }
ngOnInit() {
this.label = this.injector.get('label');
this.formGroup = this.injector.get('formGroup');
this.formControlName = this.injector.get('formControlName');
}
}
And:
#Component({
selector: 'app-input-text',
templateUrl: './input-text.component.html',
styleUrls: ['./input-text.component.css']
})
export class InputTextComponent implements OnInit{
label: string;
formGroup: FormGroup;
formControlName: string;
constructor(private injector: Injector) { }
ngOnInit() {
this.label = this.injector.get('label');
this.formGroup = this.injector.get('formGroup');
this.formControlName = this.injector.get('formControlName');
}
}
As you see both are identical except for the templateUrl, which is displaying different html elements.
So I would like to refactor the code and to create an abstract component to provide the common attributes and logic, and make then, the child classes to inherit the base class (as I would do when using Java). So I have created this class:
export class AbstractFormControl implements OnInit {
label: string;
formGroup: FormGroup;
formControlName: string;
constructor(private injector: Injector) { }
ngOnInit() {
this.label = this.injector.get('label');
this.formGroup = this.injector.get('formGroup');
this.formControlName = this.injector.get('formControlName');
}
}
And I have make the child classes extend the base class like this:
#Component({
selector: 'app-input-text',
templateUrl: './input-text.component.html',
styleUrls: ['./input-text.component.css']
})
export class InputTextComponent extends AbstractFormControl{
}
However now I'm getting the following error:
Uncaught Error: Can't resolve all parameters for InputTextComponent: (?).
Can someone explain me what's the right way to do this, or what I'm doing wrong?
Angular dependency injection system should know which type has been passed to constructor. When you inherit component such way typescript won't keep information about parameter private injector. You have two options:
1) Duplicate initializing
#Component({
...
})
export class InputTextComponent extends AbstractFormControl{
constructor(injector: Injector) { super(injector);}
}
But in your case you have the same number of parameters in your base and inherited classes and this solution seems redundant because we can omit constructor in our derived class.
We can omit constructor in derived class if we want to only use dependencies from parent class.
So let's say we have parent class like:
abstract class Parent {
constructor(private a: string, private b: number) {}
}
we can extend this class either
class Foo extends Parent {
constructor(a: string, b: number) {
super(a, b);
}
}
or
class Foo extends Parent {
}
because the second option will generate code like
function Foo() {
return _super !== null && _super.apply(this, arguments) || this;
}
Plunker Example
2) Use #Injectable for base class.
#Injectable()
export class AbstractFormControl {
this way typescript will translate the code above into
AbstractFormControl = __decorate([
core_1.Injectable(),
__metadata("design:paramtypes", [core_1.Injector])
], AbstractFormControl);
Plunker Example
and angular reflector can easily read this information
3) Use #Inject() for each of parameters
export class AbstractFormControl implements OnInit {
constructor(#Inject(Injector) private injector: Injector) { }
I have a component in angular 4 that is called three times. In template metadata I have a div with a directive with some bindings like this.
#import {gServ} from '../gServ.service';
#Component: ({
selector: 'sr-comp',
template: `<div gDirective [cOptions]="dataChart">`
})
export class SGComponent implements OnInit {
#Input('report') public report: IReportInstance;
cOptions:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.cOptions = {};
this.cOptions = this.gServ.objectMerge(this.gServ.defaultOpt, this.report.opt);
//this.report.opt is binded to a component when is instantiated.
//this.gServ.objectMerge is a function that merge the two objects
}
}
this.cOptions change for every instance of the component, then in the directive I have this:
import { Directive, ElementRef, HostListener, Input, OnInit } from '#angular/core';
#Directive({
selector: '[gDirective]'
})
export class SGDirective implements OnInit {
public _element: any;
#Input() public cOptions: string;
constructor(public element: ElementRef) {
this._element = this.element.nativeElement;
}
ngOnInit() {
console.log(this.cOptions);
}
}
The problem is that console.log(this.cOptions); always print the same object, even when component set cOptions with diferent values in ngOnInit method of the compnent.
Do you have some idea what is wrong?
Your component property binding [cOptions]="dataChart" doesn't look good, reason being your dataChart is not even defined. it should be like [DIRECTIVE_PROPERTY]="COMPONENT_PROPERTY" and your COMPONENT_PROPERTY is not even defined in SGComponent component class.
Your component class should be something like this:
#import {gServ} from '../gServ.service';
#Component: ({
selector: 'sr-comp',
template: `<div gDirective [cOptions]="Options">`
})
export class SGComponent implements OnInit {
#Input('report') public report: IReportInstance;
Options:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.Options = {};
this.Options = this.gServ.objectMerge(this.gServ.defaultOpt, this.report.opt);
}
}
#Ashwani points out a valid problem with your code. The way your template is wiring things up, nothing will ever be passed to the SGDirective input.
Another potential problem you could be running into has to do with the gServ code. If gServ is a singleton (which is probably the case) and it is returning the same object to each of the SGComponents, then all the SGDirectives will have the same value. A simple way to test this is to put {{Options | json}} in the SGComponent template.
To create a new instance of the gServ service for each SGComponent you can add a providers array to the #Component metadata. It would look like this:
import {gServ} from '../gServ.service';
#Component({
selector: 'sr-comp',
template: `{{Options | json}}<div gDirective [cOptions]="Options"></div>`
providers: [gServ],
})
export class SGComponent implements OnInit {
#Input('report') public report: IReportInstance;
Options:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.Options = this.gServ.objectMerge(this.gServ.defaultOpt, this.report.opt);
}
}
You have probably the same return/value at this.gServ.objectMerge) (you can test it wihtout calling the service, and passing each one one different objet make by you)
#import {gServ} from '../gServ.service';
#Component: ({
selector: 'sr-comp',
template: `<div gDirective [cOptions]="dataChart">`
})
export class SGComponent implements OnInit {
//#Input('report') public report: IReportInstance;
cOptions:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.cOptions = {nicolas: 'nicolas1'}; //change this in the next component that use the directive
}
}
If that is the case, your problem is that gServ is provide at the same rootComponent. with angular, service provider at the same rootComponent are singleton.
And use the same type in your directive and your component!!