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
Related
I have a few components; I'm using Injector in constructor for encapsulation
import { Component, Injector, OnInit } from '#angular/core';
#Component({
selector: 'app-base',
templateUrl: './base.component.html',
styleUrls: ['./base.component.css'],
})
export class BaseComponent implements OnInit {
some = '';
constructor(injector: Injector) {
this.some = injector.get(this.some);
}
ngOnInit(): void {}
}
I'm using BaseComponent in other Component
import { BaseComponent } from '../base/base.component';
#Component({
selector: 'app-base-state',
templateUrl: './base-state.component.html',
styleUrls: ['./base-state.component.css'],
})
export class BaseStateComponent extends BaseComponent implements OnInit {
constructor(injector: Injector) {
super(injector);
}
ngOnInit(): void {}
}
BaseStateComponent I'm going to use in others component; Question is:
Is there any way, to make injector in BaseComponent or BaseSateComponent Optional;
I have a case, when I need a component, but I don't need an injector of it;
I know about feature
constructor(#Optional(), #Self() etc...);
But truly to say, I can't understand how it work's; I will be grateful if somebody can explain it Decorators;
stack
The problem is that you want to use #Optional #Self to make injector optional. But, #optional #self works well with something that is injectable which you can provide in providers array at some level. In Angular, services are injectable. So you can use #Optional #Self #SkipSelf with services or something injectable
When you use constrocutor(private someService:SomeService), Angular will check whether SomeService is provided at componoent level means in #component's providers, if not, is it provided at Module Level, if not , is it provided at Root level? This way angular checks entire Injector tree.
When you use #Optional, it will not perform this check and so on....
Keep in mind that Injector resolves a token into a dependency.
Answer to your question
You can use optional parameter approach in typescript by simply providing ? mark next to parameter as shown below,
export class BaseComponent implements OnInit {
some = '';
constructor(private injector?: Injector) { // ? to make parameter optional
this.some = injector.get(this.some);
}
ngOnInit(): void {}
}
it also makes sense because you are using OOPs over classes.
Forked Stackblitz (Fixed some other issues also)
I'm working on rewriting an AngularJS application in Angular8. I have read of the different ways to communicate between components but can't seem to find the proper way to achieve this given my current requirements.
I currently have 2 sibling components which both use a common service that handles basic crud functionality. The form component calls a create method in the service and the list component calls a fetch method.
<page-component>
<form component></form component>
<list-component></list-component>
</page-component>
In AngularJS this would have been achieved by using a $scope.broadcast() and $scope.on() which is the effect that I'm looking to reproduce.
What I'm trying to figure out is the best way for the form component to emit an event to which the list component would (i assume) subscribe in order to tell it to refresh itself.
To be clear, I don't want to pass the updated values to the list component. I would like to simply communicate to it that its dataset has been updated and that it needs to re-fetch its records.
I have tried using an #Output in my form component:
export class FormComponent implements OnInit {
#Output() valueChange = new EventEmitter();
onUpdate() {
this.valueChange.emit(true);
}
...
}
But I'm confused on how to implement the event listener as all the documentation I have read seems to point to events passed between parents and children and not to siblings as it were. Also the examples I have seen all seem to be focused on passing the actual data to the component instead of simply having it watch for an event and do the work on its own, as I require.
I have also seen methods that use #Input which require parameters to be passed into them on declaration. Somehow I feel that these components should be able to work on their own and not depend on this.
I believe there are multiple ways to achieve this.
In case of unrelated components, like the sibling components, one method would be to use a Subject Observable of RxJS module in the common service to which Form component will push a new value that can be subscribed to by the List component.
Service:
import { Subject } from 'rxjs/Subject';
#Injectable()
export class SampleService {
public triggerSource = new Subject<boolean>();
public trigger$ = this.triggerSource.asObservable();
}
Form Component:
import { SampleService } from '../services/sample.service';
#Component({
selector: 'form-component',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css'],
})
export class FormComponent implements OnInit {
constructor(private _sampleService: SampleService) {
}
private setTriggerState(state: boolean) {
this._sampleService.triggerSource.next(state);
}
}
List Component:
import { SampleService } from '../services/sample.service';
#Component({
selector: 'list-component',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css'],
})
export class ListComponent implements OnInit {
constructor(private _sampleService: SampleService) {
this._sampleService.trigger$.subscribe(value => {
if (value) {
// value is true (refresh?)
} else {
// value is false (don't refresh?)
}
});
}
}
And since we are returning an Observable, there are numerous operators to refine the outflow of data. For eg., in your case operator distinctUntilChanged() can be used to avoid pushing redundant information. Then the declaration of the observable would be
public trigger$ = this.triggerSource.asObservable().distinctUntilChanged();
Answer to this question could be a topic for a nice holywar, you know )
My opinion based on experience is in following:
Let's assume user typing something and than performs refresh page. Ask ourself what should happen?
If User's changes should recover to "before editing" state - than the parent component is enough to share such events.
If User's changes should be as it was before refresh page action, so if you need to store it localStorage or database or elsewhere, than it's better to involve services for it.
Create a #Input property in your ListComponent and pass all the items that list component will render from PageComponent. Whenever create component emit a value fetch the new list in PageComponent and update passed property.
Implement the OnChange lifecycle hook in ListComponent and capture the property change in ngOnChange(changes) method.
If you don't like to pass the list from PageComponent then there are few options that can be used to solve the issue.
Option 1:
instead of passing a list:Array<any> pass a list:Observable<Array<any>> and subscribe the observable in your ListComponent so technically you http call will happen inside list component. But you have to reassign the list with new Observable<Array<any>> everytime FormComponent emit a value so ngOnChange will notified.
Option 2 :
You can pass a Subject to ListComponent as a propery and Subscribe to subject in ListComponent. Then you can fetch the product in ListComponent. When ever your FormComponent emit value call next() method in subject you passed. Every time you call next method your subscribles will notify and you can fetch the details from API.
reference links
https://angular.io/api/core/OnChanges
https://angular.io/api/core/Input
#Component({
//Component Metadata
})
export class ListComponent implement OnChange{
#Input() listOfAny:Array<any>=[];
ngOnChanges(changes: SimpleChanges) {
this.listOfAny =changes.listOfAny.currentValue
}
}
#Component({
//Component Metadata
})
export class FormComponent {
#Output() valueChange = new EventEmitter();
onUpdate() {
this.valueChange.emit(true);
}
}
#Component({
//Component Metadata
template:`
<page-component>
<form component (valueChange)="valueChanges($event)" ></form component>
<list-component [listOfAny]="list"></list-component>
</page-component>`
})
export class PageComponent {
list=[]
valueChanges(newValue){
// you can fetch items from API if needed.
this.list.push(newValue)
}
}
Have a look at the Rxjs Subjects. Subjects makes the inter-components communication easier.
Since Forms and List component use a common CRUD service in your case, you can have a subject (say, newRecordSubject) and push any new records created into that subject. Now the List component should subscribe to this subject to be notified of any new records whenever it is created by the Forms component.
Have a look at the sample CRUD service below. I beleieve everything is very much self explainatory from the code snippets below.
crud.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
import { Record } from './record.model';
#Injectable({providedIn: 'root'})
export class CrudService {
private newRecordSubject = new Subject<Record>();
get newRecordListener() {
return this.newRecordSubject.asObservable();
}
public insertRecord(record: Record): void {
// logic to pass the record to the backend
this.newRecordSubject.next(record);
}
public fetch(): Record[] {
// logic to fetch the inital records from the backend
return [];
}
}
Now in your List component, inject this CRUD service and subscribe to the newRecordsSubject as below.
list.component.ts
import { OnInit, Component, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs';
import { CrudService } from './crud.service';
import { Record } from './record.model';
#Component({
selector: 'list-component',
template: ''
})
export class ListComponent implements OnInit, OnDestroy {
records: Record[];
private sub: Subscription;
constructor(private service: CrudService) {}
ngOnInit() {
this.records = this.service.fetch();
this.sub = this.service.newRecordListener.subscribe(record => this.records.push(record));
}
ngOnDestroy() {
if (this.sub) {
this.sub.unsubscribe();
}
}
}
Note:
I'm converting Subject to Observable so that the subject is available as read-only to methods outside Crud Service.
It is always a best practice to unsubscribe to the Subscriptions of Subjects and Observables, to avoid memory leaks.
Hope this helps! Cheers and Happy Coding.
You can create a function in parent that sets the value in the child component that you need.
In the component
<page-component>
<form component (value change)="formValueFunction($event)" emitedformValue="formEmitiedValue"></form component>
<list-component (list change)="listValueFunction($event)" emitedListValue="listEmitedValue"></list-component>
</page-component>
In the component you can set the value to the other components like this
formEmitiedValue:any;
listEmitedValue:any;
formValueFunction(event){
this.formEmitiedValue= event;
}
listValueFunction(event)
{this.listEmitedValue=event;
}
check the event value using logs and if there are changes in form input use OnChange event hook in the components
When I was migrating my app from AngularJS to Angular 2+. I choose to use RxJS, super easy to use, but there are learning curve.
You create service and RxJS BehaviorSubject. Any component can now subscribe for changes if needed, and any component can send updated data using .next() method.
This is your service:
import {Injectable} from "#angular/core";
import { BehaviorSubject } from "rxjs/BehaviorSubject";
#Injectable()
export default class yourService {
valueChange = new BehaviorSubject<object>({});
//add service functions if needed
...
}
This is your component:
import { Component, OnInit, OnDestroy } from "#angular/core";
import YourService from "../path to your service";
#Component({
selector: "name of component",
template: require("your.html")
})
export class nameOfComponent implements OnInit, OnDestroy {
private sub;
privat val = "Your value";
constructor( private YourService: YourService) {
}
ngOnInit() {
this.sub = this.YourService.valueChange.subscribe( (value) => {
this.YourService.valueChange.next(this.val); // If you need to pass some value to the service.
this.val = value;
})
}
ngOnDestroy() {
this.sub.unsubscribe();
}
I am trying to write a service in which some events are triggered via respective methods in it so that any component in my application can listen to it.
So, I created a custom event in a service as follows
export class EventsService {
#Output() expandNav: EventEmitter<Number> = new EventEmitter();
trigExpandNav() {
this.expandNav.emit(1);
}
constructor() { }
}
Then I included this service as a provider in my component in which I wanted to emit this event via the trigExpandNav method as follows :
import {
Component,
OnInit,
Output,
EventEmitter
} from '#angular/core';
import {
EventsService
} from '../../services/events.service';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css'],
providers: [EventsService]
})
export class HeaderComponent implements OnInit {
eventsService: EventsService;
fun() {
this.eventsService.trigExpandNav();
}
constructor() {}
ngOnInit() {}
}
But I am getting this error
ERROR in src/app/layout/header/header.component.ts(21,7): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'EventEmitter<Number>' has no compatible call signatures.
I don't know where I am doing it wrong, so please help and Thanks in advance.
IMPORTANT
What I am trying to achieve is :
Suppose I have 2 components. I want to trigger an event from 1st component when a button or link is clicked and listen that event in 2nd component from the html file like this sda
For that I made a service in which I am emitting the event through a method. The thing is it's working as expected.
You shouldn't be using event emitters for Service's this is a better place for a RxJS BehaviorSubject or Subject. depending on your need.
#Ouput()s and EventEmitters are for Components only
So your service should become something along the lines of:
export class EventsService {
private expandNavSubject = new BehaviorSubject<number>(null);
trigExpandNav() {
this.expandNavSubject.next(1);
}
get expandNav$(){
this.expandNavSubject.asObservable();
}
constructor() { }
}
}
then in your header component inject the service via the constructor with:
constructor(private eventsService: EventsService){}
and call the trigger expand nav function with this.eventsService.trigExpandNav(); in your fun() function
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'
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.