Angular2: when does property binding happen? - javascript

I'm just starting with Angular2, reading the official docs. However, I have not found specific details about how and when the binding happens, and things don't seem to work as I expected.
I have a simple child component
#Component({
selector: 'dummy',
template: `
<div>{{data}}</div>
`
})
export class Dummy {
#Input() data;
}
and a root component
#Component({
selector: 'main',
template: `
<h1>hello</h1>
<dummy [data]="data"></dummy>
`
})
export class MainComponent {
data: string = "initial text";
ngOnInit() {
setTimeout(this.initData, 5000);
}
initData() {
this.data = "new text";
}
}
I would expect the text shown by the child component to change after 5 seconds, however it doesn't. What am I doing wrong? Does the documentation explain when and under what conditions bound values are initialized and updated?

you're losing context of this. at the time when setTimeout callback runs, this doesn't point to the component anymore. you might want to check a bit about javascript this problem.
try:
setTimeout(()=>{
this.data = "new text";
},5000);

You forgot to import the OnInit interface and make your component implement it.
import { Component, OnInit } from '#angular/core';
And then
export class MainComponent implements OnInit { ... }

Related

Problem calling one Angular component from another component

At work, I have run into a problem using Angular. I have this kind of Angular component:
#Component({
selector: 'foo',
templateUrl: 'foo.html'
})
export class FooComponent {
#Input() data: string;
content: string;
ngOnInit() {
this.content = this.data;
}
setValue(data) {
this.content = data;
}
}
This is initialized from my main Angular component in a code block such as this:
this.components = [FooComponent, BarComponent, BazComponent, QuuxComponent];
Now this works so far. But if I try to call the setValue() function with this.components[0].setValue("Hello world!"); I get an error "this.components[0].setValue is not a function."
What is the reason for this and how can I fix it?
This seems like a very very weird way to work with components in angular.
You really don't want to break encapsulation by calling methods inside one component from another component.
I personally haven't seen this kind of component referencing anywhere (and have doubts it is a correct approach).
There is no reason to duplicate the data property in the content.
You can pass values in the template. Or use a service if you don't have direct access to the template.
Here is a very basic example on how to modify data from the parent using a template and #Input.
app.component.ts
import { Component } from "#angular/core";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
message = "I am a message from the parent";
}
app.component.html
<app-child [content]='message'></app-child>
child.component.ts
import { Component, OnInit, Input } from "#angular/core";
#Component({
selector: "app-child",
templateUrl: "./child.component.html",
styleUrls: ["./child.component.css"]
})
export class ChildComponent implements OnInit {
#Input("content") public content: string;
constructor() {}
ngOnInit() {}
}
child.component.html
<p>{{content}}</p>

Angular 2+: Child components ts variable changes but UI does not show changed value?

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

Emiting event from component to parent module cousing exception

My app.component.ts
import { Component, Input, OnInit, OnChanges, SimpleChanges} from '#angular/core';
import {Counter } from './counter'
#Component({
selector: 'my-app',
template: `
<custom-counter [(counter)]="counterArray" (counterChange)="myValueChange($event);"></custom-counter>
<p><code>counterValue = {{counterValue}}</code></p>
<hr>
`
})
export class AppComponent implements OnChanges{
counterArray:Counter[]
counterValue = 5;
constructor(){
this.counterArray=[{id:0,value:0},{id:1,value:1}]
}
myValueChange(event:Counter[]) {
console.log(event);
}
}
my counter.ts
export class Counter {
id: number;
value: number;
}
and custom-counter component:
import { Component, Input, Output, EventEmitter } from '#angular/core';
import { Counter } from './counter';
#Component({
selector: 'custom-counter',
template: `
First counter
<button (click)="decrement()">-</button>
<span>{{this.counter[1].value}}</span>
<button (click)="increment()">+</button>
`
})
export class CustomCounterComponent {
#Input() counter : Counter[];
#Output() counterChange = new EventEmitter();
decrement() {
this.counter[1].value--;
this.counterChange.emit({
value: this.counter
})
}
increment() {
this.counter[1].value++;
this.counterChange.emit({
value: this.counter
})
}
}
My plan was that if user presses button on from the child component parent is informed about it and print something in console.
unfortunately when user press button error below is thrown:
"Error in ./CustomCounterComponent class CustomCounterComponent - inline template:3:10 caused by: Cannot read property 'value' of undefined"
I know that this exception is quite strait forward but I can not find why something is undefined while I pass everything.
If I comment out lines with emit no error occur but then I do not have any notifications for parent
Problem was in in-proper calling the emit method.
Calling like this:
this.counterChange.emit({
value: this.counter
})
was creating new object that was emited and that someway messed object binding. ( If anyone can explain this better then please do it).
after changing call to this:
this.counterChange.emit(this.counter)
when I emit strait the input object everything started to be working.
In your CustomCounterComponent class the template has an issue.
Try the following in your template:
template: `
First counter
<button (click)="decrement()">-</button>
<span>{{counter[1]?.value}}</span>
<button (click)="increment()">+</button>
`
? is a safety operator that would not throw an exception when counter[1] is undefined
Also note this is not required with counter in the template
The following method expects an an Array whereas it receives an object.
myValueChange(event:Counter[]) {
console.log(event);
}

Making a reusable angular2 component that can be used anywhere on the a site

Use Case: When making asynchronous calls, I want to show some sort of a processing screen so that end users knows something is happening rather than just staring at the screen. Since I have multiple places throughout the site where I want to use this, I figured making it a component at the "global" level is the best approach.
Problem: Being slightly new to angular2, I'm not getting if this is a problem of it being outside the directory in which the main component exists and the OverlayComponent being in another location or if I'm just all together doing it wrong. I can get the component to work fine but I need to be able to call functions to hide/destroy the component and also display the component. I have tried making it a service but that didn't get me any further so I'm back to square one. Essentially my question revolves around building a reusable component that has methods to hide/show itself when invoked from whatever component it's being called from.
Below is my current code:
Assume OverlayComponent.html is at /public/app/templates/mysite.overlay.component.html
Assume OverlayComponent.ts is at /public/app/ts/app.mysite.overlay.component
Assume mysite.tracker.component is at \public\app\ts\pages\Tracker\mysite.tracker.component.ts
OverlayComponent.html
<div class="overlay-component-container">
<div class="overlay-component" (overlay)="onShowOverlay($event)">
<div>{{processingMessage}}</div>
<div>
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</div>
</div>
</div>
OverlayComponent.ts
import { Component } from '#angular/core';
#Component({
selector: 'overlay-component',
templateUrl: '/public/app/templates/mysite.overlay.component.html',
styleUrls: ['public/app/scss/overlay.css']
})
export class OverlayComponent {
onShowOverlay(e) {
$('.overlay-component').fadeIn(1000);
}
hideOverlay(e) {
$('.overlay-component').fadeOut(1000);
}
}
TrackerComponent.ts
import { Component, Output, OnInit, EventEmitter } from '#angular/core';
import { Http } from '#angular/http';
import { TrackerService } from './Tracker.service';
import { MenuCollection } from "./MenuCollection";
import { Menu } from "./Menu";
#Component({
moduleId: module.id,
selector: 'tracker-component',
templateUrl: '/public/app/templates/pages/tracker/mysite.tracker.component.html',
styleUrls: ['../../../scss/pages/racker/tracker.css'],
providers: [TrackerService]
})
export class TrackerComponent implements OnInit{
MenuCollection: MenuCollection;
#Output()
overlay: EventEmitter<any> = new EventEmitter();
constructor(private http: Http, private TrackerService: TrackerService) {
let c = confirm("test");
if (c) {
this.onShowOverlay();
}
}
ngOnInit(): void {
this.MenuCollection = new MenuCollection();
this.MenuCollection.activeMenu = new Menu('Active Menu', []);
this.TrackerService.getTrackerData().then(Tracker => {
this.MenuCollection = Tracker;
this.MenuCollection.activeMenu = this.MenuCollection.liveMenu;
console.log(this.MenuCollection);
},
error => {
alert('error');
})
}
onShowOverlay() { //This doesn't seem to 'emit' and trigger my overlay function
this.overlay.emit('test');
}
}
At a high level, all I'm wanting to do is invoke a components function from another component. Thanks in advance for any helpful input
You can use the #ContentChild annotation to accomplish this:
import { Component, ContentChild } from '#angular/core';
class ChildComponent {
// Implementation
}
// this component's template has an instance of ChildComponent
class ParentComponent {
#ContentChild(ChildComponent) child: ChildComponent;
ngAfterContentInit() {
// Do stuff with this.child
}
}
For more examples, check out the #ContentChildren documentation.

Bindings not working in dynamically loaded component

I'm encountering a problem where if I dynamically load a component, none of the bindings in the template are working for me. As well as this the ngOnInit method is never triggered.
loadView() {
this._dcl.loadAsRoot(Injected, null, this._injector).then(component => {
console.info('Component loaded');
})
}
Dynamically loaded component
import {Component, ElementRef, OnInit} from 'angular2/core'
declare var $:any
#Component({
selector: 'tester',
template: `
<h1>Dynamically loaded component</h1>
<span>{{title}}</span>
`
})
export class Injected implements OnInit {
public title:string = "Some text"
constructor(){}
ngOnInit() {
console.info('Injected onInit');
}
}
This is my first time using dynamically loaded components so I think may be attempting to implement it incorrectly.
Here's a plunkr demonstrating the issue. Any help would be appreciated.
As Eric Martinez pointed out this is a known bug related to the use of loadAsRoot. The suggested workaround is to use loadNextToLocation or loadIntoLocation.
For me this was problematic as the component I was trying to dynamically load was a modal dialog from inside a component with fixed css positioning. I also wanted the ability to load the modal from any component and have it injected into the same position in the DOM regardless of what component it was dynamically loaded from.
My solution was to use forwardRef to inject my root AppComponent into the component which wants to dynamically load my modal.
constructor (
.........
.........
private _dcl: DynamicComponentLoader,
private _injector: Injector,
#Inject(forwardRef(() => AppComponent)) appComponent) {
this.appComponent = appComponent;
}
In my AppComponent I have a method which returns the app's ElementRef
#Component({
selector: 'app',
template: `
<router-outlet></router-outlet>
<div #modalContainer></div>
`,
directives: [RouterOutlet]
})
export class AppComponent {
constructor(public el:ElementRef) {}
getElementRef():ElementRef {
return this.el;
}
}
Back in my other component (the one that I want to dynamically load the modal from) I can now call:
this._dcl.loadIntoLocation(ModalComponent, this.appComponent.getElementRef(), 'modalContainer').then(component => {
console.log('Component loaded')
})
Perhaps this will help others with similar problems.
No need to clean component instance from DOM.
use 'componentRef' from angular2/core package to create and dispose component instance.
use show() to load the modal component at desired location and hide() to dispose the component instance before calling loadIntoLocation secondtime.
for eg:
#Component({
selector: 'app',
template: `
<router-outlet></router-outlet>
<div #modalContainer></div>
`,
directives: [RouterOutlet]
})
export class AppComponent {
private component:Promise<ComponentRef>;
constructor(public el:ElementRef) {}
getElementRef():ElementRef {
return this.el;
}
show(){
this.component = this._dcl.loadIntoLocation(ModalComponent,this.appComponent.getElementRef(), 'modalContainer').then(component => {console.log('Component loaded')})
}
hide(){
this.component.then((componentRef:ComponentRef) => {
componentRef.dispose();
return componentRef;
});
}
}

Categories

Resources