ngOnInit and constructor who run first - javascript

When I call my service injected in the constructor, I get undefined.
the service is called in ngOnInit method, From Difference between Constructor and ngOnInit I have seen that constructor run first , but in my case I noted the opposite, so I'm bit confused. have someone more explication about that, thanks.
constructor(private curveService :ProgressCurveService, private util : UtilService) {
this.startPickerOptions = new DatePickerOptions();
this.endPickerOptions = new DatePickerOptions();
//this.datePickerOptions.initialDate = new Date(Date.now());
}
ngOnInit() {
this.curveService.instance.getCurve(this.startDate.formatted,this.endDate.formatted,this.amplutid).
then(res => {
this.lineChartLabels = this.util.dateToShortString(Object.keys(res.progressPlotData))
this.lineChartData = this.util.objectToIntArray(res.progressPlotData);
}).catch(res => console.log('error if date selecting ...'));
}
progress curve service:
import { progressCurveItf } from './progress-curve/progress-curve-interface';
#Injectable()
export class ProgressCurveService {
state : string = 'project';
constructor(private prsCurve : PrsProgressCurveService, private projCurve : ProjProgressCurveService) { }
get instance():progressCurveItf{
if(this.state == 'subproject'){
return this.prsCurve;
} else {
return this.projCurve;
}
}
}

While you're returning an instance of type progressCurveItf that is an interface I think there is something wrong with the instantiation of what you're returning,
check if you provide PrsProgressCurveService and ProjProgressCurveService.

To answer your question Constructor will get invoked since it belongs to ES6, basically which got the first priority. Where as ngOnInit is a life cycle hook designed by angular team which will get invoked after constructor even after ngOnChanges life cycle hook.
Constructor -> ngOnChanges -> ngOnInit -> followed by other life cycle hooks

Related

Make setter an action using Mobx makeObservable in presence of getter

In mobx if I want to use interheritance I need to use makeObservable rather than makeAutoObservable. But using makeObservable requires I name the actions that mutate state so how can I declare a setter to be an action given it has the same name as the getter?
In other words what goes where I wrote SETTER_FOR_MYVAR or what is another way to achieve the same effect?
class BaseClass {
_myvar = null
set myvar(val) {
this._myvar = val;
}
get myvar() {
return this._myvar;
}
other_action() {
this._myvar = 5;
}
constructor() {
makeObservable(this, {
_myvar: observable,
other_action: action,
SETTER_FOR_MYVAR: action
});
}
}
Yes, I know I could farm it out to yet another helper function _myvar_setter and declare that an action but that seems ugly and I'm hoping there is a better way.
Just mark myvar as computed, everything should work out of the box (If I understand correctly what you want):
constructor() {
makeObservable(this, {
_myvar: observable,
myvar: computed,
other_action: action
});
}
Codesandbox
Excerpt from the docs:
It is possible to define a setter for computed values as well. Note that these setters cannot be used to alter the value of the computed property directly, but they can be used as an "inverse" of the derivation. Setters are automatically marked as actions.
Example:
class Dimension {
length = 2
constructor() {
makeAutoObservable(this)
}
get squared() {
return this.length * this.length
}
set squared(value) {
this.length = Math.sqrt(value)
}
}
More info in the docs

How to avoid writing same http subscribe blocks for different components?

Let's assume that there is a service will be used for http request calls.And two different components(could be more than two) which will send same request by using same observables via this service.After getting result that should be assigned to global variable(Components have not relationship like parent-child or child-parent).Below I wrote same code block for all components.Is there any better way to write this function once and call by returning same value?
Service
getStudents() {
const requestUrl = this.apiUrl + 'students/';
return this.httpClient.get(requestUrl);
}
Component1
studentList:Student[]=[];
getStudents.subscribe((students:Student[])=>{
this.studentList=students;
//Some operations
})
Component2
studentList:Student[]=[];
getStudents.subscribe((students:Student[])=>{
//Some operations
this.studentList=students;
})
I'm not a fan of global state, but if you want to maintain the same list of students across components using global state, then that state may as well live in the service (Rather than existing in each component separately)
So, for example:
Service
studentList:Student[] = [];
setStudents(students:Student[]) {
this.studentList = students;
// Operations involved with setting students
}
updateStudents() {
const requestUrl = this.apiUrl + 'students/';
return this.httpClient.get(requestUrl).pipe(
tap(this.setStudents)
);
}
Component
ngOnInit(){
this.service.updateStudents().subscribe();
}
You can have an Observable inside your service,
studentsReceived:Subject = new Subject();
on success of getStundent() you can emit next value of studentsReceived.
Now you can subscribe to the studentsReceived inside your components, after the successful API call you will be notified of each of the subscribed components.
studentRecived.subscribe(data=>{ // do some code })
You must call this getStudent() on some higher component like AppComponent.
2 Important things here:
1) If you dont want to repeat the same block of code, then create a method in the service file,
and call it in the component. Something like this:
SERVICE:
makeSubcription(componentVariableName) {
this.yourObservable.subcribe(subscribedValue => {
componentVariableName = subscribedValue;
})
}
In your Component, you can do this:
yourComponentMethod() {
this.service.makeSubscription(this.studentLists);
}
************
2) If you dont want to make a service call too many times, what you can do is,
use Behavior Subject and try to store the values, so that you are subscribing to the observable and not the actual API call. Something like this:
private initialValuesForObservable: YourObjectModel = {}; // or null;
private observableSource: BehaviorSubject<YourObjectModel> =
new BehaviorSubject<YourObjectModel>(this.initialValuesForObservable);
public subscribableObservable: Observable<YourObjectModel> =
this.observableSource.asObservable();
setObservableValue(data: YourObjectModel) {
this.observableSource.next(data);
}
getObservableData() {
return this.subscribableObservable;
}
In your COMPONENT;
this.service.getObservableData().subscribe(latestObservableData => {
this.schoolList = latestObservableData;
});

How to share data between sibling components in Angular

I am still learning and I got stuck so I need to ask a question. My understanding of Input Output decorators is that I need to add selector to html of parent to be able to use them, but for my case I don't think it's the way to go, but someone can prove me wrong.
CASE: For readability purposes I have split components. I have one component, data-fetch-transform that gets the data form local JSON file and does some adjustments to it, and another one, that wants to take that data for further use.
PROBLEM: I am unsure how to read the data from one component in the other. On the example below, how can I get countryNumber and centerNumber result in my other component. I intend to have data-fetch-transform.component.ts just manipulate the data and used in other components
Target component
project/src/app/data-use/data-use.component.ts
Data Source component
project/src/app/data-fetch-transform/data-fetch-transform.component.ts
import { Component, OnInit } from '#angular/core';
import * as data from '../../../../../data/Data.json';
#Component({
selector: 'app-datafetch-transform',
templateUrl: './datafetch-transform.component.html',
styleUrls: ['./datafetch-transform.component.css'],
})
export class DatafetchComponent implements OnInit {
public dataList: any = (data as any).default;
dataPointCount = this.data.length!!;
uniqueValues = (dt: [], sv: string) => {
var valueList: [] = [];
for (let p = 0; p < this.dataPointCount; p++) {
valueList.push(dt[p][sv]);
}
var uniqueValues = new Set(valueList);
return uniqueValues.size;
};
countryNumber=this.uniqueValues(this.dataList, 'Country')
centerNumber=this.uniqueValues(this.dataList, 'Center Name')
constructor() {}
ngOnInit(): void {}
}
You don't need another component for data manipulation (data-fetch-transform), you need a service (data-fetch-transform-service) where you should do the logic.
HERE IS WHAT YOU SHOULD HAVE IN THE SERVICE
private _dataList = new behaviorSubject([]);
public dataList$ = _dataList.asObservable();
for (let p = 0; p < this.dataPointCount; p++) {
// ... do your thing
_valueList.next(result);
}
and in the component you just subscribe to the service:
declarations:
private _subscription = new Subscription()
in constructor:
private dataService:DataFetchTransformService
and in ngOnInit:
this_subscription.add(this.dataService.dataList$.subscribe((response:any)=>{
this.data = response;
}))
in ngOnDestroy():
ngOnDestroy(){
this._subscription.unsubscribe();
}
I strongly suggest to stop using any since it can bring a lot of bugs up.
Also, as a good pattern, I always suggest use behaviorSubject only in the service as a private variable and user a public observable for data.
WHY IS BETTER TO USE A SERVICE
You can subscribe from 100 components and writing only 4 lines of code you bring the data anywhere.
DON'T FORGET TO UNSUBSRIBE IN ngOnDestroy
If you don't unsubscribe, you'll get unexpected behavior.

How do I properly handle change detection on a component that relies on a ViewChild and data from an observable in Angular 2?

So presently, I have a component that fits into a larger dashboard for rendering a graph of a node's immediate parent and child relationships. This component is supposed to refresh its graph every time the node_id input is changed externally.
I've included a simplified version of my code.
#Component({
selector: 'relations',
template: `
<div [class]="'panel panel-' + (_loading ? 'default' : 'primary')">
<div class="panel-heading">Child relations</div>
<div class="panel-body">
<div class="loading" *ngIf="_loading" style="text-align: center">
<img src="./loading.gif" height="100px" width="100px" />
</div>
<div class="graph_container" [style.display]="_loading ? 'none': 'block'" #my_graph></div>
</div>
</div>
`
})
export class GraphComponent implements OnChanges {
#Input('node_id') node_id;
#ViewChild('my_graph') graphDiv;
private _loading: boolean = true;
private _current_node: Node;
private _parent: Node;
private _children: Node[];
constructor(
private _nodeService: NodeService
) {}
ngOnChanges(changes){
this.getRelations();
}
getRelations() {
this._loading = true;
Observable.combineLatest(
this._nodeService.getEvent(this.node_id),
this._nodeService.getChildren(this.node_id),
this._nodeService.getParent(this.node_id)
).subscribe(v => {
this._current_node = v[0];
this._children = v[1];
this._parent = v[2];
this._loading = false
this.renderGraph();
});
}
renderGraph() {
...
}
}
Now the issue I'm having is a race condition; the renderGraph() method relies on the #ViewChild('my_graph') graphDiv variable to know where it should drop the canvas element for rendering the graph. Because of this, when the observable resolves, it may try to call renderGraph() before the #ViewChild component has initialized.
I've tried playing with the lifecycle hooks by doing things such as:
ngAfterViewInit(){
if (!this._loading){
this.renderGraph();
}
}
That only helps if the observable finishes before the view is loaded, and causes no graph to be rendered should the view finish rendering first.
So my question is, how can I properly achieve what I want? That is to say, re-rendering the graph following the observable resolving in response to a change to node_id.
I'm very new at Angular 2 (and front end in general), and my intuition tells me I'm not using the observable in a way it's intended to be used, but I've had difficulty in finding any examples similar to what I want.
Any help/guidance/advice would be greatly appreciated.
Thanks!
I would use BehaviorSubject which is just a special type of Observable. Snippet from the docs:
It stores the latest value emitted to its consumers, and whenever a new Observer subscribes, it will immediately receive the "current value" from the BehaviorSubject.
The reason for preferring BehaviorSubject is because it always emits the last node_id value no matter when the subscription actually happens. In case it was set before viewInit. Also, because it will always have the latest value, we don't need to have node_id property on GraphComponent. We just need a setter for it that will emit the passed value to subscribers and automatically keep it saved on the subject, so every new subscriber will get the current value.
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
...
export class GraphComponent implements OnChanges {
#ViewChild('my_graph') graphDiv;
...
private _nodeIdSubject = new BehaviorSubject(-1);
constructor(...) {}
#Input('node_id')
set node_id(id){ // this is the same as ngOnChanges but will only be triggered if node_id changed
this._nodeIdSubject.next(id);
}
ngAfterViewInit(){ // subscribe to node_id changes after view init
this._nodeIdSubject.filter(n=> n > -1).subscribe(nodeId=> this.getRelations(nodeId));
}
getRelations(nodeId) {
...
}
renderGraph() {
...
}
}
This is probably not be the best approach, but I like it because now you have a stream of node_id that you can manipulate freely.

How do I update a value in an AngularJS (with TypeScript) controller when a value on an injected service updates

At the moment I am trying to learn Typescript and AngularJS coming from a background of Actionscript. I have been playing around with some code experiments and van generally achieve what I want to but not always in the way that I would like!
At the moment I have this code:
export class CountingService
{
countScope = { count : 0 };
public increment()
{
this.countScope.count++;
}
}
export class PageController
{
constructor( private service : CountingService )
{
this.countScope = service.countScope;
}
public localCount : number = 0;
countScope;
public increment()
{
this.service.increment();
this.localCount++;
}
}
and I have a few different views that have this code in:
<div ng-controller="PageController as page">
<Button ng-click="page.increment()">Count {{page.localCount}}/{{page.countScope.count}}</Button>
</div>
When I switch between the views the localCount always resets to zero but the count in the countScope from the service does not reset and is incremented by each different controller.
This works but it's a bit messy. I have to have the untyped localScope floating around and the views need to know about the internal structure of the countScope object and bind to page.countScope.count rather than something like page.globalCount.
In Actionscript I would have a read only getting on the controller. The service would dispatch an event each time the value changed and the controller would listen for this and then update it's own property which would then update the view.
My question is what is the best practice way to achieve what I am doing here that does not require the view to have knowledge of the internals of an untyped object.
I am pretty sure that public getters do not exist but can I dispatch and listen for an event?
Many Thanks
Finally answer three. I didn't expect this to work but it does and IMHO this is by far the nicest solution.
It enforces encapsulation with getters, it doesn't need $scope injected and there are no concerns about memory leaks.
I welcome any criticisms that point out issues that I am not aware of.
The only thing that I AM aware of is that you need to enable ECMAScript 5 which means that IE 7 and 8 are not supported. That's fine for me.
export class CountingService
{
private _serviceCount : number = 100;
public increment()
{
this._serviceCount++;
}
get serviceCount() : number
{
return this._serviceCount;
}
}
export class PageController
{
constructor( private countingService : CountingService )
{}
private _localCount : number = 0;
get localCount() : number
{
return this._localCount;
}
get serviceCount() : number
{
return this.countingService.serviceCount;
}
public increment()
{
this.countingService.increment();
this._localCount++;
}
}
This seems like the perfect fit for a reactive library and is exactly what we do. I highly recommend you check out RxJs which has some awesome features. https://github.com/Reactive-Extensions/RxJS/tree/master/doc
https://www.youtube.com/watch?v=XRYN2xt11Ek
You code using Rx
export class CountingService {
//Create a stream of changes. When caller subscribe always give them the most recent value.
private rawCountStream = new Rx.Subject<number>();
public countStream : Rx.Observable<number>;
private count = 0;
constructor() {
this.countStream = this.rawCountStream.replay(1);
}
public increment() {
this.count++;
//Pump the value to callers.
this.rawCountStream.onNext(this.count);
}
}
export class PageController {
constructor(private service: CountingService) {
//Listen to the stream of changes.
service.countStream.subscribe(value => {
//do something
//IMPORTANT If you want angular to update the ui you will need to call $apply on a scope.
//RXJS has a scheduler for this so you look at that here.
//https://github.com/Reactive-Extensions/rx.angular.js
}
);
}
public localCount: number = 0;
public increment() {
this.service.increment();
this.localCount++;
}
}
This is answer number 1 and I think is the approach that John Kurlak was talking about:
export class CountingService
{
serviceCount : number = 100;
public increment()
{
this.serviceCount++;
}
}
export class PageController
{
constructor( $scope, private service : CountingService )
{
this.serviceCount = service.serviceCount;
$scope.$watch( () => this.service.serviceCount, ( newValue : number, oldValue : number ) => this.updateLocal( newValue, oldValue ) );
}
localCount : number = 0;
serviceCount : number;
public increment()
{
this.service.increment();
this.localCount++;
}
private updateLocal( newValue : number, oldValue : number )
{
this.serviceCount = newValue;
}
}
This works and I think is the sort of solution that I was after. There are things that I don't like about it though.
I don't like having to inject the $scope into my service. It seems to be an extra dependency on something that I shouldn't need.
I also really don't like the public members that can be updated by anything and break encapsulation. I want to fix this with getters but I can't figure out how to update to ECMAScript 5 (question here: Targeting ES5 with TypeScript in IntelliJ IDEA 14).
John - is this what you were proposing? If anyone comments with Pros and Cons to this answer I'd be very grateful.
I will mark this as the answer as I think this is what I was after to start with.
This is answer number 2 and I think this is what PSL was getting at (or something similar)
interface CountingCallBack
{
parent : any;
callback : ( value : number ) => void;
}
export class CountingService
{
private _observerCallBacks : Array<CountingCallBack> = [];
private _serviceCount : number = 100;
public increment()
{
this._serviceCount++;
this.notifyObservers();
}
public registerObserverCallback( callbackParent : any, callbackFunction : ( value : number ) => void )
{
var callback : CountingCallBack = { parent : callbackParent, callback : callbackFunction };
this._observerCallBacks.push( callback );
this.updateObserver( callback );
}
private notifyObservers()
{
angular.forEach( this._observerCallBacks, this.updateObserver, this );
}
private updateObserver( callback : CountingCallBack )
{
callback.callback.apply( callback.parent, [ this._serviceCount ] );
}
}
export class PageController
{
constructor( private countingService : CountingService )
{
countingService.registerObserverCallback( this, this.updateLocal );
}
localCount : number = 0;
serviceCount : number;
public increment()
{
this.countingService.increment();
this.localCount++;
}
private updateLocal( newValue : number )
{
this.serviceCount = newValue;
}
}
I'm quite pleased that I managed to implement this as I now understand how the function interface syntax works (and I quite like it) and I managed to fix the issue with the 'this' scope issue in the updateLocal function on the controller.
This solution doesn't require and $scope to be injected which is nice but I find the callback implementation quite messy. I don't like having to pass the controller and the function on the controller to the service to add a callback.
I suppose I could create an interface, ICountListener or something, and just pass that to the service and then call updateCount on the controller. That would be a bit neater but still not that good.
Is there a neater way around setting up the callback than this?
The biggest problem with this code (and a reason that it should not be used) is that it creates a memory leak. If you keep switching views the controllers never get cleaned up so you have many controllers left in memory all responding to the updated count.
It would be relatively easy to clean up the callback and de-register a callback but that would presumably involve injecting a $scope and then listening for a destroy event (I don't know how to do this but I think this was under discussion in the comments above).
Short answer: Services are singletons i.e. the constructor is only called once. If you want it reset you can do that from the constructor of your controller.
this.service.countScope.count = 0
Notes
I see a lot of things I don't like about that code sample. But I am sure its just a sample so don't take it as a personal offence ;)
don't have a service called service. Call it countService.
don't have a member countScope on the service. Just use count. And don't copy countScope to the current controller. Just use this.countService.count
Update
The root of this question is how do I update a displayed value in html when a value in a service has changed
Since you have the controller as page and page.service is the service simply {{page.service.countScope.count}} would display teh count.

Categories

Resources