So I'm trying to perform some action on the parent component of the child component when a click event is fired in the child component. Currently I have a dynamic loader which is able to load different child components. The problem I have is that the #Output() is being emitted but the parent component doesn't seem to have any knowledge when this event is fired. Is there something I am missing?
child2.component.ts
import {Component, Injector, Output, EventEmitter} from '#angular/core';
#Component({
selector: 'hello-world',
template: `
<div>Hello World {{showNum}}</div>
<li (click)="childButtonClicked(false)"> </li>
`,
})
export class HelloWorldComponent {
showNum = 0;
#Output() childEvent = new EventEmitter<boolean>();
constructor(private injector: Injector) {
this.showNum = this.injector.get('showNum');
console.log("HelloWorldComponent");
}
childButtonClicked(agreed: boolean) {
this.childEvent.emit(agreed);
console.log("clicked");
}
}
child1.component.ts
import {Component, Injector, Output, EventEmitter} from '#angular/core';
#Component({
selector: 'world-hello',
template: `
<div>World Hello {{showNum}}</div>
<li (click)="childButtonClicked(false)"> </li>
`,
})
export class WorldHelloComponent {
showNum = 0;
#Output() childEvent = new EventEmitter<boolean>();
constructor(private injector: Injector) {
this.showNum = this.injector.get('showNum');
console.log("WorldHelloComponent");
}
childButtonClicked(agreed: boolean) {
this.childEvent.emit(agreed);
console.log("clicked");
}
}
dynamic.componentloader.ts
import {Component, Input, ViewContainerRef,ComponentRef, ViewChild, ReflectiveInjector, ComponentFactoryResolver} from '#angular/core';
#Component({
selector: 'dynamic-component',// Reference to the components must be here in order to dynamically create them
template: `
<div #dynamicComponentContainer></div>
`,
})
export class DynamicComponent {
currentComponent:any = null;
#ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;
// component: Class for the component you want to create
// inputs: An object with key/value pairs mapped to input name/input value
#Input() set componentData(data: {component: any, inputs: any }) {
if (!data) {
return;
}
// Inputs need to be in the following format to be resolved properly
let inputProviders = Object.keys(data.inputs).map((inputName) => {return {provide: inputName, useValue: data.inputs[inputName]};});
let resolvedInputs = ReflectiveInjector.resolve(inputProviders);
// We create an injector out of the data we want to pass down and this components injector
let injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.dynamicComponentContainer.parentInjector);
// We create a factory out of the component we want to create
let factory = this.resolver.resolveComponentFactory(data.component);
// We create the component using the factory and the injector
let component = factory.create(injector);
// We insert the component into the dom container
this.dynamicComponentContainer.insert(component.hostView);
// We can destroy the old component is we like by calling destroy
if (this.currentComponent) {
this.currentComponent.destroy();
}
this.currentComponent = component;
}
constructor(private resolver: ComponentFactoryResolver) {
}
}
main.component.ts
import { Component, OnInit } from '#angular/core';
import { HelloWorldComponent } from '../../views/main/sidebar-views/comps/hello-world.component';
import { WorldHelloComponent } from '../../views/main/sidebar-views/comps/world-hello.component';
#Component({
selector: 'main-component',
template: require('./main.component.html')
})
export class MainComponent {
private pressed: boolean = false;
componentData:any = null;
constructor() { }
createHelloWorldComponent(){
this.componentData = {
component: HelloWorldComponent,
inputs: {
showNum: 9
}
};
}
createWorldHelloComponent(){
this.componentData = {
component: WorldHelloComponent,
inputs: {
showNum: 2
}
};
}
test(){
console.log("some click event");
}
};
main.component.html
<div>
<h2>Lets dynamically create some components!</h2>
<button (click)="createHelloWorldComponent()">Create Hello World</button>
<button (click)="createWorldHelloComponent()">Create World Hello</button>
<dynamic-component [componentData]="componentData" (childEvent)="test()"></dynamic-component>
</div>
Since you are passing a parameter to the EventEmitter, you need to change your event binding on your component selector in your template to this:
<dynamic-component [componentData]="componentData" (childEvent)="test($event)"></dynamic-component>
Also, don't forget to change function signature in your component to accept the parameter:
test(agreed: boolean){
console.log("some click event");
}
More info on official docs.
Related
I am using this wrapper for the azure maps library. I am currently implementing a symbol layer and using one of the default markers works well, but I am not able to add my own marker. I tried to add a custom marker like in my mapReady function, but the response is always undefined and the image is not added.
this is my component:
import {Component, Input, OnInit} from '#angular/core';
import * as atlas from 'azure-maps-control';
#Component({
selector: 'app-map',
templateUrl: './map.component.html',
styleUrls: ['./map.component.scss']
})
export class MapComponent implements OnInit {
private markerImagePath = 'assets/images/map-marker.png';
public dataSource: atlas.source.DataSource;
markerDescription: 'marker';
public options: atlas.IconOptions = {
image: this.markerDescription
};
points = [
[52.52437, 13.41053],
[51.50853, -0.12574]
];
ngOnInit() { }
mapReady(map: atlas.Map) {
map.imageSprite.add(this.markerDescription, this.markerImagePath).then(r => {
console.log(r);
console.log(map.imageSprite.getImageIds());
this.dataSource = new atlas.source.DataSource('markers');
this.points.forEach(p => {
const point = new atlas.Shape(new atlas.data.Point([p[1], p[0]]));
this.dataSource.add([point]);
});
});
}
}
this is my html:
<section>
<div class="row">
<div class="col-12 map-dimensions my-2 mx-auto" azure-map zoom="2"
[dataSources]="[dataSource]" (onReady)="mapReady($event.map)">
<map-symbol-layer dataSourceId="markers"
[iconOptions]="options"></map-symbol-layer>
</div>
</div>
</section>
I suspect, that I access the map data wrongly... Do any of you guys know, how I can add a custom image to the imageSprites in order for me to use it as a marker in the symbol layer?
Your code looks fine. imageSprite.add returns a Promise<void>, so your console.log will always log undefined. Could your icon be the issue ? I have been trying a similar solution and all works fine on my side :
import { Component } from '#angular/core';
import * as atlas from 'azure-maps-control';
#Component({
selector: 'app-root',
template: '<azure-map zoom="2" [dataSources]="[dataSource]" (onReady)="mapReady($event.map)">' +
'<map-symbol-layer [id]="blueLayerId" dataSourceId="blue" [iconOptions]="blueIconOptions"></map-symbol-layer>' +
'</azure-map>',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
public dataSource: atlas.source.DataSource;
public blueLayerId: string = "blueLayer";
public blueIconOptions: atlas.IconOptions = {
image: 'campground'
};
mapReady(map: atlas.Map) {
map.imageSprite.add('campground', 'assets/campground.png').then(() => {
this.dataSource = new atlas.source.DataSource('blue');
for (let i = 0; i < 10; i++) {
const point = new atlas.Shape(new atlas.data.Point([i * 5, i * 5]));
this.dataSource.add([point]);
}
});
}
}
I have a Service that calls an API (Http get request) then I call the service within my component to get the data and then assign it to a value. after that I pass the value in the html to another component but it showing as undefined
import { Component } from '#angular/core';
import { AuthService } from '../../src/app/services/auth.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
passData: any
constructor(private authService: AuthService) {
}
ngOnInit() {
this.authService.test().subscribe(data => {
for (var i = 0; i < data.length; i++) {
data[i].date = new Date(
data[i].date.substring(0, 4)+'-'+
data[i].date.substring(4, 6)+'-'+
data[i].date.substring(6, 10)+ ' '+data[i].minute)
}
this.passData = data;
});
}
}
below is where I pass in my value to the new component, but showing undefined
<div class="card-deck">
<app-stock-bar [customTitle]=passData></app-stock-bar>
The value of passData when passed is undefined, but if I log it, it will display the value in the console
the answer was very simple after some research I found that you can simply use *ngIF to check if the value is present before you load
<app-stock-bar *ngIf="passData" [customTitle]="passData"></app-stock-bar>
When you are passing the value from parent to child component. You can access it inside onChanges LifeCycle hook.
Component
#Component({})
export class AppStockBar implements OnChanges{
#Input() customTitle;
ngOnChanges(){
console.log(this.customTitle);
}
}
I think the property binding missing double quotes
<app-stock-bar [customTitle]="passData"></app-stock-bar>
I'm trying to build a directive to display a custom numpad when
the control (input) on which the directive is applied is clicked.
I'm Working on Ionic 2 RC5.
myPage.html
<ion-content>
<ion-item>
<ion-label stacked>Label</ion-label>
<ion-input dirNumpad type="text" [(ngModel)]="myField"></ion-input>
</ion-item>
</ion-content>
<ion-footer>
<numpad #idNumpad hidden></numpad>
</ion-footer>
The Numpad component is in the DOM, at the bottom of the page.
dirNumpad.ts
import { Directive, ElementRef, ViewChild } from '#angular/core';
import { Numpad } from '../../components/numpad/numpad';
#Directive({
selector: '[dirNumpad]', // Attribute selector
host: {
'(click)': 'onClick()'
}
})
export class DirNumpad {
#ViewChild('idNumpad') numpad: Numpad;
constructor( private el: ElementRef ) {
}
onClick() {
this.showNumpad();
}
showNumpad() {
console.log(this.numpad); => undefined
this.numpad.show(); => error: show property does not exist on undefined
}
}
numpad.html
<div class="numpad" style="position:absolute; top:auto; left:0;
right:0; bottom:0; height:150px;">My Numpad</div>
numpad.ts
import { Component, Input } from '#angular/core';
#Component({
selector: 'numpad',
templateUrl: 'numpad.html'
})
export class Numpad {
constructor() {}
}
My problem: I can not reach the numpad component from inside the directive through ViewChild.
console.log(this.numpad) always returns "undefined"!
I need it to show the numpad only if user clicks on the input on which the directive is applied...
What am I doing wrong?
I'm stucked with this problem, so any help will be appreciated.
ViewChild only applies to the children items of the item. Since the component is not a child in any way of the directive but rather a sibling it cant be received in ViewChild.
You can pass it as part of an input
Declare an input in your component
import { Directive, ElementRef, Input } from '#angular/core';
import { Numpad } from '../../components/numpad/numpad';
#Directive({
selector: '[dirNumpad]', // Attribute selector
host: {
'(click)': 'onClick()'
}
})
export class DirNumpad {
#Input('numpad') numpad: Numpad;
constructor( private el: ElementRef ) {
}
onClick() {
this.showNumpad();
}
showNumpad() {
console.log(this.numpad); => undefined
this.numpad.show(); => error: show property does not exist on undefined
}
}
and set it in your html
<ion-content>
<ion-item>
<ion-label stacked>Label</ion-label>
<ion-input dirNumpad [numpad]="idNumpad" type="text" [(ngModel)]="myField"></ion-input>
</ion-item>
</ion-content>
<ion-footer>
<numpad #idNumpad hidden></numpad>
</ion-footer>
In my angular 2 application there is a component containing an array of objects that is passing the chosen (clicked) one to it's direct child component. This does display the data more detailed. I'm using the "SimpleChanges" feature to watch in this child component if the object given changed to make another http request to get the related comments from a database.
If I try to build it with npm I get an error, saying :
app/displayEntry.component.ts(23,41): error TS2339: Property 'entry' does not exist on type 'SimpleChanges'
If I just comment this part out, start npm and finally put it in there again and save it, there is no Problem anymore ( no erro and it works ).
My question is, is there a way to work around this behavior and can this cause any trouble later I don't foresee or should I just ignore it? Thanks for your help
Parent component:
import { Component, OnInit } from '#angular/core';
import { entry } from './Objekte/entry';
import { entryService } from './entry.service'
#Component({
templateUrl: 'app/Html_Templates/displayLastEntrys.template.html'
})
export class displayLastEntrys implements OnInit{
public entrys : entry[];
private entryChoosen: boolean;
private ChoosenEntry : entry;
constructor ( private entryservice : entryService){
this.entryChoosen = false;
}
ngOnInit() : void {
this.getData();
}
getData() {
this.entryservice.getFirstEntrys().then(entrys => this.entrys = entrys);
}
entryClicked(ent: entry){
this.entryChoosen = true;
this.ChoosenEntry = ent;
}
leaveEntry () {
this.entryChoosen = false;
}
voted( upordown : boolean ) {
}
}
Child component:
import { Component, Input, Injectable, OnChanges , SimpleChanges, Output, EventEmitter} from '#angular/core';
import { entry} from './Objekte/entry';
import { entryService } from './entry.service';
import { comment } from './Objekte/comments';
#Component({
selector: 'display-entry',
templateUrl: 'app/Html_Templates/displayEntry.template.html'
})
export class displayComponent implements OnChanges{
#Input() public entry : entry;
public comments : comment[];
private changecounter : number;
constructor(private service : entryService) {
this.changecounter = 0;
}
ngOnChanges(changes : SimpleChanges){
this.service.getComments(changes.entry.currentValue.id)
.then(com => this.comments = com )
.catch();
this.entry.viewed++;
// To implement :: change database
}
votedUp () : void {
this.entry.votes ++;
// To implement :: change database
}
votedDown () : void {
this.entry.votes --;
// To implement :: change database
}
}
The accepted solution is suboptimal for TypeScript, as you're defeating the type system.
SimpleChanges does not have an entry property, so the compiler quite rightly balks. The solution is to treat the changes object as an array:
ngOnChanges(changes : SimpleChanges){
if (changes['entry']) {
this.service.getComments(changes['entry'].currentValue.id)
}
}
Then you can continue to strongly type the ngOnChanges method.
To make the compiler not complain just change your method definition for parameter one from SimpleChanges to any:
ngOnChanges(changes: any) {
//...
Maybe it's changed a lot now but this works these days
import {Component, Input, OnChanges, SimpleChanges} from '#angular/core';
import {ConfigModel} from './config.model'
#Component({
selector: 'selector',
templateUrl: './template.html',
styleUrls: ['./styles.scss']
})
export class BlaComponent implements OnChanges {
#Input() config: ConfigModel;
ngOnChanges(changes: SimpleChanges): void {
if (changes.config && changes.config.currentValue) {
let config = <ConfigModel>changes.config.currentValue;
// do more
}
}
}
I myself got the compile error because i wasn't using .currentValue after calling changes.config
If you are completely dependent on the IDE's auto-completion, make sure to actually use SimpleChanges instead of just SimpleChange. A very thing to be overlooked at.
This question already has an answer here:
Error if don't check if {{object.field}} exists
(1 answer)
Closed 6 years ago.
I am using angular2-universal-starter project.
So i was trying to pass an object to a child component using #Input , but its not working correctly.
I have used dynamic component loader to load child component and I want to pass the object to child component.
Following is my code snippet:
app.component.ts
import {Component, Directive, Renderer, DynamicComponentLoader, ElementRef} from 'angular2/core';
import {Http} from 'angular2/http';
import {headingComponent} from './heading.component';
#Directive({
selector: '[x-large]'
})
export class XLarge {
constructor(element: ElementRef, renderer: Renderer) {
// we must interact with the dom through Renderer for webworker/server to see the changes
renderer.setElementStyle(element.nativeElement, 'fontSize', 'x-large');
}
}
#Component({
selector: 'app',
directives: [
XLarge
],
template: `
<div>
<div>
<span x-large>Hello, {{ user.name }}!</span>
</div>
<icici-heading [user]="user"></icici-heading>
</div>
`
})
export class App {
public user;
constructor(dcl: DynamicComponentLoader, elementRef: ElementRef) {
dcl.loadNextToLocation(headingComponent, elementRef);
}
ngOnInit(){
this.user = { "id": 11, "name": "Mr. Nice" };
}
}
heading.component.ts
import {Component, OnInit,Input} from 'angular2/core';
#Component({
selector: 'icici-heading',
template: `
<div>
<!--{{user.name}}-->this is not working
{{name}}
</div>
`
})
export class headingComponent implements OnInit {
#Input() user;
name: string;
constructor() { }
ngOnInit() {
this.name="heading is rendered";
}
}
I guess you just need to make your code more forgiving when the value is not yet available.
This will work:
{{user?.name}}
The Elvis or safe-navigation operator only evaluates .name when user != null
For dynamically added components you also need to pass values imperatively
dcl.loadNextToLocation(headingComponent, elementRef)
.then(cmpRef => cmpRef.instance.user = this.user);