I am trying to pass a simple string object from a parent component to a sub-child component. I have tried doing it the following way:
parent.ts
import {Component} from 'angular2/core';
import {Router,ROUTER_DIRECTIVES,ROUTER_PROVIDERS,RouteConfig} from 'angular2/router';
import {ChildCmp} from "./child";
import {bootstrap} from 'angular2/platform/browser';
#Component({
selector: 'app',
template:`
<router-outlet></router-outlet>
`,
directives: [ROUTER_DIRECTIVES]
})
export class ParentCmp{
public data = "Some data from parent.";
constructor (private _router:Router){
var config = [];
if(!this._router.registry.hasRoute("Child",ParentCmp))
config.push({path: "/child/...",component:ChildCmp,name: 'Child',useAsDefault:true, data: {"data": this.data}});
this._router.config(config);
}
}
bootstrap(ParentCmp,[
ROUTER_PROVIDERS
]);
child.ts
import {Component} from 'angular2/core';
import {RouteData,Router,ROUTER_DIRECTIVES,RouteConfig} from 'angular2/router';
import {SubChildCmp} from "./sub_child";
#Component({
selector: 'child',
template: `<router-outlet></router-outlet>`,
directives: [ROUTER_DIRECTIVES]
})
#RouteConfig([
])
export class ChildCmp{
public data:Object;
constructor(private _data:RouteData,private _router:Router){
this.data = this._data.get("data");
var config = [];
if(!this._router.registry.hasRoute("SubChild",ChildCmp))
config.push({path:"/sub_child",component: SubChildCmp,name:"SubChild", useAsDefault:true, data:{"data":this.data}});
this._router.config(config);
}
}
sub_child.ts
import {Component} from 'angular2/core';
import {RouteData} from 'angular2/router';
#Component({
selector: "sub-child",
template: `Data from parent is -->
{{data}}
`
})
export class SubChildCmp{
public data:Object;
constructor(private _data:RouteData){
this.data = this._data.get("data");
}
}
But I am getting a blank page. It looks like the routing configuration in child.ts is not being configured properly. How can I achieve this? I just want to pass some data from parent component to sub-child component. I re-produced the problem here on plunker
Usually a service is used for this use case
#Injectable
export class SharedData {
data;
}
#Component({
selector: 'app',
providers: [SharedData],
template:`
<router-outlet></router-outlet>
`,
directives: [ROUTER_DIRECTIVES]
})
export class ParentCmp{
public data = "Some data from parent.";
constructor (private _router:Router, private _sharedData:SharedData){
var config = [];
if(!this._router.registry.hasRoute("Child",ParentCmp))
_sharedData.data = this.data;
}
}
}
export class SubChildCmp{
public data:Object;
constructor(_sharedData:SharedData){
this.data = _sharedData.data;
}
}
Using Observable or BehaviorSubject with subscribe() might be necessary if there are timing issues, for example when SubChildCmp reads the value before the ParentCmp has set it.
For more details see https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service
Related
My project structure:
app.component.ts:
import { Component } from "#angular/core"
import { Todo } from './components/shared/todo.model'
import { todos } from "./components/shared/todo.data"
import {TodoService} from "./components/shared/todoService"
import {TodoService} from "./components/shared/todoService";
#Component({
moduleId: module.id,
selector: "app",
templateUrl: "app.component.html",
styleUrls: ['app.component.css'],
providers: [TodoService]
})
export class AppComponent {
title:string = "Angular 2Do";
}
todo-form.component.ts:
import {Component, Output, EventEmitter} from "#angular/core";
import {Todo} from "../shared/todo.model";
import {TodoService} from "../shared/todoService"
#Component({
moduleId: module.id,
selector: "todo-form",
templateUrl: "todo-form.component.html",
styleUrls: ["todo-form.component.css"],
})
export class TodoForm {
...
constructor(private todoService:TodoService) {
console.log(this.todoService);
this.todoService.order = 2;
console.log( this.todoService);
}
}
todo-list.component.ts:
import {Component, Input, OnInit} from "#angular/core"
import { ITodo } from "../shared/todo.model"
import { TodoService } from "../shared/todoService"
#Component({
moduleId: module.id,
selector: "todo-list",
templateUrl: "todo-list.component.html",
styleUrls: ["todo-list.component.css"],
})
export class TodoListComponent implements OnInit {
todos:ITodo[];
...
constructor(private todoService:TodoService) {
...
console.log(this.todoService);
this.todoService.order=1;
console.log(this.todoService);
}
...
}
app is the parent of the list and form components
Whaen I start application I see in console:
but if expand all I see:
Which result actual and why in second view I see 1 and in another 2.
The console.log '+' button can only show you the current state of the object, not the object at the snapshot in time of when it was called.
See console.log() async or sync? for a more in depth explanation.
So order: 1, is the final state of the object.
never use providers( providers: [TodoService] ) in component
as
import { Component } from "#angular/core"
import { Todo } from './components/shared/todo.model'
import { todos } from "./components/shared/todo.data"
import {TodoService} from "./components/shared/todoService"
import {TodoService} from "./components/shared/todoService";
#Component({
moduleId: module.id,
selector: "app",
templateUrl: "app.component.html",
styleUrls: ['app.component.css']
})
export class AppComponent {
title:string = "Angular 2Do";
}
it makes new instance when component initialise so put providers in module only ie. NgModule
Trying to grab the ID param from the URL, however it seems as though I am not defining my params var. Can't see where I am going wrong so maybe someone can help!
Code
import {Component} from 'angular2/core';
import {ROUTER_DIRECTIVES, RouteParams, RouteConfig} from 'angular2/router';
import {OnInit} from 'angular2/core';
#Component({
selector: 'view-movie',
templateUrl : './templates/view-movie.tpl.html',
directives: [ROUTER_DIRECTIVES]
})
export class ViewmovieComponent implements OnInit {
public movie = {};
public id = String;
constructor(params: RouteParams){};
ngOnInit() {
this.id = params.get('id');
}
}
Was suppose to be inserting the params.get in the code block following the constructor function like so
import {Component} from 'angular2/core';
import {ROUTER_DIRECTIVES, RouteParams, RouteConfig} from 'angular2/router';
import {OnInit} from 'angular2/core';
#Component({
selector: 'view-movie',
templateUrl : './templates/view-movie.tpl.html',
directives: [ROUTER_DIRECTIVES]
})
export class ViewmovieComponent implements OnInit {
public movie = {};
public id = String;
constructor(params: RouteParams){
this.id = params.get('id');
}
ngOnInit() {
console.log(this.id);
}
}
simply use either this one
constructor(params: RouteParams){this.id = params.get('id');}
ngOnInit() {console.log(this.id);}
or this one
constructor(private params: RouteParams){}
ngOnInit() {
this.id = this.params.get('id');
console.log(this.id);
}
because if we initialize something without declare type then we have to access within the constructor but if we are going to access outside of the constructor then it will be must to use this
I am trying to add component dynamically into a child component from the parent component. I have injected the child component so that I can call its method in order to add component into the child component. Here is how I am doing it:
test.ts
import {Component, DynamicComponentLoader,ElementRef,AfterViewInit} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {ChildComponent} from './child_test.ts';
#Component({
selector: 'app',
template: '<div #element></div>',
providers: [ChildComponent,ElementRef]
})
class AppComponent implements AfterViewInit{
constructor(private _loader: DynamicComponentLoader, private _elementRef: ElementRef, private _childComponent: ChildComponent) {
}
ngAfterViewInit(){
this._loader.loadIntoLocation(ChildComponent,this._elementRef,'element');
this._childComponent.addElement();
}
}
bootstrap(AppComponent);
child_test.ts
import {Component, ElementRef,DynamicComponentLoader} from 'angular2/core';
#Component({
selector: 'element',
template: '<div>Test Element</div>'
})
export class MyElement{}
#Component({
selector: "child-component",
template: "<div>Child Component</div>"
})
export class ChildComponent{
constructor(public _elementRef: ElementRef,private _loader: DynamicComponentLoader){}
public addElement(){
this._loader.loadNextToLocation(MyElement,this._elementRef);
}
};
When execute the test.ts I get the following error:
TypeError: Cannot read property 'getViewContainerRef' of undefined
Not sure if I fully understand your question but I think this is what you want
ngAfterViewInit(){
this._loader.loadIntoLocation(ChildComponent,this._elementRef,'element')
.then(childComponent => childComponent.instance.addElement());
}
this._loader.loadIntoLocation(...) returns a Promise that completes with a reference to the added element.
I just started off with Angular2 quick start project. Have a simple application working. I added DataService class, so that the code will have separation of concern.
Initially I've added the DataService class write after the my main component of app which is MyAppComponent like below.
import {Component, View} from 'angular2/core';
import {NgFor} from 'angular2/common';
import {bootstrap} from 'angular2/platform/browser';
#Component({
'selector': 'my-app',
template: `<div *ngFor="#item of items">{{item}}</div>`,
directives: [NgFor],
providers: [DataService] //taking service as injectable
})
export class MyAppComponent {
items: Array<number>;
constructor(service: DataService) {
this.items = service.getItems(); //retrieving list to bind on the UI.
}
}
//created service, but its after the component which has meta annotation
export class DataService {
items: Array<number>;
constructor() {
this.items = [1, 2, 3, 4];
}
getItems() {
return this.items; //return the items list
}
}
bootstrap(MyAppComponent)
Above code compiles correctly, but at run-time it throws below error.
EXCEPTION: Cannot resolve all parameters for
MyAppComponent(undefined). Make sure they all have valid type or
annotations.
After 2 hours playing with the code, I shifted the DataService just above the MyAppComponent which got worked. I'm really glad that issue solved.
But I'm very curious to know that, why it wasn't working if I placed DataService class right after the class with MetaAnnotation over it?
Edit
I tried solution give by #Günter Zöchbauer like below,
import {Component, View, Inject, forwardRef} from 'angular2/core';
import {NgFor} from 'angular2/common';
import {bootstrap} from 'angular2/platform/browser';
#Component({
'selector': 'my-app',
template: `<div *ngFor="#item of items">{{item}}</div>`,
directives: [NgFor],
providers: [DataService] //tried commenting this still throws error.
})
export class MyAppComponent {
items: Array<number>;
constructor(#Inject(forwardRef(() => DataService)) service: DataService) {
this.items = service.getItems();
}
}
but still getting error in console. which looks wiered
EXCEPTION: TypeError: Cannot read property 'toString' of undefined
JavaScript doesn't hoist classes. Either use forwardRef, move DataService to it's own file or move DataService class above MyAppComponent
#Component({
'selector': 'my-app',
template: `<div *ngFor="#item of items">{{item}}</div>`,
directives: [NgFor],
providers: [forwardRef(() => DataService)] //taking service as injectable
})
export class MyAppComponent {
items: Array<number>;
constructor(#Inject(forwardRef(() => DataService)) service: DataService) {
this.items = service.getItems(); //retrieving list to bind on the UI.
}
}
See also
- Angular 2 error:
- http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html
How to create object which can be modified by multiple angular2 component.
Eg.
PeopleService.ts
//a simple service
import {Injectable} from 'angular2/angular2'
#Injectable()
export class PeopleService {
constructor() {
this.people = [
{id: 1, name: 'Brad'},
{id: 2, name: 'Jules'},
{id: 3, name: 'Jeff'}
];
}
changeBrad(){
this.people[0].name = "Brad_Changed";
}
}
App.ts
import {Component, View} from 'angular2/angular2';
import {PeopleService} from './peopleService';
#Component({
selector: 'my-app',
bindings: [PeopleService]
})
#View({
template: '{{appval}}',
})
export class App{
appval:string;
constructor(public peopleService:PeopleService){
peopleService.changeBrad();
this.appval = peopleService.people[0].name; // result : Brad_Changed
}
}
Home.ts
import {Component, View} from 'angular2/angular2';
import {PeopleService} from './peopleService';
#Component({
selector: 'my-home',
bindings: [PeopleService]
})
#View({
template: '{{homeval}}',
})
export class Home{
homeval:string;
constructor(public peopleService:PeopleService){
this.homeval = peopleService.people[0].name; // result : Brad
}
}
In Home.ts result is " Brad" which is not modified by App.ts I want result to be "Brad_Changed" which was modified by App.ts. How do I write my PeopleService.ts and how to pass it by reference to other component in angular2?
Just remove PeopleService from Home component bindings. By adding this binding, you're creating new instance of PeopleService for Home component and all its children:
import {Component, View, Host} from 'angular2/angular2';
import {PeopleService} from './peopleService';
#Component({
selector: 'home',
// Adding this you're creating new instance of PeopleService for
// Home component and all its children
// bindings: [PeopleService]
})
#View({
template: '<p>{{homeval}}</p>',
})
export class Home{
homeval:string;
constructor(peopleService:PeopleService){
this.homeval = peopleService.people[0].name; // result : Brad
}
}
See this plunker