I have a component like this:
import { Component, OnInit,Input } from '#angular/core';
#Component({
selector: 'topic-details',
templateUrl: './detailpane.component.html',
styleUrls: ['./detailpane.component.scss']
})
export class DetailpaneComponent implements OnInit {
#Input() topics;
myprop;
constructor() { }
assign(property,value){
property = value;
}
ngOnInit() {
}
}
And in my template:
<button (click)='assign(myprop,"some value")'>testing</button>
{{myprop}}
Take a look at the myprop property. In essence assign method has no effect! I was expecting myprop in the template to refer exactly same myprop in the component. I am beginnning to think that as soon as myprop goes out of the component inside the template, it becomes local to the template and doesn't reference the component (original) property.
Any explanation would be great!
You are assigning the value to wrong property, your property below, only exists in that function:
assign(property,value){
property = value;
}
but you want to assign it to myProp instead, which is also the variable you display in your view:
assign(property,value){
this.myProp = value;
}
Make notice of the keyword this
And if you actually have just one myProp (and not part e.g of iteration), you don't need to pass it to the function, just the new value:
<button (click)='assign("some value")'>testing</button>
assign(value){
this.myProp = value;
}
showPane is a primitive, thus is immutable.
This is more a javascript thing than an angular 2 issue.
you can either set it the template (my favorite)
(click)='showPane = !showPane'
or use an object
//in class
flagsCollection = {showPane:false}
toggle(flag){
this.flagsCollection[flag] = !this.flagsCollection[flag];
}
// in template
(click)='toggle("showPane")'
Your toggle method change locally the flag parameter. If you want to update showPane you could try this :
#Component({
selector: 'topic-details',
templateUrl: './detailpane.component.html',
styleUrls: ['./detailpane.component.scss']
})
export class DetailpaneComponent implements OnInit {
#Input() topics;
showPane = false;
constructor() { }
toggle(){
this.showPane = !this.showPane;
}
ngOnInit() {
}
}
<div class="col-md-12 list-group topics" [ngClass]="{'topics-small':showPane}" >
<a class="list-group-item cursor-pointer"
*ngFor='let topic of topics'
(click)='toggle()'
>{{topic.name}}</a>>
</div>
Related
I have created a component to reuse the mat-progress-spinner from angular material. I need this in order to avoid putting for every single page the same code. Here is the code that is working:
<div id="overlayProgressSpinner">
<div class="center">
<mat-progress-spinner
style="margin:0 auto;"
mode="indeterminate"
diameter="100"
*ngIf="loading">
</mat-progress-spinner>
</div>
</div>
It is simple. Only to set "loading" as true or false.
What did I do?
I put above code inside a custom component. Now it is like so:
<app-progress-spinner></app-progress-spinner>
its HTML code is the same and its TS code is as a follows:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-progress-spinner',
templateUrl: './progress-spinner.component.html',
styleUrls: ['./progress-spinner.component.scss']
})
export class ProgressSpinnerComponent implements OnInit {
loading = false;
constructor() { }
ngOnInit() {
}
public isLoading(value: boolean) {
this.loading = value;
}
public changeSpinnerCSSClass() {
const htmlDivElement = (window.document.getElementById('overlayProgressSpinner') as HTMLDivElement);
if (this.loading) {
htmlDivElement.className = 'overlay';
} else {
htmlDivElement.className = '';
}
}
}
when the property "loading" belongs to the current component, I can show and hide the "mat-progress-spinner" component. Otherwise, when it belongs to "app-progress-spinner" it is set but it is not being displayed. The code that I am trying to make it visible is as follows:
this.progressSpinner.isLoading(false); // it is set, but it does not work.
this.progressSpinner.changeSpinnerCSSClass(); // it works
it appears that *ngIf="loading" cannot be set by using the approach the works if the logic behind belongs to the current component.
How to achieve this?
You need to create an input in your ProgressSpinnerComponent. To do that, add the #Input() decorator before the property loading:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-progress-spinner',
templateUrl: './progress-spinner.component.html',
styleUrls: ['./progress-spinner.component.scss']
})
export class ProgressSpinnerComponent implements OnInit {
#Input() loading = false;
So anywhere you need to use the app-progress-spinner you do:
<app-progress-spinner [loading]="loading"></app-progress-spinner>
Note: The loading variable assigned to the input loading belongs to the component that contains theapp-progress-spinner.
This happens because every component have it own scope, meaning that it have no access to external world unless you create an input or output in order to receive or send data. There's also the ngModel that can be used for bi-diretional data, but not recommend in most cases.
I have an object from parent component also received in a child component similar to this:
{
attribute: 'aaaa',
attribute2: [
{
value
},
{
value
},
{
value
},
]
}
This object is an #Input from a parent component. When I make changes to the objects inside the attribute2 array, I would like the child component detect that changes were made and then gets updated. As this is an object, I could'nt make it work, so I clone the entire object (this.objet = _.cloneDeep(this.object) in the parent component so then the child component detects that changes happened.
Is there any other way of doing this that does not clone the entire object? Thanks in advance
EDIT:
Child Component
export class ChildComponent implements OnInit, OnChanges {
#Input() public object: any;
}
html
<div>
<span>{{object.attribute}}</span>
<div *ngFor="let items of object.attribute2">{{item.value}}</div>
</div>
Parent Component
export class ParentComponent implements OnInit {
public object: any;
updateObject() {
this.object.attribute2[1] = 'Changed value';
this.object = _.cloneDeep(this.object);
}
}
html
<div>
<child-component [object]="object"></child-component>
</div>
An efficient way is to use EventEmitter and service communication to
trigger changes in the child component.
On way as mentioned by #Tony is to use ngOnChanges(). It is a good shortcut for detecting bounded properties change but as you add more and more bindings, using this hook will affect you application in the long run because it will run every time any of the bound property changes whether or not you desire it all the calls.
So for Service based communication, I've created an example on
Stackblitz:
https://stackblitz.com/edit/angular-fgut7t
Gist: https://gist.github.com/stupidly-logical/a34e272156b498513505127967aec851
In this example, I am binding an Array to the child component using #Input() an on addition of new data, the array is updated by the parent and the latest value is passed on the service which then emits this value. The child component subscribes to this value and the relevant code is executed.
The Service:
import { Injectable, EventEmitter } from '#angular/core';
#Injectable({
providedIn: "root"
})
export class DataService {
dataUpdated:EventEmitter<any> = new EventEmitter();
constructor() { }
setLatestData(data) {
this.dataUpdated.emit(data);
}
}
Child Component TS
import { Component, OnInit, Input } from '#angular/core';
import { DataService } from '../data-service.service';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() allData: [];
latestData: any;
constructor(private dataService: DataService) { }
ngOnInit() {
this.dataService.dataUpdated.subscribe((data) => {
this.latestData = data;
});
}
}
Child Component HTML
<p>
Latest Data: {{ latestData }}
</p>
<h3>List:</h3>
<li *ngFor="let data of allData">
{{ data }}
</li>
Parent Component TS
import { Component } from '#angular/core';
import { DataService } from './data-service.service'
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
dataArr = [];
constructor(private dataService: DataService){}
onAddTimestamp() {
let timestamp = new Date();
this.dataArr.push(timestamp);
this.dataService.setLatestData(timestamp);
}
}
Parent Component HTML
<hello name="{{ name }}"></hello>
<p>
Start editing to see some magic happen :)
</p>
<button
(click)="onAddTimestamp()"
>
Add Timestamp
</button>
<app-child
[allData] = "dataArr"
></app-child>
Use the ngOnChanges() lifecycle method in your component.
ngOnChanges is called right after the data-bound properties have been
checked and before view and content children are checked if at least
one of them has changed.
Some like this
#Input() object: string;
ngOnChanges(changes: SimpleChanges) {
console.log(changes.object.currentValue);
// You can also use object.previousValue and
// object.firstChange for comparing old and new values
}
Whatever values are inside the individuals are printed without issues but whatever is obtained using #Input or #Output is not displayed.
child.component.ts
#Component({
selector: 'app-form-input',
templateUrl: './form-input.component.html',
styleUrls: ['./form-input.component.scss']
})
export class FormInputComponent implements OnInit {
#Input() fieldType: string;
//with another Input and 1 Output DOM
constructor(
) {
}
ngOnInit() {
console.log(this.fieldType);
}
}
parent.component.html
<app-form-input (value)="action($event)"
[fieldType]="date"
[maxAllowItem]="1">
</app-form-input>
Is there anything goes wrong in syntax?
The Log always show 'undefined' in all cases.
Thanks
I think this is trying to pull in a variable defined within your component.
Try the following syntax, wrap the string again, this should ensure you are passing a string and not a variable from the component, the input will then know to expect a string.
[fieldType]="'date'"
This is wrapping the string in " and '.
You may need to initialize the initial values of your #Input and #Output variables inside your component because #Input properties will be undefined unless they are provided from outside and #Output properties need to be initialized with EventEmitter
Also you need to check the values inside ngOnChanges which will be executed during Change Detection
Your code will be like this:
#Component({
selector: 'app-form-input',
templateUrl: './form-input.component.html',
styleUrls: ['./form-input.component.scss']
})
export class FormInputComponent implements OnInit {
#Input() fieldType: string;
#Output() event: EventEmitter<any>
//with another Input and 1 Output DOM
constructor() {
this.fieldType = ''
this.event = new EventEmitter()
}
ngOnInit() {
}
ngOnChanges() { // <- it will run every time and give you the latest value of fieldType
console.log(this.fieldType);
}
}
this is my case :
i have a Component :
#Component({
selector: 'home',
templateUrl: './administration.html',
directives: [Directivetest]
})
export class Administration {
Hello : string;
constructor(private http: Http) {
}
ngOnInit() {
//init stuff here
}
callMe(value) {
alert("call you" + this.Hello );
}
}
and my Template :
<h3>Test Administration </h3>
<directivetest></directivetest>
this is my directive :
#Component({
selector: 'directivetest',
templateUrl: './directivetest.html'
})
export class DirectiveTest{
klickme() {
//CALL A Administration Function (callMe(value)) or change the value????????????
}
}
Is there a way to change the variable or call a function from my "directive" component,
What You should do is :
First make a service ( just like component without any selector)
Put all data and related methods in that service.
Inject this service in both component.
If you just want to change a value and the directive is used inside the parent elements template just use binding or a mediator service to not tightly couple the directive to a parent component.
If you really need to directly access the parent component see this answer https://stackoverflow.com/a/31796289/217408
#Directive({
selector : '[layout-item]'
})
export class MyDirective {
constructor(private _element: ElementRef, private _viewManager: AppViewManager) {
let hostComponent = this._viewManager.getComponent(this._element);
// on hostComponent we have our component! (my-component, my-second-component, my-third-component, ... and so on!
}
}
As other said, you can either use directive or service to do something.
Here I'm showing you directive way which is irrelevant to your code.
By Günter Zöchbauer help, I could accomplished this problem Look here . This is a very simple example to start with angular2 directive.
I have a component and directive.
I use directive to update component's view. Moreover directive's changeColor function is getting called with a component's changeColor function.
Component
#Component({
selector: 'my-app',
host: {'[style.backgroundColor]':'color',}
template: `
<input type="text" [(ngModel)]="color" (blur)="changeColor(color)" />
<br>
<span > (span) I'm {{color}} color <span>
<div mySelectedColor [selectedColor]="color"> (div) I'm {{color}} color </div>
`,
directives: [selectedColorDirective]
})
export class AppComponent implements AfterViewInit{
#ViewChild(selectedColorDirective) myDirective: selectedColorDirective;
color:string;
constructor(el:ElementRef,renderer:Renderer) {
this.color="Yellow";
//renderer.setElementStyle(el, 'backgroundColor', this.color);
}
changeColor(color)
{
this.myDirective.changeColor(this.color);
}
ngAfterViewInit() {
}
}
Directive
#Directive({
selector:"[mySelectedColor]",
host: {
// '(keyup)': 'changeColor()',
// '[style.color]': 'selectedColor',
}
})
export class selectedColorDirective {
#Input() selectedColor: string = '';
constructor(el: ElementRef, renderer: Renderer) {
this.el=el;
this.el.nativeElement.style.backgroundColor = 'pink';
// renderer.setElementStyle(el, 'backgroundColor', this.selectedColor);
}
changeColor(clr)
{
console.log('changeColor called ' + clr);
//console.log(this.el.nativeElement);
this.el.nativeElement.style.backgroundColor = clr;
}
}
I am stuck on trying to pass a property value into my component. From what I've read everything looks correct. But it is still not working. My test value gets output to the screen and the console as null. :(
This is my test component:
import {Component, Input} from 'angular2/angular2';
#Component({
selector: 'TestCmp',
template: `Test Value : {{test}}`
})
export class TestCmp {
#Input() test: string;
constructor()
{
console.log('This if the value for user-id: ' + this.test);
}
}
This is how I am calling the component from the parent page.
<TestCmp [test]='Blue32'></TestCmp>
When the page render's the test value is empty. I only see 'Test Value :'.
Instead of 'Test Value : Blue32'.
You have four things that I can note :
You are passing an input in the root component, which will not work.
As #alexpods mentioned, you are using CamelCase. You should not.
You are passing an expression instead of an string through [test]. That means that angular2 is looking for a variable named Blue32 instead of passing a raw string.
You are using the constructor. That will not work, it must be after the view has been initialized data-bound properties have been initialized (see docs for OnInit).
So with a few fixes it should work
Example updated to beta 1
import {Component, Input} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
#Component({
selector : 'childcmp',
template: `Test Value : {{test}}`
})
class ChildCmp {
#Input() test: string;
ngOnInit() {
console.log('This if the value for user-id: ' + this.test);
}
}
#Component({
selector: 'testcmp',
template : `<childcmp [test]="'Blue32'"></childcmp>`
directives : [ChildCmp]
})
export class TestCmp {}
bootstrap(TestCmp);
See this plnkr as an example.
Update
I see that people still reach this answer, so I've updated the plnkr to beta 1 and I corrected one point on the explanation : You can access inputs in ngAfterViewInit, but you can access them earlier in the lifecycle within ngOnInit.
It's that easy as surrounding the string with double quotes, like this:
<TestCmp [test]="'Blue32'"></TestCmp>
If you use brackets [] the Angular uses property binding and expects to receive an expression inside the quotes and it looks for a property called 'Blue32' from your component class or a variable inside the template.
If you want to pass string as a value to the child component you can pass it like so:
<child-component childProperty='passing string'></child-component>
or if you for some reason want to have the brackets:
<child-component [childProperty]="'note double quotes'"></child-component>
And then take it in to child.component.ts like this:
import { Component, Input } from "#angular/core";
#Component({})
export class ChildComponent {
#Input()
childProperty: string;
}
This angular class could make the trick for static attributes:
ElementRef
https://angular.io/docs/ts/latest/api/core/index/ElementRef-class.html
import {ElementRef} from 'angular2/core'
constructor(elementRef: ElementRef) {
elementRef.nativeElement.getAttribute('api')
}
Sharing what worked for me:
Adding an input to the Angular 4 app
Assuming that we have 2 components:
parent-component
child-component
We wanted to pass some value from parent-component to child-component i.e. an #Input from parent-component.html to child-component.ts. Below is an example which explains the implementation:
parent-component.html looks like this:
<child-component [someInputValue]="someInputValue"></child-component>
parent-component.ts looks like this:
class ParentComponent {
someInputValue = 'Some Input Value';
}
child-component.html looks like this:
<p>Some Input Value {{someInputValue}}</p>
child-component.ts looks like this:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'child-component',
templateUrl: './child-component.html'
})
export class ChildComponent implements OnInit {
#Input() someInputValue: String = "Some default value";
#Input()
set setSomeInputValue(val) {
this.someInputValue += " modified";
}
constructor() {
console.log('someInputValue in constructor ************** ', this.someInputValue); //someInputValue in constructor ************** undefined
}
ngOnInit() {
console.log('someInputValue in ngOnInit ************** ', this.someInputValue); //someInputValue in ngOnInit ************** Some Input Value
}
}
Notice that the value of the #Input value is available inside ngOnInit() and not inside constructor().
Objects reference behaviour in Angular 2 / 4
In Javascript, objects are stored as references.
This exact behaviour can be re-produced with the help of Angular 2 / 4. Below is an example which explains the implementation:
parent-component.ts looks like this:
class ParentComponent {
someInputValue = {input: 'Some Input Value'};
}
parent-component.html looks like this:
{{someInputValue.input}}
child-component.html looks like this:
Some Input Value {{someInputValue}}
change input
child-component.ts looks like this:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'child-component',
templateUrl: './child-component.html'
})
export class ChildComponent implements OnInit {
#Input() someInputValue = {input:"Some default value"};
#Input()
set setSomeInputValue(val) {
this.someInputValue.input += " set from setter";
}
constructor() {
console.log('someInputValue in constructor ************** ', this.someInputValue); //someInputValue in constructor ************** undefined
}
ngOnInit() {
console.log('someInputValue in ngOnInit ************** ', this.someInputValue); //someInputValue in ngOnInit ************** Some Input Value
}
changeInput(){
this.someInputValue.input += " changed";
}
}
The function changeInput() will change the value of someInputValue inside both ChildComponent & ParentComponent because of their reference. Since, someInputValue is referenced from ParentComponent's someInputValue object - the change in ChildComponent's someInputValue object changes the value of ParentComponent's someInputValue object. THIS IS NOT CORRECT. The references shall never be changed.
I believe that the problem here might have to do with the page's life cycle. Because inside the constructor the value of this.test is null. But if I add a button to the template linked to a function that pushes the value to the console (same as I am doing in the constructor) this.test will actually have a value.
Maybe look like a hammer, but you can put the input wrapped on an object like this:
<TestCmp [test]='{color: 'Blue32'}'></TestCmp>
and change your class
class ChildCmp {
#Input() test: any;
ngOnInit() {
console.log('This if the value for user-id: ' + this.test);
}
}
you have to import input like this at top of child component
import { Directive, Component, OnInit, Input } from '#angular/core';
When you are making use of #Input for the angular interaction.It is always preferred approach to pass the data from parent to child in JSON object apparently it doesn't not restrict by #Angular Team to use local variable or static variable.
In context to access the value on child component make use ngOnInit(){} angular life hook cycle regardless of constructor.
That will help you out. Cheers.
When I had this issue there was actually just a compile error that I had to fix first (circular dependency needed resolved).