I'm having a bit of trouble I cant seem to figure out why my #ViewChild isnt working..
Basically I want to call a function in one component from another component so in my sidebar component I have a function called sendData() and I want to be able to call that from a button click in my header component so what Ive done is..
Sidebar component
import { Component, OnInit, Input, OnChanges, SimpleChanges, AfterViewInit } from '#angular/core';
import * as _ from 'lodash';
#Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.scss']
})
export class SidebarComponent implements OnInit, OnChanges {
constructor(
private contenfulService: ContentfulService,
private userService: UserService
) { }
ngOnInit() {
}
sendData(){
...do something
}
}
header.component.ts
import { Component, OnInit, Input, ViewChild, EventEmitter } from '#angular/core';
import { UserService } from '../../../user.service';
import { SidebarComponent } from '../sidebar/sidebar.component';
#Component({
selector: 'app-program-header',
templateUrl: './program-header.component.html',
styleUrls: ['./program-header.component.scss']
})
export class ProgramHeaderComponent implements OnInit {
#ViewChild(SidebarComponent) sidebar;
constructor() { }
ngOnInit() {
}
}
header.component.html
<div (click)="sidebar.sendData()"></div>
but it isnt working Im getting this error in the console...
ERROR TypeError: Cannot read property 'sendData' of undefined
I have removed code for brevity, so please let me know if there is more information you need
Im not sure what the problem is?
EDIT
Or if anyone knows another way to call a function from a seperate component let me know
Any help would be appreciated!
Thanks!
ViewChild is expected to be used to get target element from current component's view(template) which matches the selector.
But according to your comment above, it seems there is no app-sidebar placed in your header.component.html, so ViewChild is not able to get a valid element which results in your current error.
The solution should be place app-sidebar at least once.
<app-sidebar></app-sidebar>
<div (click)="sidebar.sendData()"></div>
If header and sidebar components are siblings, you can not pass data between them directly. Take a look at ‘Output’ and ‘Input’ from angular. Alternatively, you can use a Service to pass data between components. Check out services and observables.
<parent>
<app-program-header>
</app-program-header>
<app-sidebar>
</app-sidebar>
</parent>
Just as an addition to this conversation, I have been squirreled off on a couple of occasions chasing what seemed to be the lack of a functioning #ViewChild when the cause was the #ViewChild referencing a component in a module that was not being imported. The "cannot read property of undefined" can become extremely misleading and may not be related to #ViewChild at all; be sure to check your imports first.
In my case - it was the import from "#shared" which has caused this issue. You have to pay attention, that the component you are using the #ViewChild component reference is not in the same module with same shorthand path. If it is, import the #ViewChild component from
'shared/components/sidebar/sidebar.component'
and not from '#shared'
Related
I have a legacy script that I need to include in my angular application.
The thing about this script is that it relates to a specific component, and it has to be loaded only after the view of the component is loaded.
As for today, I succeeded to include it on OnInit function but sometimes (not always for some reason) the CLI throws an error about it.
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-player-page',
templateUrl: './player-page.component.html',
styleUrls: ['./player-page.component.scss']
})
export class PlayerPageComponent implements OnInit {
public itemId: string;
constructor() {}
ngOnInit() {
//We loading the player srcript on after view is loaded
require('assets/scripts/player/player.js');
}
}
The script assumes that elements in the UI exists and it searches them by the id.
When adding it on top of the page, it doesn't work.
What is the best way to achieve the desired behavior?
There are multiple solutions to this issue.
declare the require const on top of your component
declare const require: any;
import { Component, OnInit } from '#angular/core';
#Component({})
...
use the dynamic import() function from typescript
ngAfterViewInit() {
//We loading the player script on after view is loaded
import('assets/scripts/player/player.js');
}
change the library to only start running after you call a function from the component, this way you can add it to the scripts array of your angular.json
I am having trouble trying to get the queryparams into a component. For now, I just want to console.log(...) it.
I am using the ActivatedRoute from #angular/router for this task.
I am redeveloping a certain platform for work so unfortunately some irrelevant code will have be to substituted with "..."
My Component.ts code:
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import { RelevantReportService } from './../reportServices/relevantReports.service';
import { ActivatedRoute ,Params, Router } from '#angular/router';
#Component({
selector: 'vr-reports',
templateUrl: './reports.component.html',
styleUrls: ['./reports.component.scss'],
providers: [RelevantReportService],
encapsulation: ViewEncapsulation.None
})
export class ReportsComponent implements OnInit {
reportSections: any;
constructor( private relevantReportService: RelevantReportService,
private router: Router,
private activatedRoute : ActivatedRoute
) { }
ngOnInit() {
...
console.log(this.activatedRoute.queryParams.value.reportName)
// console.log(this.activatedRoute.queryParams._value.reportName)
}
...
}
When I do console.log(this.activatedRoute.queryParams.value.reportName), the console spits out the queryparams (which is exactly what I wanted) HOWEVER it also says
"Property 'value' does not exist on type 'Observable' "
so I believe this not the correct way of tackling it.
It's observable in order to be able to monitor for changes in the params (by subscribing to observable). To get currently passed query params use:
this.activatedRoute.snapshot.queryParams
You could also use ActivatedRouteSnapshot instead of ActivatedRoute
Nothing surprising there!
activatedRoute.queryParams is an observable, and therefore you need to subscribe to it as per https://angular.io/api/router/ActivatedRoute#queryParams
You need to do the following :
ngOnInit() {
this.activatedRoute.queryParams.subscribe(values => {
console.log(values);//Which will print the properties you have passed
});
}
For Angular5 i would say the best option is using URL tree.
Since a router state is a tree, and the URL is nothing but a serialized state, the URL is a serialized tree. UrlTree is a data structure that provides a lot of affordances in dealing with URLs
Details
https://angular.io/api/router/UrlTree
I am following the component-relative paths angular documentation.
Per the instructions, I am keeping my component ts and component html in the same directory. For the purpose of this question, that directory is /app/components/first
My component ts contains the following code, and is named First.component.ts
import {Component, Injectable} from '#angular/core';
import {MyService} from "../../service/MyService";
import {ValueObj} from "../../value/ValueObj";
#Component({
moduleId: module.id,
selector: 'relative-path',
templateUrl: 'First.component.html',
providers: [MyService]
})
#Injectable()
export class FirstComponent {
public valueObjs: ValueObj[];
constructor(private _myService: MyService) {}
getAllItems(): void {
this._myService.getAllValueObjs().subscribe(
data => this.valueObjs= data, error=> console.log(error),
() => console.log("getting complete"));
}
}
My First.component.html contains the following code:
<form (ngSubmit)="getAllItems()">
<label>Value Objects</label>
<button type="submit">Search</button>
</form>
<hr>
<p style="color:red">{{error}}</p>
<h1>All Objects</h1>
<div *ngFor="let valueObj of valueObjs">{{valueObj.name}}</div>
I have looked at this solution, The selector "my-app" did not match any elements , however, I do not find it applicable. My main.ts file only contains this:
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app.module';
// start the application
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
and has no reference to BrowserModule. I also looked at why getting the error "The selector did not match any elements"? , however, I have no boot.ts file to compare my values.
Lastly, I have looked at Angular2 CLI build EXCEPTION : The selector "app-root" did not match any elements , however, I have no reference to angular-universal in my packaging.json.
Any idea as to what I am doing wrong?
I just had the same issue, the example is a bit confusing. The line:
selector: 'relative-path',
is just an example name of your component, it is not a command to use relative paths.
Simply adding the moduleId line will make relative paths work, so you can leave your selector as "first-component" or whatever you had before.
The error message is actually trying to say that the saying that the selector you have now ("relative-path") does not match what is in the index.html file. This only happens when you change the component that is in the bootstrap entry in app.module. If you had the same issue with a non-bootstrap module, you get a much clearer error message.
First of all, sorry for the weird title. I couldn't find a better summary.
So what I want is to create an application which consists of a basic 3 part structure (header / content / footer). Depending on the route that is active, the header part should change (every header is a component on its own).
So far the header that is displayed is determined by an [ngSwitch] statement in the "mainApp.component" and looks like this:
<span [ngSwitch]="currentLocation">
<span *ngSwitchWhen="'/login'"><login-header></login-header></span>
<span *ngSwitchWhen="'/home'"><home-header></home-header></span>
</span>
<div class="content"><router-outlet></router-outlet></div>
<app-footer></app-footer>
Where the "mainApp.component.ts" looks like this:
import { Component, OnInit } from '#angular/core';
import {ROUTER_DIRECTIVES} from '#angular/router';
import {HomeHeaderComponent} from './home-header';
import {LoginHeaderComponent} from './login-header';
import {FooterComponent} from './footer';
import {Router} from '#angular/router';
#Component({
moduleId: module.id,
selector: 'app',
templateUrl: 'mainApp.component.html',
styleUrls: ['mainApp.component.css'],
directives: [HomeHeaderComponent, LoginHeaderComponent, FooterComponent, ROUTER_DIRECTIVES],
providers: []
})
export class mainApp implements OnInit {
public currentLocation: string;
constructor(public router: Router) {
this.currentLocation = location.pathname;
}
ngOnInit() {
}
}
This works just fine when I go to my localhost:4200/login or localhost:4200/home manually as it renders the mainApp.component, checks which is the current route and displays the header accordingly. However, when I change the route by e.g. a button click via
<span class="btn btn-default headerButton homeButton" (click)="navigateToHome()"></span>
Where navigateToHome() is simply
navigateToHome() {
this.router.navigate(['/home']);
}
The header stays the same as the change detection doesn't take the route change into concideration and therefore doesn't rerun the [ngSwitch] / rerender the mainApp.component.
I already thought about including the header inside the components template they belong to but if there is a way to do it in the main component I would prefer that.
If you guys got any idea of how to solve this, or ways to do it better / another way / the way its best practice I'm glad to hear of it.
Thanks.
The answer here is to subscribe to the router events like so:
this.router.events.subscribe((e) => {e instanceof NavigationEnd? this.currentLocation = '/' + e.url.split('/')[1] : ''});
This subscribes to the routers events in general and changes the currentLocation variable whenever the navigation ends succesfully by checking the url property of the returned value.
I implemented this in the (at the time of this post) current router version #angular/routerv3.0.0-alpha8 which works just fine.
I have an app that is structured like this.
<app>
<header>
<component-a></component-a>
</header>
<div>
<router-outlet></router-outlet>
<component-b></component-b> <!-- loaded through router -->
</div>
</app>
In component-b some data is retrieved and later set to a new object. For example, something like this..
{
infoThatComponentANeeds : true,
someMoreInfoAddedFromCompB : []
}
This info is needed for the template in component-a. At first I tried to pass the info up to component-a via #Outuput and eventEmitter using a custom event. But I was unable to get it to bubble up. I suspect that has to do with it being loaded through the router. So now I am trying to share the data between the two components using a shared service.
My Service So Far:
import {Injectable} from 'angular2/core';
#Injectable()
export class SharedService
{
public spec:Spec= null;
public getSpec()
{
return this.spec;
}
public setSpec(spec:Spec)
{
this.spec = spec;
}
}
This is how I am trying to use it in component-a:
ngDoCheck()
{
if(this._sharedService.spec)
{
this.spec= this._sharedService.getSpec();
}
}
The Problem:
After the spec is set in component-b and ngDoCheck from component-a checks to see if the spec has been set. It comes back as undefined so the getSpec() function does not run, and no spec is returned. So I am not sure what I am missing, or why it would still be undefined in my SharedService after it has been set. Should the shared service keep a reference to what was set? Or am I completely misunderstanding this concept?
I have also explored ways of sharing this data via promises/observables. However I have not had any luck with that either. And the majority of the examples I have found use HTTP, which I really do not need at this point.
Update:
Here is some more info.
Boot.ts
import {bootstrap} from 'angular2/platform/browser';
import {HTTP_PROVIDERS} from 'angular2/http';
import {
ROUTER_PROVIDERS,
APP_BASE_HREF,
Location,
LocationStrategy,
HashLocationStrategy,
PathLocationStrategy
} from 'angular2/router';
import {AppComponent} from './components/app.component';
import {SharedService} from './services/shared.service';
bootstrap(<any>AppComponent, [
ROUTER_PROVIDERS,
SharedService,
provide(LocationStrategy, {useClass: PathLocationStrategy})
]);
AppComponent.ts
import {AfterViewInit, Component, OnInit} from 'angular2/core';
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {ComponentA} from './componentA';
#Component({
selector : 'body',
directives : [ComponentA, ROUTER_DIRECTIVES],
template : `
<app>
<header>
<component-a></component-a>
</header>
<div>
<router-outlet></router-outlet>
</div>
</app>
`
})
#RouteConfig([
{path: '/:appName/', name: 'ComponentB', component: ComponentB}
])
export class AppComponent
{
constructor(){}
}
Update 2:
Here is a plunker I created to try to isolate the issue. I removed the router stuff and simplified the whole thing. I am still seeing the same result..
https://plnkr.co/edit/kx6FWWGS1i04bH5J9DXM?p=preview
Watch the console.log()
Fixed:
This was the key.
Be sure to remove configurations in the providers attribute of your
two components.
Perhaps your service isn't actually shared. I mean you could have two instances according to the way you configured providers.
To be sure, just add the service when bootstrapping your application:
bootstrap(AppComponent, [ SharedService ]);
Be sure to remove configurations in the providers attribute of your two components.
If you're interested in hierarchical injectors of Angular2 (the concept behind this), you could have a look at this question:
What's the best way to inject one service into another in angular 2 (Beta)?