Angular 6 nested ViewChild inside ng-template is null - javascript

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;
}
}

Related

In Angular can I use #Input as event emitter

I like to explore the feasibility with using component Input as Event output, similar to how react does it.
Parent component
parentHandler(message){
console.log(message)
}
Parent html
<child [myEvent]="parentHanlder"></child>
Child component
#Input myEvent
ngOnInit(){
this.myEvent('hello')
}
I am wondering if there are disadvantage or potential issue with this approach e.g memory/performance
thank you
This will work perfectly as expected.
The behavior will be similar to #Output except the context of execution.
The scope of execution will be with child component
export class AppComponent {
version: number = 7;
messageMe(param){
alert("Message Me.."+param+this.version);
}
}
<framework
[messageHandler]="messageMe"></framework>
export class FrameworkComponent implements OnInit {
#Input()
messageHandler: Function;
name="Jone";
ngOnInit() {
this.messageHandler(this.name);
}
}
You can use setter in this situation
Parent:
<child [myEvent]="parentHanlder"></child>
Child (ts):
#Input()
set myEvent(parentHandler: string) {
this.myEvent('hello');
}

Javascript Angular 4 Change ngClass from another Component

I currently have this code in my app.component.ts
app.component.html
<div [ngClass]="myclass">
...rest of the content here
</div>
This I have the this:
<button (click)="changeClass('myFavClass')">Change Class to myFavClass</div>
app.component.ts
export class AppComponent {
myclass: string;
changeClass(myclass) {
this.myclass = myclass;
}
}
Now, all this works fine BUT I now want to put the triggering button on another component.
If I put this on another component:
<button (click)="changeClass('myFavClass')">Change Class to myFavClass</div>
How can I get it to change the class?
There are two ways you can do this you can use output with an EventEmit
Or you can set up a service that monitors the changes to a variable and use that as the control point for the change.
Personally, I use services for this instance as its easier to manage the code and its flow.
This answer has all the code in you need to look at.
Changing a value in two different components at the same time Angular 2
Hope that helps
There are at least two options. Subject and Observable or if this another component is a parent you can use #Input.
Subject and Observable method:
angular guide Highly recommended to read whole page.
Some component
export class SomeComponent {
constructor(private ClassService: ClassService) { }
private changeClass(class) {
this.ClassService.changeClass(class);
}
}
Another Component
export class AnotherComponent implements OnInit, OnDestroy {
constructor(private ClassService: ClassService) { }
private class: string = "";
private subscribtion: Subscribtion;
ngOnInit(): void {
this.Subscribtion = this.ClassService.someClass$.subscribe(
(class) => { this.class = class; }
)
}
ngOnDestroy(): void {
this.Subscribtion.unsubscribe();
}
}
Service
#Injectable();
export class ClassService{
constructor() { }
private someClassSource= new Subject<string>();
someClass$= this.someClassSource.asObservable();
changeClass(class) {
this.someClassSource.next(class);
}
}
taken from my answer
#Input method:
angular guide
This is very simple, when you click button changeClass method will change elClass which will be passed to another component by #Input decorator, every change of #Input will cause a detect changes which will detect that value has changed so class will change to myClass.
Parent component
parent.component.html
<another-component [elementClass]="elClass"></another-component>
<button (click)="changeClass('myClass')">change class<button>
parent.component.ts
export class ParentComponnet {
private elClass: string = "";
changeClass(class: string) {
elClass = class;
}
}
Another component (must be child component)
another.component.html
<div [ngClass]="elementClass">
another.component.ts
export class AnotherComponent {
#Input() elementClass: string;
}
There is also Child to Parent interaction via #Output (emitting event) angular guide

Angular 4 ngOnInit not working when loading the component first time

I Have a component in Angular 4 that and a template to change the route
This component is called but does not load anything no server call.
If i put the ngOnInit() method content into constructor it works fine.
It seems ngOnInit is not called. Anybody please can help i am working on this since last 2 days.
here is my routing configuration.
const testRouting: ModuleWithProviders = RouterModule.forChild([
{ path:'createtest/:id', component:TestComponent, resolve: { test:TestResolver }},
{ path:'createtest', component:TestComponent, resolve: { test:TestResolver }},
{ path:'testlist', component:TestListComponent }
]);
import {Component,OnInit} from '#angular/core'
import {TestService,Test} from '../shared'
import 'rxjs/add/operator/map';
#Component({
selector:'test-list',
templateUrl:'./testlist.component.html'
})
export class TestListComponent implements OnInit{
testList:Array<Test>;
constructor(private testService:TestService){}
ngOnInit = ()=>{
this.testService.getTest()
.subscribe(
data=>this.testList = <Array<Test>>data,
error=>alert(error)
);
console.log("ngOnInit");
}
}
And here is my template to configure routing
<nav class="navbar navbar-light">
<div class="container">
<a class="navbar-brand" routerLink="/">SLearn</a>
<a class="xyz" routerLink="/testlist">Test List</a>
</div>
</nav>
You have to overload the ngOnInit function. This doesn't work with arrow functions.
You have to do:
ngOnInit() {
this.testService.getTest()
.subscribe(
data=>this.testList = <Array<Test>>data,
error=>alert(error)
);
console.log("ngOnInit");
}
Hope i could help.
Update (Additional information thanks to maximus):
While a normal function declaration creates the ngOnInit property on the prototype, an arrow function creates it on the instance.
Angular itself looks only for the hooks on the prototype. which is why your original approach doesn't work as expected.

How can I pass values into component?

I have this component:
export class MyComponent {
#Input() active:boolean;
constructor() {
console.log(this.active);
}
}
You will notice that I've declared an Input that I pass in like this:
<mycomponent
[active]="1 == 1"></mycomponent>
When this loads, the log statement in the constructor logs undefined. What am I doing wrong?
#Input property bindings are first only available after the ngOnInit,
So you should do :
export class MyComponent {
#Input() active:boolean;
ngOnInit() {
console.log(this.active);
}
}
Also FYI :
From the docs :
ngOnInit
Initializes the directive/component after Angular first displays the data-bound properties and sets the directive/component's input properties.
Called once, after the first ngOnChanges.
More on Life Cycle Hooks
Below is an example of how to use this.active in your HTML template:
<div *ngIf='active'>
<span [ngClass]="{'glyphicon-play': active}">
<div>

Update the data in one component based on what is clicked in another component in Angular 2

I have two components let's call them CompA and CompB. I would like for the clicked item object in CompA to appear in CompB. Here is what I have done so far.
CompA:
import {Component} from 'angular2/core';
import {CompB} from './compB';
#Component({
selector: 'comp-a',
template: '<ul>
<li *ngFor="#item of items" (click)="show(item)">
{{item.name}}
</li>
</ul>',
providers: [CompB]
})
export class CompA {
constructor(public _compB: CompB){}
show(item){
this._compB.displayItem(item);
}
}
CompB:
import {Component} from 'angular2/core';
#Component({
selector: 'comp-b',
template: '<div>
{{item.name}}
</div>'
})
export class CompB {
public newItem: Object;
constructor(){
this.newItem = {name: "TEST"};
}
displayItem(item){
this.newItem = item;
}
}
The problem is that when I click an item it doesn't change anything in CompB. I did a console log on CompB and I am getting the item object just fine but I view doesn't update with the clicked item's name. It just stays as 'TEST'.
Even if I set this.newItem in the displayItem function to a hardcoded string, it still doesn't change.
Update:
Both components are siblings in a main.html like this...
main.html
<div class="row">
<div class="col-sm-3">
<comp-a></comp-a>
</div>
<div class="col-sm-9">
<comp-b></comp-b>
</div>
</div>
Thats because the Component B you got injected in the constructor is not the component B used in the application. Its another component B that the hierarchical injector created, when Component B was added to the list of providers.
One way to do it is to create a separate injectable service, and inject it in both components. One component subscribes to the service and the other triggers a modification. For example:
#Injectable()
export class ItemsService {
private items = new Subject();
newItem(item) {
this.subject.next(item);
}
}
This needs to be configured in the bootstrap of the Angular 2 app:
boostrap(YourRootComponent, [ItemsService, ... other injectables]);
And then inject it on both components. Component A sends new items:
export class CompA {
constructor(private itemsService: ItemsService){}
show(item){
this.itemsService.newItem(item);
}
}
And component B subscribes to new items:
export class CompB {
constructor(itemsService: ItemsService){
itemsService.items.subscribe((newItem) => {
//receive new item here
});
}
Have a look at the async pipe, as its useful to consume observables in the template directly.
If you get a CompB instance passed to
constructor(public _compB: CompB){}
it's not the instance you expect but a different (new) one.
There are different strategies to communicate between components. This depends on how they are related in the view. Are they siblings, parent and child or something else. Your question doesn't provide this information.
For parent and child you can use data binding with inputs and outputs.
For siblings you can use data binding if you include the common parent (use it as mediator)
You always can use a shared service.
For data-binding details see https://angular.io/docs/ts/latest/guide/template-syntax.html

Categories

Resources