Angular 2+ detect object property change inside service - javascript

Let's say we have a service like this one:
SupermanService {
private _superman: Hero;
public supermanReplaced = new EventEmitter<Hero>();
public supermanPropertyUpdated = new EventEmitter<Hero>();
public get superman(): Hero {
return this._superman;
}
public set superman(superman): void {
this._superman = superman;
this.supermanReplaced.emit(superman);
}
public updateSupermanProperty(key: string, val: string | number): Hero {
this._superman[key] = val;
this.supermanPropertyUpdated.emit(superman);
return this._superman;
}
}
Is there some way to detect supermanPropertyUpdated without using the updateSupermanProperty() function but by e.g. setting this.superman.power = 10?
I found some posts that suggest the KeyValueDiffer in combination with the DoCheck hook, but that is not available for services.

You can use get/set methods.
In your example:
class SupermanService {
private _superman: Hero;
public supermanReplaced = new EventEmitter<Hero>();
public supermanPropertyUpdated = new EventEmitter<Hero>();
public set power(level: integer) {
this._superman.power = level;
this._supermanPropertyUpdated.emit(this._superman);
}
public get superman(): Hero {
return this._superman;
}
public set superman(superman: Hero): void {
this._superman = superman;
this.supermanReplaced.emit(superman);
}
public updateSupermanProperty(key: string, val: string | number): Hero {
this._superman[key] = val;
this._supermanPropertyUpdated.emit(superman);
return this._superman;
}
}
After this you can use:
SupermanService.power = 10;
and all the listeners will be notified
Update
Another implementation to solve this problem is modifying your Hero class adding a public EventEmitter property and subscribing to this from your service. Assign a setter for each property on your Hero class and emit the change like Output and in your service you can emit the changes.
class Hero {
public onPropertyChange = new EventEmitter<Hero>();
private _name: string;
get name(): string {
return this._name;
}
set name(value: string) {
this._name = value;
this.onPropertyChange.emit(this);
}
}
class SupermanService {
private _superman: Hero;
public supermanReplaced = new EventEmitter<Hero>();
public get superman(): Hero {
return this._superman;
}
public set superman(superman: Hero): void {
this._superman = superman;
this.supermanReplaced.emit(superman);
}
public get supermanPropertyUpdated(): EventEmitter<Hero> {
return this._superman.onPropertyChange;
}
}

Related

RxJs Websocket : Object's property (object) with no defined type not sent

Using rxjs websocket, I'm facing an issue when sending data.
I always wrap the message I send into a Request object that looks like this :
export class Request {
public constructor() { }
private t: string = null;
public get type(): string {return this.t;}
public set type(type: string) {this.t = type;}
private i: string = null;
public get issuer(): string {return this.i;}
public set issuer(issuer: string) {this.i = issuer;}
private n: string = null;
public get name(): string {return this.n;}
public set name(name: string) {this.n = name;}
private c: MyCandidateClass = null;
public get candidate(): MyCandidateClass {return this.c;}
public set candidate(candidate: MyCandidateClass) {this.c = candidate;}
private cis: { [id: string]: string; } = {};
public get credentialsInfo(): { [id: string]: string; } {return this.cis;}
public set credentialsInfo(credentialsInfo: { [id: string]: string; }) {this.cis = credentialsInfo;}
}
The object is built, and my cis property is a key/value map which value can be for example {_PinCode: "1234"}.
Everything is fine in my request object, but here is what's actually sent to the websocket server :
{
cis: {},
c: candidate // <- an object of class MyCandidateClass as expected
i: "some_username",
n: "some_name",
t: "some_type"
}
I am losing the cis object.
While investigating, I noticed that if I create a class CredentialsInfo with the correct properties inside and change my cis property from Request like the following :
private cis: CredentialsInfo = null;
public get credentialsInfo(): CredentialsInfo {return this.cis;}
public set credentialsInfo(credentialsInfo: CredentialsInfo) {this.cis = credentialsInfo;}
With these modifications, it works just fine.
However, I want my cis to be key/value map as defined in my initial Request, not a defined type of my own since I can't always know what will the map keys be.
I'm guessing this has to deal with default serialization/deserialization ?
How can I achieve this, and make the initial Request work ?
Note : I'm using Angular and rxjs, here are some relevant parts of my websocket service :
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
// ...
ws: WebSocketSubject<any> = null;
// ...
connect(): void {
// ...
this.ws = webSocket(this.websocketUrl);
this.ws.subscribe(
(response) => this.onReceive(ResponseInfo.map(response)),
(err) => this.onError(err),
() => this.onClose('Complete (connection closed)')
);
// ...
}
send(request: RequestInfo): void {
// ...
this.ws.next(request);
}
// ...

Angular 7 Service Returns Undefined from restful API

I'm trying to return a list of tasks from and Angular service that gets the data from a restful .NET API. The API returns the proper data, but when console.log the result from the subscribed to function it is only undefined. Please help!
Here is the API method that is functioning correctly:
TaskController.cs
[HttpGet, Route("")]
public IEnumerable<GetAllTasksAndInfoDto> GetAll()
{
IEnumerable<GetAllTasksAndInfoDto> tasks = _taskRepository.GetAllTasksAndInfo();
return tasks;
}
I have two services on called task.service.ts and one called email-notification.service.ts. Task is to return all the tasks in a collection. Email-Notification is to retrieve the tasks from the task service, then iterate through them to check a for a specific property value in the task data.
Here is the method from task.service.ts:
export class TaskService {
constructor(private http: HttpClient, private messageService: MessageService) {}
getTasks(): Observable<getAllTasksAndInfo[]> {
return this.http.get<getAllTasksAndInfo[]>(http://localhost:xxxxx/api/task)
.pipe(
tap(_=>this.log('fetched tasks')),
catchError(this.handleError<getAllTasksAndInfo[]>('getTasks', [])));
}
I'm not sure how to check the data at this ^ point, but it's not showing an error.
Here is the code from email-notification that is returning "Undefined":
export class EmailNotificationService{
tasks: getAllTasksAndInfo[];
constructor(private http: HttpClient, private messageService: MessageService, public taskService: TaskService)
{
this.getTasks().subscribe(result => this.tasks = result);
}
getTasks(): Observable<getAllTasksAndInfo[]>{
return this.taskService.getTasks();
}
this.tasks.forEach((task) => {
console.log(`task: ${JSON.stringify(task)}`);
}
});
Here I am getting an error in the console for trying to iterate through and undefined object.
Here is my typescript class:
export class getAllTasksAndInfo {
taskId: number;
stepName: string;
officeName: string;
csrName: string;
taskType: string;
daysActive: number;
}
and here is my API class:
public class GetAllTasksAndInfoDto
{
public int taskId { get; set; }
public string stepName { get; set; }
public string officeName { get; set; }
public string csrName { get; set; }
public string taskType { get; set; }
public int daysActive { get; set; }
}
Any help appreciated. Thank you!
It might be because you have not subscribed to the Observable or the initial data that is the tasks array is preset to undefined
have you tried using rxjs to subscribe to your observable in your EmailNotificationService sometimes it might come in undefined because the initial value of the array is undefined to begin with maybe setting it up with an empty array might help
private tasks: getAllTasksAndInfo[] = [];
constructor(private http: HttpClient, private messageService: MessageService, public taskService: TaskService)
{
this.tasks = this.getTasks();
}
getTasks() {
this.taskService.getTasks().subscribe((tasks: getAllTasksAndInfo[]) => {
console.log(tasks);
return tasks;
});
}

How to inject a service in a model class with existing constructors

I have an model class with a default constructor and a constructor with parameters. I also have a service with some methods I would like to use in the model class. I have the include for the service, but when I attempt to inject the service with the constructor for the service, I get
"Multiple constructor implementations are not allowed."
Here is an example of what I have attempted:
import { MyService } from '../utilities/utils.service';
export class MyData {
private __var1: string;
get var1(): string { return this.__var1; }
set var1(val: string) { this.__var1 = val; }
private __var2: string;
get var2(): string { return this.__var2; }
set var2(val: string) { this.__var2 = val; }
// etc.
constructor()
constructor(
var1: string,
var2?: string
) {
this.__var1 = var1;
this.__var2 = var2;
}
constructor(private myService: MyService) { }; // causes error.
}
I thought this was the correct approach, obviously not.

Force the use of a factory

I have a class where I want a simple factory method:
class GTree{
public static createNode(){
return new GNode();
}
}
This means that I don't want to allow the consumer to immediately instantiate the GNode.
How do I properly implement this?
Obviously I can't do:
class GNode{
constructor(){
throw TypeError("This is nonsense");
}
}
Because then I can't create nodes anymore at all.
How do I force using the factory?
Here's a simpler scheme than my earlier comments. Just define the GNode class in a private (but shared) scope and thus that's the only place the constructor can be called from and also reset the .constructor property so it doesn't leak out:
const GTree = (function() {
class GNode {
constructor() {
}
someOtherMethod() {
console.log("someOtherMethod");
}
}
// reset public .constructor
GNode.prototype.constructor = function() {
throw new Error("Can't call GNode constructor directly");
};
class GTree {
constructor() {
this.nodes = [];
}
createNode() {
let node = new GNode();
this.nodes.push(node);
return node;
}
get length() {
return this.nodes.length;
}
}
return GTree;
})();
let tree = new GTree();
let node1 = tree.createNode();
let node2 = tree.createNode();
node1.someOtherMethod();
console.log(tree.length + " nodes");
You can't really do that in javascript, but you can do this:
export class GTree {
public static createNode(name: string): GNode {
return new GNodeImpl(name);
}
}
export interface GNode {
name: string;
}
class GNodeImpl implements GNode {
constructor(public name: string) {}
}
(code in playground)
Only GTree and the GNode interface are exported, meaning that it's not possible to instantiate GNodeImpl from outside the module.
I added the name property just for the example.

spring mvc ajax bad request error

Spring Controller Method :
#RequestMapping(value="/checklist/{id}",method=RequestMethod.PUT, consumes=MediaType.APPLICATION_JSON_VALUE , produces=MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Checklist update(#RequestBody Checklist checklist, #PathVariable("id") int id)
{
checklist.setId(id);
return service.update(checklist);
}
JavaScript AJAX code:
var checklist={name:$('#newName').val(), details:$('#newDetails').val()};
$.ajax({ //send updated item values to
method:'put',
url:'/tracker/checklist/'+$(editingItem).attr('id'),
contentType:'application/json',
dataType:'json',
data:checklist,
success:function(data)
{
console.log(data);
$('#myModal').modal('hide');
}
});
Checklist Model:
package com.tracker.web.models;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
#Entity
#Table(name="checklists")
public class Checklist {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private int item_order;
private String name;
private String details;
private String phase;
private String completed;
private String skipped_note;
private Date completed_on;
private int completed_by;
#Temporal(TemporalType.TIMESTAMP)
#CreationTimestamp
private Date created_at;
#Temporal(TemporalType.TIMESTAMP)
#UpdateTimestamp
private Date updated_at;
#ManyToOne
private Event event;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getItem_order() {
return item_order;
}
public void setItem_order(int item_order) {
this.item_order = item_order;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
public String getPhase() {
return phase;
}
public void setPhase(String phase) {
this.phase = phase;
}
public String getCompleted() {
return completed;
}
public void setCompleted(String completed) {
this.completed = completed;
}
public String getSkipped_note() {
return skipped_note;
}
public void setSkipped_note(String skipped_note) {
this.skipped_note = skipped_note;
}
public Date getCompleted_on() {
return completed_on;
}
public void setCompleted_on(Date completed_on) {
this.completed_on = completed_on;
}
public int getCompleted_by() {
return completed_by;
}
public void setCompleted_by(int completed_by) {
this.completed_by = completed_by;
}
public Date getCreated_at() {
return created_at;
}
public void setCreated_at() {
this.created_at = new Date();
}
public Date getUpdated_at() {
return updated_at;
}
public void setUpdated_at() {
this.updated_at = new Date();
}
public Event getEvent() {
return event;
}
public void setEvent(Event event) {
this.event = event;
}
}
I am using Jquery 1.11. when i use with 'GET' instead of 'PUT' 'method' on client side and 'consumes' on server side, it works. even i tried with JSON.stringify while sending sending. i am using jackson on server side to convert data into json
Which version of jquery you are using?
If you are using jquery prior to 1.9.0 than try type: 'PUT' instead of method:'put' in your ajax call and it should work. Otherwise it should be of with method:'put' .
Check documentation for more reference http://api.jquery.com/jquery.ajax/

Categories