Angular 4 ngOnInit not working when loading the component first time - javascript

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.

Related

how can pass different parameters using [routerLink] or router.navigate to a component?

I configured app-routing.module.ts as following:
const routes: Routes = [
{
path: "branches/:branch",
component: BranchesComponent
},
// ...
];
and also in app.component.html have:
<li>
<a [routerLink]="['/branches', 'engineering']"> engineering </a>
</li>
<li>
<a [routerLink]="['/branches', 'baseSciense']"> baseSciense</a>
</li>
<li>
<a [routerLink]="['/branches', 'humanities']"> humanities</a>
</li>
</ul>
<router-outlet></router-outlet>
and in the barnches.component.ts I coded as bellow:
branch: string ='';
constructor(private route: ActivatedRoute) { }
ngOnInit(): void {
this.route.params.subscribe(({branch: branch1}) => this.branch = branch1);
// I also tried this code:
// this.route.params.subscribe(branch => this.branch = branch['branch']);
// unfortunately, bellow codes have error on p.branch and params.branch! why?
// this.route.params.subscribe(p => this.branch = p.branch)
// this.branch = this.route.snapshot.params.branch;
console.log(`branch is : ${this.branch}`);
}
till here everything comes correct, and URL is changed once the respective link is clicked, such as :
http://localhost:4200/branches/engineering
http://localhost:4200/branches/baseSciense
http://localhost:4200/branches/humanities
but the property branch in Branches component is not changed and has same value (engineering) for different parameters in log of the console. it is illogical for me!
how can solve this problem as pass different parameters to and capture them into branches component? Best regards
You need to move your console.log within the subscription. Most of the code related to this feature will need to take place within the subscription. The Component doesn't re-render on url change because it is loading the same component and angular doesn't re-render components if it is the same component.
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
#Component({
template: 'Branch: {{branch}}',
})
export class BranchesComponent implements OnInit {
branch = '';
constructor(private route: ActivatedRoute) {}
ngOnInit() {
// Set the value every time the URL param changes.
this.route.params.subscribe(({ branch }) => {
this.branch = branch;
console.log('branch:', this.branch);
});
}
}
In your component you can subscribe to router events and listen for them like this.
this.router.events.pipe(filter(r => r instanceof NavigationEnd)).subscribe(r => {
console.log((r as NavigationEnd).url);
});

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

Angular 4/5 Component variable from Subject/Services not binding to HTML template

I have a variable in my app.component.ts file that is passed in from a controller via my services.ts file.
As far as I can see, the Object property passed to the Component via the Subject is working, because I can console.log(this.selectedContact.name) and It will log to the console perfectly, which would mean that it is correctly being stored into the variable selectedContact: any;. But I cannot seem to bind this to my HTML and I am curious as to what I may be missing.
Here is my app.component.ts file for the view I'm attempting to bind to:
import { Component, OnInit } from '#angular/core';
import { ContactsListComponent } from '../contacts-list/contacts-list.component';
import { ApiService } from '../api.service';
#Component({
selector: 'app-contact-details',
templateUrl: './contact-details.component.html',
styleUrls: ['./contact-details.component.scss']
})
export class ContactDetailsComponent implements OnInit {
selectedContact: any;
constructor(private _apiService: ApiService) { }
ngOnInit() { this.showContact()}
showContact() {
this._apiService.newContactSubject.subscribe(
contact => {
this.selectedContact = contact;
console.log(this.selectedContact.name);
});
}
}
Here is my HTML file (The view I am attempting to bind to.:
<div id= 'contact-card' >
<header>
<button routerLink='/home'>< Contacts</button>
</header>
<section id= card-container>
<img id ="largeContactPhoto" onerror="onerror=null;src='../assets/User Large/User — Large.png';"
src={{selectedContact.largeImageURL}} />
<h1> {{selectedContact.name}} </h1>
<h2> {{selectedContact.companyName}} </h2>
<ul>
<hr>
<li class="contact-data">
<h3> Phone: </h3>
{{selectedContact.phone.home}}
<hr>
</li>
<li class="contact-data">
<h3> Phone: </h3>
{{selectedContact.phone.mobile}}
<hr>
</li>
<li class="contact-data">
<h3> Phone: </h3>
{{selectedContact.phone.work}}
<hr>
</li>
<li class="contact-data">
<h3> Address: </h3>
{{selectedContact.address.street}}<br>
{{selectedContact.address.city}},{{selectedContact.address.state}} {{selectedContact.address.zipcode}}
<hr>
</li>
<li class="contact-data">
<h3> Birthdate: </h3>
{{selectedContact.birthdate}}
<hr>
</li>
<li class="contact-data">
<h3> Email: </h3>
{{selectedContact.email}}
<hr>
</li>
</ul>
</section>
</div>
app.service.ts (where the click event data is being passed to app.component.ts:
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/map';
#Injectable()
export class ApiService {
//API URL
private url: string = 'assets/api/contacts.json';
public newContactSubject = new Subject<any>();
//Initialize HttpClient for request
constructor(private _http: Http) { }
//Pull JSON data from REST API
getContacts(): Observable<any> {
return this._http.get(this.url)
.map((response: Response) => response.json());
}
openSelectedContact(data) {
this.newContactSubject.next(data);
}
}
Sibling Component THIS IS WHERE THE CLICK EVENT HAPPENS* :
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { ApiService } from '../api.service';
#Component({
selector: 'app-contacts-list',
templateUrl: './contacts-list.component.html',
styleUrls: ['./contacts-list.component.scss']
})
export class ContactsListComponent implements OnInit {
title : string = 'Contacts';
sortedFavorites: any[] = [];
sortedContacts: any[] = [];
errorMessage: string;
constructor (private _apiService: ApiService, private router: Router) {}
ngOnInit(){ this.getContacts()}
getContacts() {
this._apiService.getContacts()
.subscribe(
(contacts) => {
//Sort JSON Object Alphabetically
contacts.sort((a , b) => {
if (a.name > b.name) {return 1};
if (a.name < b.name) {return -1};
return 0;
})
//Build new Sorted Arrays
contacts.forEach( (item) => {
if (item.isFavorite) {
this.sortedFavorites.push(item);
} else {
this.sortedContacts.push(item);
}
});
});
}
openFavorite($event, i) { <--Click event coming in from HTML template
let selectedFavorite = this.sortedFavorites[i];
this._apiService.openSelectedContact(selectedFavorite); <--Routing this data to services.ts file
this.router.navigate(['/details']); <--Navigate to detailed view to see selected contact.
};
}
This binding will work with any other test variables that i put into my app.component.ts file and they get bounded to the page. I feel like this is the right syntax for it to be properly binding the object.properties, but it doesn't seem to be working.
What might I be missing on this? Thanks a lot in advance!!
EDIT One: I feel like I should also add that this exact same code was working before when I had my component routed to this component. It was only when I moved my files to a new component and created a different new that this started happening. Maybe it is isn’t imported correctly somewhere?
EDIT TWO: I have refactored the code in the original post and corrected my TypeScript declarations and removed this from my html template. I have placed my console.log(this.selectedContact.name) function inside my showContact() method and it does log the correct name once the name in the contacts list is clicked along side this error: ERROR TypeError: Cannot read property 'largeImageURL' of undefined. So it is logging this.selectedContact.name correctly and that variable isn't defined anywhere else in my application. I have added my app.service.ts file as well as my sibling component.ts file just to put this in context where the click event happens. I want the click event data to go from Sibling component --> Api Service.ts file --> Sibling component on a click event. This is very confusing to me why this isn't happening.
EDIT THREE
One other issue that I have noticed is that whenever I attempt to use the safe navigation operator to resolve this, my highlighting changes in my html template, making me think that it is breaking it.
versus this:
Notice the change in the </h1> highlighting? This has subsiquently made every open and closing tag show up in grey instead of the red now. I have never attempted to use the safe navigation operator, so I am unsure as to whether this is normal. I have tried it with different spacing around the {{ and }} and still no luck.
Are you expecting an array or an object? You should type exactly what you are getting from your service.
Here you mention an array:
selectedContact: any[] = [];
Here you mention an object.
<h1> {{this.selectedContact.name}} </h1>
Give a shot in your template with the json pipe.
{{ selectedContact | json }}
This will help you debug.
Also, because your data is displayed on the browser before it is received by your service, you will get an undefined variable. Either use ? or an *ngIf. (With the *ngIf, don't define your selectedContact as an array to start with)
<div id= 'contact-card' *ngIf="selectedContact">
or
<h1> {{this.selectedContact?.name}} </h1>

Angular 4.3.1 update component from inside observable callback [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 5 years ago.
THE SETUP
I'm teaching myself Angular with a simple app of my own: a game which will use routing, services, etc. On the landing page ( route for '/'), I want the header to hide. The header and router-outlet are together in the app-component like this (including some debug output in the brand):
<nav class="navbar navbar-default navbar-fixed-top" [hidden]="!showHeader">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="" [routerLink]="['/']">FARKLE! {{showHeader | json}}</a>
</div>
</div>
</nav>
<div class="container">
<router-outlet></router-outlet>
</div>
The component class declares the boolean showHeader and attempts to update it when the route changes like this:
import { Component, OnInit } from '#angular/core';
import { Router, NavigationEnd } from '#angular/router';
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
showHeader: boolean;
constructor(
private router: Router
) {}
ngOnInit () {
this.showHeader = true;
this.router.events.subscribe(this.setHeader());
}
private setHeader () {
var showHeader = this.showHeader;
return function (event) {
if (event instanceof NavigationEnd) {
showHeader = event.url === '/';
console.log(event.url, showHeader, this.showHeader);
};
};
}
}
The value of this.showHeader properly sets in ngOnInit and the header will show or not show correctly depending on how it is initialized. The console shows the event occurring during navigation and the value being determined correctly. The issue is in that in the context of the callback, this is no longer the component. So, I attempt to pass this.showHeader in by reference. BUT, showHeader is not reflected in the template (most likely because it is not actually getting into the scope of the event callback.
THE QUESTION
So, how do you affect the component scope from with the callback of the observable?
What I understood is, you're kind of doing closure, where the setHeader's returned function will be registered as subscription.
The setHeader's returned function should use Arrow function in order to persist correct this
private setHeader () {
var showHeader = this.showHeader;
//return function (event) {
//should get changed to below line
return (event) => { //Use Arrow function here.
if (event instanceof NavigationEnd) {
showHeader = event.url === '/';
console.log(event.url, showHeader, this.showHeader);
};
};
}
You need to bind the this keyword to the setHeader function.
You can do that in 2 ways:
use arrow => function
this.router.events.subscribe(() => this.setHeader());
use bind
this.router.events.subscribe(this.setHeader.bind(this));

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