78

I have a variable in the parent component that might be changed by child, parent will be using this variable in the view and thus has to propagate changes.

import {Component, View} from 'angular2/core';

@Component({selector: 'parent'})
@View({
    directives: [Child],
    template: `<childcomp></childcomp>`
})
class Parent {
    public sharedList = new Array();
    constructor() {
    }
}


@Component({selector: 'child'})
@View({template: `...`})
class Child {
    constructor() {
        //access 'sharedList' from parent and set values
        sharedList.push("1");
        sharedList.push("2");
        sharedList.push("3");
        sharedList.push("4");
    }
}
Sangwin Gawande
  • 7,658
  • 8
  • 48
  • 66
Khaled
  • 8,255
  • 11
  • 35
  • 56
  • 12
    I wonder if we should start marking Angular2 "alpha" solutions differently so people starting with it now don't get confused. This is obviously dated with '@View'. Ideas? – Todd W Crone Feb 08 '16 at 15:51
  • 1
    Makes much sense to me. I have myself been confused looking at answers on SO and finally realizing its a lot way back – Saurabh Tiwari Jun 12 '17 at 07:27

5 Answers5

76

If you use input property databinding with a JavaScript reference type (e.g., Object, Array, Date, etc.), then the parent and child will both have a reference to the same/one object. Any changes you make to the shared object will be visible to both parent and child.

In the parent's template:

<child [aList]="sharedList"></child>

In the child:

@Input() aList;
...
updateList() {
    this.aList.push('child');
}

If you want to add items to the list upon construction of the child, use the ngOnInit() hook (not the constructor(), since the data-bound properties aren't initialized at that point):

ngOnInit() {
    this.aList.push('child1')
}

This Plunker shows a working example, with buttons in the parent and child component that both modify the shared list.

Note, in the child you must not reassign the reference. E.g., don't do this in the child: this.aList = someNewArray; If you do that, then the parent and child components will each have references to two different arrays.

If you want to share a primitive type (i.e., string, number, boolean), you could put it into an array or an object (i.e., put it inside a reference type), or you could emit() an event from the child whenever the primitive value changes (i.e., have the parent listen for a custom event, and the child would have an EventEmitter output property. See @kit's answer for more info.)

Update 2015/12/22: the heavy-loader example in the Structural Directives guides uses the technique I presented above. The main/parent component has a logs array property that is bound to the child components. The child components push() onto that array, and the parent component displays the array.

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • I didn't know that approach, however it's not working for eg. strings. @mark-rajcok could you check my [plunker](http://plnkr.co/edit/OiOzTkXfLOPSm06gKcuO?p=preview) – kit Dec 12 '15 at 12:13
  • 1
    @kit, your link seems to be a link to my plunker. But no matter...sharing a string between parent and child like this won't work (in the child -> parent direction) because the child has a local primitive type, rather than a local reference type. With a primitive type, both the parent and the child have their own copies (of the string) -- that's just how JavaScript works... we can't create references to primitive types. Although the parent will force new string values down to the child, the parent has no reference to the child's string property, so it won't notice any child changes. – Mark Rajcok Dec 12 '15 at 21:27
  • To make it clear: If I want to share a string variable between a child and parent component using @Input, I just use the wrapper of primitive string new String("myValue") and everything works fine. – Johannes Jul 20 '16 at 14:46
  • @user59442, using your approach, how would you change the string value in the child, and have it reflected in the parent? – Mark Rajcok Jul 20 '16 at 15:34
  • 1
    How does one share data between parent and child components when is used in the template HTML to specify the child component (page) based on the current route? – Vern Jensen Nov 23 '16 at 23:00
  • @VernJensen I think you are looking for [data binding with routing children](http://stackoverflow.com/questions/36158891/angular2-data-binding-with-routing-children). Basically you need shared services. – Franklin Yu Dec 09 '16 at 05:19
  • This should not work with OnPush because the reference is same. – bhantol May 18 '18 at 15:08
23

What about a little trickery like NgModel does with NgForm? You have to register your parent as a provider, then load your parent in the constructor of the child.

That way, you don't have to put [sharedList] on all your children.

// Parent.ts
export var parentProvider = {
    provide: Parent,
    useExisting: forwardRef(function () { return Parent; })
};

@Component({
    moduleId: module.id,
    selector: 'parent',
    template: '<div><ng-content></ng-content></div>',
    providers: [parentProvider]
})
export class Parent {
    @Input()
    public sharedList = [];
}

// Child.ts
@Component({
    moduleId: module.id,
    selector: 'child',
    template: '<div>child</div>'
})
export class Child {
    constructor(private parent: Parent) {
        parent.sharedList.push('Me.');
    }
}

Then your HTML

<parent [sharedList]="myArray">
    <child></child>
    <child></child>
</parent>

You can find more information on the subject in the Angular documentation: https://angular.io/guide/dependency-injection-in-action#find-a-parent-component-by-injection

jsgoupil
  • 3,788
  • 3
  • 38
  • 53
  • it this 'klunky' or does it fit inside the 'angular way of doing things?' I think I like it (especially if renaming ParentProvider to SharedListProvider and providing the list instead of the component). Of course maybe you want the whole component but I'm in exactly this situation and I have a child component that simply needs access to the parent's 'report' property – Simon_Weaver Nov 14 '16 at 18:53
  • I'm getting an error: Class 'Parent' used before its declaration. If to move the provider to another file then it says: WARNING in Circular dependency detected – Igor Mar 07 '19 at 15:12
13

You can do this In the parent component declare:

get self(): ParenComponentClass {
        return this;
    }

In the child component,after include the import of ParenComponentClass, declare:

private _parent: ParenComponentClass ;
@Input() set parent(value: ParenComponentClass ) {
    this._parent = value;
}

get parent(): ParenComponentClass {
    return this._parent;
}

Then in the template of the parent you can do

<childselector [parent]="self"></childselector>

Now from the child you can access public properties and methods of parent using

this.parent
FRL
  • 748
  • 7
  • 9
  • 1
    Though the accepted answer is the more angular way (and more streamlined) there may be those who are working on an unconventional app and want full access to the parent context. I would suggest this approach for that - especially if your parent component has a lot of member variables and functions you want to use. – babycakes Oct 24 '17 at 03:26
  • I understand that there would be situation where this approach might be need, but can this be classified as an anti-pattern or is this a common practice in Angular 2+? – Sharath Nov 09 '20 at 02:49
3

Basically you can't access variables from parent directly. You do this by events. Component's output property is responsible for this. I would suggest reading https://angular.io/docs/ts/latest/guide/template-syntax.html#input-and-output-properties

kit
  • 4,890
  • 3
  • 24
  • 23
  • 1
    Although it's not the official documentation, I also found this write-up useful: http://www.sitepoint.com/angular-2-components-inputs-outputs/ – user2428107 May 14 '16 at 00:37
  • Thanks! Just needed the simple reminder to go look at Output on the ng2 main site: https://angular.io/docs/ts/latest/api/core/index/Output-var.html - simple enough. – Methodician Aug 10 '16 at 17:04
  • 2
    I would like also leave this link here: https://angular.io/docs/ts/latest/cookbook/component-communication.html – sinedsem Aug 20 '16 at 19:08
  • do you have a full solution to this particular problem? it isn't immediately obvious how emitting an event is helpful here – Simon_Weaver Nov 14 '16 at 18:56
  • how about https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#child-to-parent – kit Nov 14 '16 at 19:03
3

The main article in the Angular2 documentation on this subject is :

https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-child

It covers the following:

  • Pass data from parent to child with input binding

  • Intercept input property changes with a setter

  • Intercept input property changes with ngOnChanges

  • Parent listens for child event

  • Parent interacts with child via a local variable

  • Parent calls a ViewChild

  • Parent and children communicate via a service

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689