I would like to pass a string parameter to my component. Depending on passing parameter i will pass different parameters for services in my component. I do next: In index.html call my component, passing parameter.
<top [mode]="tree">Loading...</top>
In my component i include Input from angular2/core
import {Input, Component, OnInit} from 'angular2/core';
In my component`s class i declare an input
#Input() mode: string;
And with console.log() i try to catch my passing parameter of 'tree', but it`s undefined.
console.log(this, this.mode);
The full code of a component file:
import {Http, HTTP_PROVIDERS} from 'angular2/http';
import {Input, Component, OnInit} from 'angular2/core';
import {ParticipantService} from '../services/participant.service';
import {orderBy} from '../pipes/orderby.pipe';
#Component({
selector: 'top',
templateUrl: 'dev/templates/top.html',
pipes: [orderBy],
providers: [HTTP_PROVIDERS, ParticipantService]
})
export class AppTopComponent implements OnInit {
constructor (private _participantService: ParticipantService) {}
errorMessage: string;
participants: any[];
#Input() mode: string;
ngOnInit() {
console.log(this, this.mode);
this.getParticipants('top3');
var self = this;
setInterval(function() {
self.getParticipants('top3');
}, 3000);
}
getParticipants(public mode: string) {
this._participantService.getParticipants(mode)
.then(
participants => this.participants = participants,
error => this.errorMessage = <any>error
);
}
}
When you use [...], the value you provide corresponds to an expression that can be evaluated.
So tree must be something that exists in the parent component and correspond to a string.
If you want to use the string tree, use this:
<top mode="tree">Loading...</top>
You can notice that such parameters can't be used for root component. See this question for more details:
Angular 2 input parameters on root directive
As a workaround for the limitation Thierry explained you can use
constructor(private _participantService: ParticipantService,
elRef:ElementRef) {
this.mode=elRef.nativeElement.getAttribute('mode');
}
you need to wait until template is bound to DOM. in order to do this, you have to implement AfterViewInit
export class AppTopComponent implements AfterViewInit{
public ngAfterViewInit() {
console.log(this, this.mode);
this.getParticipants('top3');
var self = this;
setInterval(function() {
self.getParticipants('top3');
}, 3000);
}
}
Related
Suppose you have a parent component A and inside of it you have some variable x. You would like to pass this variable to the child component B. Easy! Just use #Input annotation and call it a day. But what if B has another child component C? How would we pass x from A to C? I tried using the same approach to pass it from B to C, but it only passes the value undefined.
You can use a common service file which is data.service.ts file in this case. This service will be injected by both the parent and grand child. When component A which is grand parent here want to send a data it will call the deliverMsg method of the data service file. The component C which is grand child will listen to this change by injecting the same data.service
data.service.ts
// relevant imports
#Injectable()
export class DataService {
private message = new BehaviorSubject('default message');
portMessage = this.message.asObservable();
constructor() { }
deliverMsg(message: string) {
this.message.next(message)
}
}
parent.component.ts
//all relevant imports
#Component({
selector: 'app-parent-a',
template: 'html file url',
styleUrls: ['./sibling.component.css']
})
export class ParentComponent implements OnInit {
message:string;
constructor(private data: DataService) { }
ngOnInit() {
}
newMessage() {
this.data.deliverMsg("Hello from Grand Parent")
}
}
grandchild.component.ts
// all relevant imports
#Component({
selector: 'app-sibling',
template: 'template',
styleUrls: ['./sibling.component.css']
})
export class SiblingComponent implements OnInit {
message:string;
constructor(private data: DataService) { }
ngOnInit() {
this.data.portMessage.subscribe(message => this.message = message)
}
}
Alternatively you can also you NgRx
I have a child TestComponent component as follows:
import { Component, OnInit, Input } from '#angular/core';
import { ApiService } from '../../../api.service';
#Component({
selector: 'app-test',
templateUrl: './test.component.html'
})
export class TestComponent implements OnInit {
constructor(private apiService: ApiService) { }
testDisplayMessage = 'No data to show';
ngOnInit() {
}
getMessage(param: string) {
this.callingTest = true;
this.apiService.getTest( param ).subscribe( data => {
this.setTestDisplayMessage( data );
this.callingTest = false;
}, err => {
console.log( JSON.stringify( err ) );
this.setTestDisplayMessage( 'Failed to get data' );
this.callingTest = false;
} );
}
setTestDisplayMessage( message: string ) {
this.testDisplayMessage = message;
}
}
contents of test.component.html
<p style="padding: 10px;">{{ testDisplayMessage }}</p>
Use in parent componet :
Trigger JS Code in parent component on button click,
import { TestComponent } from './test/test.component';
....
.....
#Component({
providers: [ TestComponent ],
templateUrl: 'parent.component.html'
})
export class ParentComponent implements OnInit {
...
constructor(private testComponent: TestComponent) { }
...
// Button on parent template triggers this method
getMessage() {
this.testComponent.getMessage('Hello');
}
...
}
Html tag added in parent component,
<app-test></app-test>
When I debugged above code trigger point, call to setTestDisplayMessage() happens the field testDisplayMessage in TestComponent gets changed but UI shows the old message 'No data to show', why is the message on change does not reflect on UI template? Or this is not the way it is supposed to get used? Shall I use #Input
Update:
Based on the pointers given in the following answers as well as comment sections, I changed my component as #ViewChild so in above parent component instead of passing the child component as an argument to constructor I declared it as child component using #ViewChild, so code changes as follows,
Earlier wrong code
constructor(private testComponent: TestComponent) { }
Solution
#ViewChild(TestComponent)
testComponent: TestComponent;
I found this article useful.
Use #ViewChild()
In html file:
<app-test #childComp></app-test>
In parent component.ts file
import { Component, OnInit, ViewChild } from '#angular/core';
....
.....
#Component( {
templateUrl: 'parent.component.html'
} )
export class ParentComponent implements OnInit {
#viewChild('childComp') childComp: any;
constructor() { }
...
// Button on parent template triggers this method
getMessage() {
this.childComp.getMessage('Hello');
}
...
}
Update:
Based on the pointers given in the following answers as well as comment sections, I changed my component as #ViewChild so in above parent component instead of passing the child component as an argument to constructor I declared it as child component using #ViewChild, so code changes as follows,
Earlier wrong code
constructor(private testComponent: TestComponent) { }
Solution
#ViewChild(TestComponent)
testComponent: TestComponent;
I found this article useful.
definitely use #Input() but on set method
#Input()
set someProperty(value) {
// do some code
}
now every time you pass new value here, code will run
basically, your approach is wrong, please use Input() or Services to share data between components.
however, if you want to make ur code work, the below may work
import change detector
constructor(private cdRef: ChangeDetectorRef) {
}
note: import reference ->
import { ChangeDetectorRef } from '#angular/core';
execute detect change after the value is updated
setTestDisplayMessage( message: string ) {
this.testDisplayMessage = message;
this.cdRef.detectChanges();
}
I hope this helps
Here's my code:
import { Component, OnInit, ViewChild } from '#angular/core';
import { AuthService } from '../core/auth.service';
import { MatRadioButton, MatPaginator, MatSort, MatTableDataSource } from '#angular/material';
import { SelectionModel } from '#angular/cdk/collections';
import { OrdersService } from '../orders.service';
export interface DataTableItem {
ordersn: string;
order_status: string;
update_time: number;
}
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
radioValue: number;
dataSource = new UserDataSource(this.orderService);
selection = new SelectionModel<any>(true, []);
// Sorting and pagination
#ViewChild(MatSort) sort: MatSort;
#ViewChild(MatPaginator) paginator: MatPaginator;
// Columns displayed in the table. Columns IDs can be added, removed, or reordered.
displayedColumns = ['ordersn', 'order_status', 'update_time'];
// Filter
applyFilter(filterValue: string) {
this.dataSource.filter = filterValue.trim().toLowerCase();
}
// Whether the number of selected elements matches the total number of rows.
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
// Selects all rows if they are not all selected; otherwise clear selection.
masterToggle() {
this.isAllSelected() ?
this.selection.clear() :
this.dataSource.data.forEach(row => this.selection.select(row));
}
constructor(public auth: AuthService, private orderService: OrdersService) {
}
onSelectionChange(radioSelection: MatRadioButton) {
this.radioValue = radioSelection.value;
console.log(this.radioValue);
}
ngOnInit() {
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
}
}
export class UserDataSource extends MatTableDataSource<any> {
constructor(private orderService: OrdersService) {
super();
this.orderService.GetOrdersList().subscribe(d => {
this.data = d.orders;
});
}
radioFilter() {
const array = [];
this.orderService.GetOrdersList().subscribe(d => {
for (const entry of d.orders) {
if (entry.order_status === 'READY_TO_SHIP') {
array.push(entry);
}
}
this.data = array;
console.log(array);
});
}
}
I'm trying to call radioFilter() from HomeComponent. What I've tried:
Implementing #ViewChild in HomeComponent but I would get this error: Class 'UserDataSource' used before its declaration.
Importing UserDataSource and then added to the constructor in HomeComponent. I would get this error: Getting Uncaught Error: Can't resolve all parameters for HomeComponent
I'm kind of out of anymore idea, thus any suggestion is much appreciated. Thanks!
Getting Uncaught Error: Can't resolve all parameters for HomeComponent
First of all your dataSource is not registered in a ngModule as injectable.
So it's not possible to inject it to the constructor in HomeComponent.
I also don't think you want to do that because ngMaterial-dataSources are stateful and injectables shouldn't be stateful.
Class 'UserDataSource' used before its declaration
Your dataSource is not a ViewChild in your component's template. It's just an object (without a html-template). The error you get is that annotations in typeScript are processed on compile/transpile time. But the UserDataSource class is declared below the HomeComponent. You are using it before it's declared. You could just put it above the HomeComponent but better put it in a new file and import it. But that's not the solution.
Possible solution
I don't get why you cannot just call the radioFilter method.
It's a public method of your UserDataSource and there is an instantiated object in HomeComponent called dataSource. Just make sure to not call it in the constructor. Member variables are processed after the constructor is called. But imho you can just call dataSource.radioFilter()
I am getting many errors at the dev tools console when adding a service into my component but the code still working but I want to get rid of from these errors
This's the service:
getPagesData(pageSlug: string): Observable<any[]> {
return this._http.get<any[]>(`${environment.apiUrl}wp/v2/pages/?slug=${pageSlug}`);
}
This is the component:
import { Component, OnInit } from '#angular/core';
import { DataService } from 'src/app/services/data.service';
#Component({
selector: 'app-membership',
templateUrl: './membership.page.html',
styleUrls: ['./membership.page.scss'],
})
export class MembershipPage implements OnInit {
public pageContent: any = {};
public content: string;
constructor(
private _data: DataService
) { }
ngOnInit() {
this._data.getPagesData('memberships')
.subscribe(
page => this.pageContent = page[0]
)
}
getContent(): string {
return this.pageContent.content.rendered.replace(/\[(.+?)\]/g, "");
}
}
What cause the errors is the getContent() method! it says that is the .rendered is an undefined property but it doses defined on the API!
I have searched on that problem and most of the solutions I found it's about using the symbol ? at HTML template but I can't use that in the component itself.
If you are calling getContent() in the HTML/template, you can most likely avoid this error by either:
Making pageContent initially null and using *ngIf to only display the content once it has asynchronously resolved:
Component:
public pageContent: any = null;
Template:
<div *ngIf="pageContent">{{getContent()}}</div>
Or you could instead RxJS operators such as map() and the async pipe:
Component:
import { Component, OnInit } from '#angular/core';
import { DataService } from 'src/app/services/data.service';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
#Component({
selector: 'app-membership',
templateUrl: './membership.page.html',
styleUrls: ['./membership.page.scss'],
})
export class MembershipPage implements OnInit {
public pageContent: Observable<string>;
public content: string;
constructor(private _data: DataService) { }
ngOnInit() {
this.pageContent = this._data.getPagesData('memberships')
.pipe(
map(page => page[0].content.rendered.replace(/\[(.+?)\]/g, ""))
);
}
}
Template:
<div>{{pageContent | async}}</div>
That being said, you should probably have additional checks to ensure each sub-property is available prior to accessing it, but usually this type of error is because you are attempting to access the contents before they have resolved.
Hopefully that helps!
Yes, you cannot use ? Elvis (Safe navigation) operator in the component itself because it is designed for view part only.
But you can add some check in the component too to avoid such errors like -
getContent(): string {
const dataToReturn = this.pageContent && this.pageContent.content && this.pageContent.content.rendered.replace(/\[(.+?)\]/g, "");
return dataToReturn
}
.rendered is an undefined property
Also, This error may produce you have defined pageContent = {} so on {} neither content nor rendered exist , may be that is also the reason to exist such errors.
Angular recommend to strongly typecast your data before use.
So, What I am trying to do seems like it would be trivial. And it probably is. But I can't figure it out. My question is:How can I pass a variable from #Input to a service in an Angular2 component? (Code has been simplified)
My component is as follows:
import { Component, Input } from '#angular/core';
import { CMSService } from '../cms.service';
#Component({
selector: 'cmstext',
templateUrl: './cmstext.component.html',
styleUrls: ['./cmstext.component.css']
})
export class CMSTextComponent {
constructor(private cms: CMSService) { }
#Input() id : string;
content = this.cms.getContent(this.id); // this.id is NULL so content is NULL
}
And then my service:
import { Injectable } from '#angular/core';
#Injectable()
export class CMSService {
constructor() { }
getContent(textId:string) : string {
this.text = textId; // textId is NULL so this.text returns NULL
return this.text;
}
}
My component template:
<p>id: {{id}}</p>
<p>Content: {{content}}</p>
When <cmstext id="4"></cmstext> is added to another component template the output is:
id: 4
content:
I'm just diving into Angular2 any help or suggestions would be greatly appreciated!
Just make it a setter and put the code there:
#Input()
set id(value : string) {
this.content = this.cms.getContent(value);
}
As pointed out by #Kris Hollenbeck,ngOnInit() was the answer. My final code looked like this. The component now passed the variable to the service.
import { Component, Input, OnInit } from '#angular/core';
import { CMSService } from '../cms.service';
#Component({
selector: 'cmstext',
templateUrl: './cmstext.component.html',
styleUrls: ['./cmstext.component.css']
})
export class CMSTextComponent implements OnInit {
public content : string;
#Input() id : string;
constructor(private cms: CMSService) { }
ngOnInit() {
this.content = this.cms.getContent(this.id);
}
}
This assigned the data from the service to the variable "content" and the id passed from the element attribute to the variable "id". Both variables were then accessible to the template!