Angular 2 trigger event - javascript

I am trying to trigger a click event on another element but it doesn't work.
My Component:
import {Component, Output, EventEmitter} from 'angular2/core';
#Component({
selector: 'generate-car',
template: `
<button [disabled]="!pressed" (click)="parkingCar()" type="button" class="parking">Parking Car</button>
<car *ngFor="#car of cars" [class]="car.type" (animate)="test()">
<span class="car-number">{{car.id}}</span>
</car>
`
})
When I click on the button I need to trigger (animate) event.
My class:
export class GenerateCar {
#Output() animate = new EventEmitter();
parkingCar() {
this.animate.emit(null)
}
test() {
console.log( 111 )
}
}

I think set up is not correct or something.
You need to understand the concept of EventEmitter of Angular2. It allows you to fire custom event of DOM element and propagate it to its parent.
I don't understand what you would like to do with EventEmitter with your example. But I'd like to give you a direction which might help you in your example.
EventEmitter very simple example
boot.ts
#Component({
selector: 'my-app',
directives:[Car],
template: `
<div>{{name}} from parent</div><br>
<car (animate)="test($event)"></car><bR>
`
,
})
export class BodyContent { //ParentComponent
name:string='Angular1';
test(arg) {
console.log('test start');
//this.animate.subscribe((value) => { this.name = value; });
this.name=arg;
console.log(arg);
}
}
,
})
car.ts
export class Car {
#Output() animate: EventEmitter = new EventEmitter();
setValue()
{
console.log('setValue strat');
this.animate.next('angular2');
// You can also write below line in place of above line
//this.animate.emit('Angular2');
}
}
Working Plunker
Note:
(animate) = "test($event): It will tell Angular to invoke the ParentComponent's test($event) method when ChildComponent(car) fires animate. The data that we passed to the “next()” method in caris available in ParentComponent by passing $event as an argument to the test() method.
For further information you can refer to this nice article.
Hope this will help you to go further.

Related

How do you access the for loop of the parent component within a child component which has the inputs?

So I am stuck on this. I am trying to get the Parent component to talk or integrate with the child component.
Here is the parent component which basically has the for loop used to iterate or generate more links if a user wants to add more or presses the button to add more.
<div class="section url-wrapper">
<div *ngFor="let url of urls; let i = index;" class="link input-wrapper">
<childComponent></childComponent>
<button class="button bad icon-only" (click)="removeURL(i)">
<i class="far fa-times"></i>
</button>
</div>
</div>
The parent component should only be able to register and display the output of the child component.
This is an example of the child component
<div class="section url-wrap">
<input aria-label="URL Title" placeholder="Title" type="text" [value]="urls[i].title" (ngModel)="urls[i].title" name="url.title.{{ i }}"
(input)="updateTitle(i, $event.target.value)">
<input aria-label="URL" placeholder="https://example.com" type="text" [value]="urls[i].url" (ngModel)="urls[i].url" name="url.url.{{ i }}"
(input)="updateUrl(i, $event.target.value)">
</div>
I need help both allowing the parent component to register input from the child component and being able to iterate from the for loop from the parent if it is possible.
Please let me know if you need more information such as the component files or clarification
The below code & example will demonstrate how data flows from parent -> child -> parent by using the #Input() and #Output() directives.
Working Example Here
parent.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-parent',
template: `
<div class="section url-wrapper">
<div *ngFor="let url of urls" class="link input-wrapper">
<app-child [url]="url" (updateUrl)="onUrlUpdate($event)"></app-child>
</div>
</div>
`
})
export class ParentComponent implements OnInit {
public urls = [
{url: "https://example.com", title: "Example1"},
{url: "https://example.com", title: "Example2"},
{url: "https://example.com", title: "Example3"},
]
constructor() { }
ngOnInit() {
}
onUrlUpdate($event) {
// completely overkill, but just used to demonstrate a point
var url = this.urls.find(_url => {
// we can see here that the $event.url is actually the same object as the urls[i] that was
// passed to the child. We do not lose the reference when it is passed to the child or back
// up to the parent.
return $event.url === _url
});
if (url) {
url[$event.prop] = $event.newValue;
}
console.log(`Updated URL's "${$event.prop}" property with the value "${$event.newValue}"`);
}
}
child.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
template: `
<div class="section url-wrap">
<input aria-label="URL Title" placeholder="Title" type="text" [value]="url.title"
(input)="handleUrlUpdate($event, 'title')"/>
<input aria-label="URL" placeholder="https://example.com" type="text" [value]="url.url"
(input)="handleUrlUpdate($event, 'url')"/>
</div>
`,
})
export class ChildComponent implements OnInit {
#Input() url; // passed in from parent via [url] property on <app-child>
#Output() updateUrl = new EventEmitter();
constructor() { }
ngOnInit() {
// this.url is now available for the life of the child component (assuming it was passed by the parent)
}
handleUrlUpdate($event, propToUpdate) {
// overkill, but used to demonstrate a point
this.updateUrl.emit({url: this.url, prop: propToUpdate, newValue: $event.target.value});
}
}
The stardard way to let components speack each others is with input-output:
You can pass values from parent to children with #Input for example:
Parent code:
<childComponent [someInputValue]="hello"></childComponent>
Children code:
#Input() someInputValue; //this property will be "hello"
and you can pass values (after being triggered) from children to parent:
Children code:
#Output() itemSelectedOutput: EventEmitter<any> = new EventEmitter();
buttonClicked() {
this.itemSelectedOutput.emit("clicked");
}
Parent code:
<childComponent [someInputValue]="hello" (itemSelectedOutput)="someParentMethod($event)"></childComponent>
someParentMethod(event: any) {
console.log(event);
}
You can reach the same thing with ISubscription but I suggest you to use the way above
Hope it can help
I wouldn't do it this way in particular. If the children have to know about the parents, then your architecture should be adjusted

Angular 6 nested ViewChild inside ng-template is null

We are using a modal (ng-bootstrap's one) in our application. That modal looks like:
<ng-template #modal let-modal>
<app-audio #audio></app-audio>
</ng-template>
And it's logic:
#ViewChild('modal')
modal: ElementRef;
#ViewChild('audio')
audio: AudioComponent;
The modal is opened with:
this.modalService.open(this.modal, { size: 'lg' });
Everything fine till here. The modal opens and the audio component is shown. But now, we want to access the logic that is inside the component, and when doing something like:
this.audio.somePublicComponentFunction()
It happens that this.audio is null. I have already tried to get the child with angular's change detector, but cannot find a way to properly link this.audio with the actual component. Any ideas? Thanks a lot.
You can see the issue here: stackblitz.com/edit/angular-ofmpju
You can call the method audio.someFunction() from the template itself.
<ng-template #modal let-modal>
<div style="background-color: red;">
<h1>Modal header</h1>
<app-audio #audio></app-audio>
<!-- on click, call audio comp method someFunction() using its reference -->
<button (click)="audio.someFunction()">Operate with audio from inside modal</button>
</div>
</ng-template>
No need of #ViewChild property here. This should do the trick for you.
Forked demo
You can read the child component without the refrence variable like this
#ViewChild(AudioComponent)
audio: AudioComponent;
This will give you the instance of the child component - where you can access the method
this.audio.someComponentFunction()
Your html
<ng-template #modal let-modal>
<app-audio></app-audio>
</ng-template>
This will solve your issue i think - Happy coding
Update:
Hope i found a workaround for this issue - if in case you want to trigger only one function you can use this method
I have just added a property with getter and setter and triggered the function when we set the value
#Input()
get triggerFunction(): boolean {
return this.runFuntion;
}
set triggerFunction(value: boolean) {
this.runFuntion = value;
this.someFunction();
}
So this causes to trigger the function every time when the model show up - property mentioned above belongs to the child component which is nested inside the <ng-template> so finally the model template will read as mentioned below:
<ng-template #modal let-modal>
<app-audio [triggerFunction]="true"></app-audio>
</ng-template>
Hope this will act a workaround for now - Thanks
For me all this solutions did not work and I still wanted to access my own component inside a third party ng-template. Here is my 'solution'. I don't think this is best practice but a desperate solution to get what I want ;-) It only works for your own components of course.
// mycomponent.ts => component that needs to be accessed
import { Component, Output, EventEmitter, AfterViewInit } from '#angular/core';
#Component({
selector: 'my-component',
templateUrl: './mycomponent.html'
})
export class MyComponent implements AfterViewInit {
#Output() initialized: EventEmitter<MyComponent> = new EventEmitter<MyComponent>();
ngAfterViewInit(): void {
this.initialized.emit(this);
}
reload(): void {
// Do something
}
}
// somecomponent.html => component with <ng-template> holding MyComponent
<ng-template>
<div class="btn-group ml-2">
<my-component (initialized)="onMyComponentInitialized($event)"></my-component>
</div>
</ng-template>
// somecomponent.ts => component with <ng-template> holding MyComponent
import { Component, OnDestroy } from '#angular/core';
import { MyComponent } from '../../my-component';
#Component({
selector: 'some-component',
templateUrl: './some-component.html'
})
export class SomeComponent implements OnDestroy {
private _myComponent: MyComponent = null;
onMyComponentInitialized(component: MyComponent) {
this._myComponent = component;
}
someOtherMethod() {
if (this._myComponent) {
// Call some method on the component
this._myComponent.reload();
}
}
ngOnDestroy() {
this._myComponent = null;
}
}

Textarea to ignore enter but need to trigger Save button

A bit tricky situation. For the code below, I have added (keydown.enter)="false" to ignore the break line/enter button in textarea
This is causing a user issue and would like the existing behaviour where Pressing enter should automatically trigger the "Save button"
Any idea how to trigger the Save button when still focusing in textArea but ignore the breakline?
<textarea #textArea
style="overflow:hidden; height:auto; resize:none;"
rows="1"
class="form-control"
[attr.placeholder]="placeholder"
[attr.maxlength]="maxlength"
[attr.autofocus]="autofocus"
[name]="name"
[attr.readonly]="readonly ? true : null"
[attr.required]="required ? true : null"
(input)="onUpdated($event)"
[tabindex]="skipTab ? -1 : ''"
(keydown.enter)="false"
[(ngModel)]="value">
</textarea >
Extending the answer by #Pengyy
You can bind the bind the enter key to a pseudoSave function, and preventDefault inside of that, thus preventing both the Save function and the newline. Then you can either call the save function from there(assuming it is accessible such as a service) or you can emit an EventEmitter, and have that emit get caught to trigger the Save function.
you can bind the same function of Save button to keydown.enter of texterea, and call $event.preventDefault to avoid the newline.
sample plunker.
Assuming that your textarea is inside a form element.
{Plunker Demo}
You can achieve it by using a hidden submit input, like this
#Component({
selector: 'my-app',
template: `
<form (submit)="formSubmitted($event)">
<input #proxySubmitBtn type="submit" [hidden]="true"/>
<textarea #textArea (keydown.enter)="$event.preventDefault(); proxySubmitBtn.click()">
</textarea>
</form>
`,
})
export class App {
formSubmitted(e) {
e.preventDefault();
alert('Form is submitted!');
}
}
You can create a service which can send a notification to other components that will handle the command. The service could look like this:
import { Injectable } from "#angular/core";
import { Subject } from "rxjs/Subject";
#Injectable()
export class DataSavingService {
private dataSavingRequested = new Subject<void>();
public dataSavingRequested$ = this.dataSavingRequested.asObservable();
public requestDataSaving(): void {
this.dataSavingRequested.next();
}
}
... and should be registered in the providers section of the module. Note: if data must be passed in the notification, you can declare a non-void parameter type for the dataSavingRequested Subject (e.g. string).
The service would be injected in the component with the textarea element and called in the handler of the Enter keypress event:
import { DataSavingService } from "./services/data-saving.service";
...
#Component({
template: `
<textarea (keypress.enter)="handleEnterKeyPress($event)" ...></textarea>
`
})
export class ComponentWithTextarea {
constructor(private dataSavingService: DataSavingService, ...) {
...
}
public handleEnterKeyPress(event: KeyboardEvent): void {
event.preventDefault(); // Prevent the insertion of a new line
this.dataSavingService.requestDataSaving();
}
...
}
The component with the Save button would subscribe to the dataSavingRequested$ notification of the service and save the data when notified:
import { Component, OnDestroy, ... } from "#angular/core";
import { Subscription } from "rxjs/Subscription";
import { DataSavingService } from "../services/data-saving.service";
...
#Component({
...
})
export class ComponentWithSaveButton implements OnDestroy {
private subscription: Subscription;
constructor(private dataSavingService: DataSavingService, ...) {
this.subscription = this.dataSavingService.dataSavingRequested$.subscribe(() => {
this.saveData();
});
}
public ngOnDestroy(): void {
this.subscription.unsubscribe();
}
private saveData(): void {
// Perform data saving here
// Note: this method should also be called by the Save button
...
}
}
The code above assumes that the saving must be performed in the component with the Save button. An alternative would be to move that logic into the service, which would expose a saveData method that could be called by the components. The service would need to gather the data to save, however. It could be obtained with a Subject/Observable mechanism, or supplied directly by the components as a parameter to saveData or by calling another method of the service.
it could be 2 solutions:
Use javascript to handle enter event and trigger Save function in it
or
Use Same thing from Angular side as describe in this.
This may also help you

Angular 2: Function as an #Input-Property

You can see my Plunk here.
In this very easy example I am passing a function
_ => {
console.log(number);
}
to a child component with the #Input property. My parent component looks like that:
#Component({
selector: 'my-app',
template: `
<child [func]="getFunction(3)">
</child>
<button type="button" (click)="startChangeDetection()">
Start change detection
</button>
`,
directives : [Child],
styles:[`
.titles {
color:#0099FF
}
.child-style {
background-color:#00ffff
}
` ],
})
export class CarComponent {
startChangeDetection()
{
}
getFunction(number)
{
return _ => {
console.log(number);
}
}
}
The button does nothing else than trigger another round of change detection (there is no implementation in the callback function.
However, my change detection always recognizes my input as a change, nevertheless it never changes.
This is my child component:
#Component({
selector: 'child',
template: `
<h2>Child Component</h2>
`,
inputs: ['func']
})
export class Child {
private func;
ngOnChanges(changes)
{
console.log(changes.func.previousValue.toString());
console.log(changes.func.currentValue.toString());
}
}
You can see, that in ngOnChanges I log my function to the console. But the logged value (obviously) does never change, so the output is always:
function (_) {
console.log(number);
}
function (_) {
console.log(number);
}
Why the heck does Angular even call ngOnChanges? And why does it think there is any change?
This method returns a different function instance every time getFunction is called.
getFunction(number)
{
return _ => {
console.log(number);
}
}
because of <child [func]="getFunction(3)">, getFunction is called every time change detection is run.
Binding to functions is usually not the best idea. If you move out the function creation this way, the same function instance is returned every time and Angular change detection won't recognize it as change:
myCallback = _ => this.myCallBack(number) {
console.log(number);
}
getFunction(number)
{
return this.myCallback;
}
I don't see anything abnormal there. The change detection is getting called twice.
First time because child component is rendered inside car component. The component tree is changed.
Second time because the function getFunction is getting called when you are passing it as getFunction(3) to the input. Thats equivalent to change in input value and hence triggering change detection cycle.

Shared value between Parent and Child manipulation

I have two components: Parent, Child.
Here is the scenario:
Child gets the value from Parent which continue changes
Child is always updated for the changed value
Child manipulates the value and displays it
On the Parent side the value doesn't get changed just becauseof it's manipulated on the Child side
An example use case:
I've tried this with the #Input. Because the #Input value gets manipulated on the Child side, on the Parent side it changes also. This is what I want to prevent, but also still want to keep getting the updated value from the Parent side.
An example code with #Input:
#Component({
selector: 'c-parent',
template: `
<div>{{question.name}}</div>
<button type="button" label="xxx" (click)="changeTheValue()"></button>
<br/>
<c-child [tmpQuestion]="question"></c-child>`
})
export class Parent implements OnInit {
question: Question; // don't get changed from the Child side
ngOnInit() {
question.name = "1";
}
changeTheValue(){
question.name = "2";
}
}
#Component({
selector: 'c-child',
template: `<div>{{tmpQuestion.name}}</div>`
})
export class Child implements OnInit {
#Input() tmpQuestion: Question; // be updated for the changes
ngOnInit() {
tmpQuestion.name = "This is the question: " + question.name; //manipulate the value
}
}
How can I do this with #Input approach or do I need to use something else?
Plunker
Using this
Added this to variables inside functions so question.name becomes this.question.name.
Primatives
Primatives (String, Number, Boolean, Symbol) are easier to work with if you want the child component to detect changes so in the parent component I sent the name property into the child component's input field.
Child Component Manipulated Value
A few things need to happen to display a manipulated value in the child component:
Create a variable for the manipulated value, I used manipulatedValue.
Move the manipulation logic into it's own function
like this:
manipulate() {
this.manipulatedValue = "This is the question: " + this.tmpQuestionName;
}
Call the manipulate function inside both ngOnInit and ngOnChanges
Pipes
If all you need is to do value manipulation you might be better off using a pipe
Parent Component
#Component({
selector: 'c-parent',
template: `
<div>Parent Question Name: {{question.name}}</div>
<button type="button" label="xxx" (click)="changeTheValue()">Change the value</button>
<br/>
<c-child [tmpQuestionName]="question.name"></c-child>`
})
export class Parent implements OnInit {
question = { name: '1' };
ngOnInit() {
this.question.name = "1";
}
changeTheValue(){
this.question.name = 'new ' + this.question.name;
}
}
Child Component
#Component({
selector: 'c-child',
template: `<div>Child Manipulated Value: {{manipulatedValue}}</div>`
})
export class Child implements OnChanges, OnInit {
#Input() tmpQuestionName; // be updated for the changes
manipulatedValue;
ngOnInit() {
this.manipulate();
}
ngOnChanges() {
this.manipulate();
}
manipulate() {
this.manipulatedValue = "This is the question: " + this.tmpQuestionName; //manipulate the value
}
}

Categories

Resources