After reading this section about component interaction — I've noticed that there is another way of communicating from child to parent ( which wasn't really documented there) :
It turns out that if I have a parent class :
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}}</h2>
<my-item></my-item>
</div>
`,
})
export class App {
name:string;
go1()
{
alert(2)
}
constructor() {}
}
And in the child component - I Inject a parent type into the ctor :
#Component({
selector: 'my-item',
template: `
<div>
<input type="button" value='invoke parent method' (click) = "myGo()"/>
</div>
`,
})
class MyItem {
name:string;
private parent1 : App;
myGo()
{
this.parent1.go1() //<--- access is available here
}
constructor(private parent1 : App)
{
this.parent1 = parent1; //<------------ manually, unlike service
this.name = `Angular! v${VERSION.full}`
}
}
Then Angular sees that i'm trying to inject a parent type class and it gives me access to it.
The click works of course.
Question:
Beside other alternatives which I already know , is it documented anywhere ? or is it just a feature that I can't rely on it
plnkr
This works well and can be used.
A major downside is, that it breaks encapsulation.
The child component needs to know about the parent component.
This approach makes the child tightly coupled to it's parent, which is usually considered a bad thing because it prevents the component to be re-used anywhere within your application, because it now only works as a child of this exact parent.
Related
Right now, I have dead simple use case of the inheritance, but for the life of me, I cannot get it to work.
The use case
My use case is thus:
I have BaseComponent that has some LoaderComponent. It has methods showLoader() and hideLoader() for showing it and hiding it, respectively.
Every loadable component, or even every component, extends this BaseComponent. Then, when there is network request, or we need to wait for a lot of stuff to render, we can just showLoader(), and then hideLoader() when the work is done.
Implementation
My implementation of this is pretty straightforward, but for some obscure reason, it's not working.
base.component.ts
//...
export class BaseComponent implements AfterViewInit {
#ViewChild(LoaderComponent) loader: LoaderComponent;
constructor() {}
ngAfterViewInit() {
// caveman debugging, I know. Even worse is that I found myself placing breakpoint on this line of caveman debugging. SAD!
console.log(this.loader);
}
showLoader() {
this.loader.show();
}
hideLoader() {
this.loader.hide();
}
}
base.component.html
I use transclusion here, of course.
<ng-content> </ng-content>
<app-loader #loader></app-loader>
contact.component.ts
//...
#AutoUnsubscribe
export class ContactComponent extends BaseComponent implements OnInit {
constructor(
private senderService: SenderService,
private messageSentService: MessageSentService,
private router: Router
) {
super();
// setup logic here...
}
ngOnInit() {}
sendEmail() // or whatever...
{
this.showLoader();
// send the email
// do the request
this.emailSender = this.senderService.send(this.emailMessage);
this.emailSender.subscribe((res) => {
this.hideLoader();
// handling the request here....
}
//...
}
contact.component.html
<app-base>
<!-- the contact page -->
</app-base>
When I fire this up, what I see, in the developer console, is :
.
When I place my breakpoint on the caveman debugging in BaseComponent.prototype.ngAfterViewInit(), it hit twice. On the first instance, I get this.constructor.name === "BaseComponent". However, on the second one, is the derived class : this.constructor.name === "ContactComponent".
The state of the decorated view child field loader isn't being passed down!!
How do I fix this, and without resorting to some bullshit design like making the derived class has-a base class?
Problem
From What I understand ur question is you are not only using just component inheritance but template composition as well
contact.component.html
<app-base>
<!-- the contact page -->
</app-base>
Explanation
Thing is You can not use Parent Template while inherit from it. a component has only one template.
in contact.component.html <app-base> means an instance of base-component as part of ur template.
so ur child component template is composed of base not inherit from it .
bcs Child Component template has not loader it it so its null.
Either use compose or inheritance
Simple Solution
while using component inheritance you have to copy all ur base template to child template
contact.component.html
<app-loader></app-loader>
Advance Solution
We can achieve almost what u want with composite pattern
start with just an interface
export interface ILoader{
showLoading ():void
hideLoading ():void
}
create a app-loader wrapper that implement ILoader
#Component({
selector: 'app-loader-wrapper',
template: `<ng-content> </ng-content>
<app-loader></app-loader>`,
styleUrls: ['./loader-wrapper.component.css']
})
export class LoaderWrapperComponent implements ILoader {
#ViewChild(Loader) loader: Loader
showLoading (){
this.loader.showLoading()
}
hideLoading (){
this.loader.hideLoading()
}
}
Create a baseComponent Class it will be base of all ur component which need loading note it has no template
class BaseComponent implements ILoader{
#ViewChild(LoaderWrapperComponent) loader: LoaderWrapperComponent
showLoading (){
this.loader.showLoading();
}
hideLoading (){
this.loader.hideLoading();
}
}
now create components and inherit from it as use composition in template
#Component({
selector: 'my-app',
template: `<app-loader-wrapper>
// rest of ur component
</app-loader-wrapper>`,
styleUrls: [ './app.component.css' ]
})
export class AppComponent extends BaseComponent {
}
I'm passing a function as parameter from parent to child component. When click event is occurred, function of parent component trigger, but all the property of parent component is undefined. For example,
Parent Component
export class AppComponent implements OnInit {
constructor( private notificationService: NotificationService ) {}
unreadNotification(): Observable<any> {
// here this.notificationService is undefined
console.log( this.notificationService );
}
}
Parent html
<notification-menu [unread]= "unreadNotification"></notification-menu>
child Component
export class NotificationMenuComponent implements OnInit {
#Input() updateUnread: Function;
}
child html
<button type="button" class="icon-button" (click)="updateUnread()">
</button>
Now when I click on notification button, unreadNotification is triggered, but value of this.notificationService in console.log is undefined.
How can I solve this?
You should use #Input() to pass values from parent to child and #Output() to pass values from child to parent.
Child HTML:
<button type="button" class="icon-button" (click)="update()">
</button>
Child Component:
export class NotificationMenuComponent implements OnInit {
#Output() updateUnread = new EventEmitter<string>();
update() {
this.updateUnread.emit("I am working man!");
}
}
Parent HTML:
<notification-menu (updateUnread)= "unreadNotification($event)"></notification-menu>
Parent Component:
export class AppComponent implements OnInit {
constructor( private notificationService: NotificationService ) {}
unreadNotification(dataFromChild: string) {
console.log(dataFromChild);
}
}
The answer from #nimeresam is good advice - using an #Output is an idomatic way to achieve this.
It's worth noting though, that the reason that your original solution doesn't work is due to the way that javascript handles the this context.
Writing (click)="updateUnread()" is equivalent to saying this.updateUnread() with this being NotificationMenuComponent - as notificationService does not exist on NotificationMenuComponent you get the undefined error.
To have the context of the parent component used, you would need to bind the context to the updateUnread function before passing it into the child component.
This can be achieved either by converting the function to be an arrow functionn, or using Function.bind
See:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
It's normally a good idea to enable the typescript option for --noImplicitThis to help catch these errors (though unsure if it will detect it in this case)
You can use arrow function so that you can use parent component's information. You can try as like as given below.
updateUnreadNotification = () => {
// by using arrow function you can get notificationService information
console.log( this.notificationService );
}
Hope your problem will be solve by this.
I am tetsing a template driven form in angular, just testing not validating it.
I have read that it can be done using a viewChild property but it seems to not work for me.
I create a reference like this in my one of my forms label:
<label #ref id=.. class=...>
And now in my component I do this:
#ViewChild('ref') ref:ElementRef;
So, I suppose I created a valiable of type ElementRef that is viewChild of my input. So now I can use ref in my tests.
Inside my tests I do this:
let ref: HTMLElement:
it(....=>
{
ref = fixture.debugElement.query(By.css('ref')).nativeElement;
fixture.detectChanges();
expect(ref.innerHTML)toContain('Name');// or whatever
}
)
Now consider that the test, html and component files are separated from one another.
I still get errors of nativeElemnt property cannot be read. eventhough I have imported ElemntRef.
Is this the right way to access the DOM elemnts?? Or this viewChild doesnt make a referece to my label??
And again can I use the ID to access the form elements? What I have read they use a reference with #.
Thanks!!
For direct access to DOM in Angular you can make judicious use of ElementRef
However Direct access to DOM elements is not a good practice because
it leaves you vulnerable to XSS attacks.
Your AppComponent
import {Component, ElementRef} from '#angular/core';
#Component({
selector: 'my-app'
})
export class AppComponent implements ngOnInit {
constructor(private _elementRef : ElementRef) { }
ngOnInit(): void
{
this.ModifyDOMElement();
}
ModifyDOMElement() : void
{
//Do whatever you wish with the DOM element.
let domElement = this._elementRef.nativeElement.querySelector(`#someID`);
}
}
Your HTML
<p id="someID"></p>
While reading about #Input() and #Output() I have found that we can use an alias instead of using the property name for the decorators.
Example
class ProductImage {
//Aliased
#Input('myProduct') product: string;
//Un-aliased
#Input() product: string;//not aliased
}
HTML
//Aliased attribute
<SomeComponent [myProduct] = "Product Name"></SomeComponent>
//Un-aliased attribute
<SomeComponent [product] = "Product Name"></SomeComponent>
The official Angular documentation says:
Sometimes we want the public name of an input/output property to be different from the internal name.
This is frequently the case with attribute directives. Directive consumers expect to bind to the name of the directive. For example, when we apply a directive with a myClick selector to a tag, we expect to bind to an event property that is also called myClick.
And This tutorial briefly explains it:
an alias let's me override the property name to be the alias instead of the original property name
Other than that I have not been able to find anything else on aliasing #Input() and #Output() on SO or through Google.
Things I would like to know are:
What does 'aliasing' attempt to achieve?
Is 'aliasing' something that we should be using regularly?
It's like your name and your nickname.
Inside your family you might be called Nic, where as , outside your family in order for other to know you legally , you should be called Nicolas.
So, if we consider the class of your component , the inside of your family :
#Component({
selector:'my-component'
})
export class MyComponent{
#Output('nicolas') nic = new EventEmitter<any>();
someFunction(){
// because we're inside the family ( inside this class ), instead of nicolas , we can call nic like this :
this.nic ...... ( which you'd normally emit an event ).
}
}
But from outside, you're still nicolas , so when someone want's to call you ( use that event emitter ) it should use nicolas;
<my-component (nicolas)="onNicolasChanged($event)"></my-component>
Note that we can't do this :
<my-component (nic)="onNicolasChanged($event)"></my-component>
This is aliasing that variable.
Also , please read my answer here :
What does 'aliasing' attempt to achieve?
it simply renames the input/output variable as per your needs. For example, if there is a hero object and when it is selected it is passed to another component. In that case it would be appropriate to call it selectedHero . Aliasing simply achieves that.
#Input('selectedHero') hero: string;
Is 'aliasing' something that we should be using regularly?
Depends on the context you are working on. Its not a necessity. For example it can be used to avoid conflicts in variable names , or for readability of the code.
Actually #Input is used when we are sending data of a parent component to child component and #Output is vice-versa. Example is
parent.component.html
<p> Child first name is {{childfirstname}}</p>
<input placeholder="First Name" type="text" [(ngModel)]="firstname">
<button mat-button (click)="send()">Click Parent</button>
<app-child
[firstname]="firstname"
(sendfirstname)="reciveFirstname($event)"
*ngIf="flage">
<app-child>
ParentComponent.ts
import { Component } from '#angular/core';
#Component({
selector:.......
)}
export class ParentComponent {
flage: boolean = false;
firstname: string;
childfirstname: string;
}
send() {
this.flage = true;
}
reciveFirstname(firstname) {
this.childfirstname = firstname;
}
Child.component.html
<p> Parent first name is {{firstname}} <br>
parent last name is {{lastname}} </p>
<input placeholder="First Name" type="text" [(ngModel)]="firstnamechild">
<button mat-button (click)="send">Send Parent</button>
child.component.ts
import { Component,Input, Output, EventEmitter } from '#angular/core';
#Component({
selector:'app-child',
........
})
export class ChildComponent {
firstnamechild: string;
#Input() firstname: string;
#Output()
sendfirstname: EventEmitter<string> = new EventEmitter<string>();
constructor() {}
send() {
this.sendfirstname.emit(this.firstnamechild);
}
}
A good reason to use it is if you ever need to change the variable name in the component, you don't need to change it in your template because it's using the alias. It's a simple way to prevent your code from breaking if another dev ever changes that variable name.
Question
How to create child components inside a parent component and display them in the view afterwards using Angular2? How to make sure the injectables are injected correctly into the child components?
Example
import {Component, View, bootstrap} from 'angular2/angular2';
import {ChildComponent} from './ChildComponent';
#Component({
selector: 'parent'
})
#View({
template: `
<div>
<h1>the children:</h1>
<!-- ??? three child views shall be inserted here ??? -->
</div>`,
directives: [ChildComponent]
})
class ParentComponent {
children: ChildComponent[];
constructor() {
// when creating the children, their constructors
// shall still be called with the injectables.
// E.g. constructor(childName:string, additionalInjectable:SomeInjectable)
children.push(new ChildComponent("Child A"));
children.push(new ChildComponent("Child B"));
children.push(new ChildComponent("Child C"));
// How to create the components correctly?
}
}
bootstrap(ParentComponent);
Edit
I found the DynamicComponentLoader in the API docs preview. But I get the following error when following the example: There is no dynamic component directive at element 0
This is generally not the approach I would take. Instead I would rely on databinding against an array that will render out more child components as objects are added to the backing array. Essentially child components wrapped in an ng-for
I have an example here that is similar in that it renders a dynamic list of children. Not 100% the same, but seems like the concept is still the same:
http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0
Warning: DynamicComponentLoader has been deprecated in RC.4
In Angular 2.0, loadIntoLocation method of DynamicComponentLoader serve this purpose of creating parent-child relationship. By using this approach you can dynamically create relationship between two components.
Here is the sample code in which paper is my parent and bulletin is my child component.
paper.component.ts
import {Component,DynamicComponentLoader,ElementRef,Inject,OnInit} from 'angular2/core';
import { BulletinComponent } from './bulletin.component';
#Component({
selector: 'paper',
templateUrl: 'app/views/paper.html'
}
})
export class PaperComponent {
constructor(private dynamicComponentLoader:DynamicComponentLoader, private elementRef: ElementRef) {
}
ngOnInit(){
this.dynamicComponentLoader.loadIntoLocation(BulletinComponent, this.elementRef,'child');
}
}
bulletin.component.ts
import {Component} from 'angular2/core';
#Component({
selector: 'bulletin',
template: '<div>Hi!</div>'
}
})
export class BulletinComponent {}
paper.html
<div>
<div #child></div>
</div>
Few things you needs to be take care of are mentioned in this answer
You should use ComponentFactoryResolver and ViewElementRef to add component at runtime.Let's have a look at below code.
let factory = this.componentFactoryResolver.resolveComponentFactory(SpreadSheetComponent);
let res = this.viewContainerRef.createComponent(factory);
Put the above code inside your ngOnInit function and replace "SpreadSheetComponent" by your component name.
Hope this will work.
Programmatically add components to DOM in Angular 2/4 app
We need to use ngAfterContentInit() lifecycle method from AfterContentInit. It is called after the directive content has been fully initialized.
In the parent-component.html, add the a div like this:
<div #container> </div>
The parent-component.ts file looks like this:
class ParentComponent implements AfterContentInit {
#ViewChild("container", { read: ViewContainerRef }) divContainer
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
ngAfterContentInit() {
let childComponentFactory = this.componentFactoryResolver.resolveComponentFactory(childComponent);
this.divContainer.createComponent(childComponentFactory);
let childComponentRef = this.divContainer.createComponent(childComponentFactory);
childComponentRef.instance.someInputValue = "Assigned value";
}
}
Inside src\app\app.module.ts, add the following entry to the #NgModule() method parameters:
entryComponents:[
childComponent
],
Notice that we're not accessing the div#container using the #ViewChild("container") divContainer approach. We need it's reference instead of the nativeElement. We will access it as ViewContainerRef:
#ViewChild("container", {read: ViewContainerRef}) divContainer
The ViewContainerRef has a method called createComponent() which requires a component factory to be passed as a parameter. For the same, we need to inject a ComponentFactoryResolver. It has a method which basically loads a component.
The right approach depends on the situation you're trying to solve.
If the number of children is unknown then NgFor is the right approach.
If it is fixed, as you mentioned, 3 children, you can use the DynamicComponentLoader to load them manually.
The benefits of manual loading is better control over the elements and a reference to them within the Parent (which can also be gained using templating...)
If you need to populate the children with data, this can also be done via injection, the Parent is injected with a data object populating the children in place...
Again, a lot of options.
I have used 'DynamicComponentLoader' in my modal example, https://github.com/shlomiassaf/angular2-modal