Clone instance with getter/setter field decorations - json

I have a class which is populated as such (from json):
export class blogEntry {
hasImage: boolean;
#newItem(Video) video: Video;
#newItem(Author) author: Author;
#newItem(Comments) comments: Comments;
#newItem(Picture) blogImage: Picture;
#newItem(PictureCaption) pictureCaption: PictureCaption;
#newItem(TextSummary) summary: TextSummary;
constructor(data: blogEntry|Object) {
Object.assign(this, data);
}
}
Where the decorator is defined as such (in a seperate file):
export function newItem<T extends EditablePart>(type) {
return function(target: Object, propertyKey) {
let value: T;
Object.defineProperty(target, propertyKey, {
get: function(): T {
return value;
},
set: function(newVal: T) {
if (newVal) {
value = construct(type, newVal);
}
},
enumerable: true
});
}
}
export function construct<T extends EditablePart>(type: { new(...args : any[]): T ;}, newVal) {
return new type(newVal);
}
All annotated types extend EditablePart.
After using this class to change the data (by using the annotated fields, i.e. via the getters/setters supplied there), i want to save the class data as json to my backend server. Before introducing the decorators in the class I could just use:
publish(): blogEntry {
return new blogEntry(this);
}
Now I only get hasImage. When using developer tools in chrome I can see the fields but I have to click the dots behind them ('invoke property getter') to retrieve the data.
Any thought how to clone the class (I want to continue using the class)? Any help would be greatly appreciated!

It occurs to me use JSON.stringify(...) and a custom function that helps you resolve directives before.
The function would be a replacer function according stringify documentation.
Example: I have a component I would like to save in BD. My component have a ViewChild decorator and I want to save it too. I know that my ViewChild is an ElementRef but I wouldn't like to save a structure like this:
{
nativeElement: {}
}
The above is resulted of my JSON.stringify(...) function when it gets my ViewChild property so my replacer function comes in action.
JSON.stringify(this, function(key, value) {
const isViewChild = value.nativeElement; // This is for identifying a ViewChild.
if (isViewChild) {
return isViewChild.innerHTML; // I want to resolve it with HTML only.
}
return value;
});
Now, my viewchild looks as follow:
{
...other properties,
myViewChild: '<div>My viewchild translated to html</div>'
}
Other approach is overriding the toJSON function. Using my component again:
#Component({...})
export class MyComponent {
#ViewChild('mySon') myViewChild: ElementRef<any>;
...other properties
toJSON() {
return { myViewChild: this.myViewChild.nativeElement.innerHTML, ...otherProps };
}
}
And when you JSON.stringify the component you are going to get your component with directives resolved.
Try it!

Related

In Angular 2, how do you intercept and parse Infinity / NaN bare-words in JSON responses?

I am writing an Angular front end for an API that occasionally serves Infinity and -Infinity (as bare words) in the JSON response object. This is of course not compliant with the spec, but is handled a few different JSON libraries, albeit optionally. I have an Angular service in place that can successfully retrieve and handle any retrieved entity that does not have these non-conforming values. Additionally, I have managed to get an HttpInterceptor in place which just logs when events trickle through, just to be sure I have it connected properly.
The issue that I am facing is that the HttpInterceptor seems to allow me to do one of two things:
Catch/mutate the request before it is sent to the API, or
Catch/mutate the request after it comes back from the API, and also after it is parsed.
What I would like to do is very similar to this question for native javascript, but I have not been able to determine if it is possible to tie into the replacer function of JSON.parse in the Angular Observable pipe (I think that if tying into that is possible it would solve my issue).
I have also found this question for Angular which is close, but they appear to have been able to handle changing the response to something other than the bare-words, which I don't have the liberty of doing.
This is the current implementation of my HttpInterceptor, note that it does not actually make any changes to the body. When retrieving an entity without these bare-word values, it logs to the console and all is well. When retrieving an entity with any of these bare-word values, an error is thrown before the HERE line is hit.
function replaceInfinity(body: string): string {
// Do something
return body;
}
#Injectable()
export class JsonInfinityTranslator implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
map((event) => {
if (event instanceof HttpResponse) {
console.log("HERE");
return event.clone({body: replaceInfinity(event.body)});
} else {
return event;
}
})
);
}
}
TL;DR: Is there a way to mutate the body text of the returned response before the Angular built in JSON deserialization?
I was able to figure out how to achieve this, and it came down to:
Modifying the request to return as text instead of json
Catch the text response and replace the bare word symbols with specific string flags
Parse the text into an object using JSON.parse, providing a reviver function to replace the specific string flags with the javascript version of +/-Infinity and NaN
Here's the Angular HttpInterceptor I came up with:
import {Injectable} from '#angular/core';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '#angular/common/http';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';
#Injectable()
export class JsonBareWordNumericSymbolTranslator implements HttpInterceptor {
private static infinityFlag = '__INFINITY_FLAG__';
private static negInfinityFlag = '__NEG_INFINITY_FLAG__';
private static nanFlag = '__NAN_FLAG__';
private static replaceBareWordSymbolsWithFlags(body: string): string {
const infinityBareWordPattern = /(": )Infinity(,?)/;
const negInfinityBareWordPattern = /(": )-Infinity(,?)/;
const nanBareWordPattern = /(": )NaN(,?)/;
return body
.replace(infinityBareWordPattern, `$1"${this.infinityFlag}"$2`)
.replace(negInfinityBareWordPattern, `$1"${this.negInfinityFlag}"$2`)
.replace(nanBareWordPattern, `$1"${this.nanFlag}"$2`);
}
private static translateJsonWithFlags(substitutedBody: string): any {
return JSON.parse(substitutedBody, (key: string, value: string) => {
if (value === this.infinityFlag) {
return Infinity;
} else if (value === this.negInfinityFlag) {
return -Infinity;
} else if (value === this.nanFlag) {
return NaN;
} else {
return value;
}
});
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.responseType !== 'json') {
// Do not modify requests with response types other than json
return next.handle(req);
}
return next.handle(req.clone({responseType: 'text'})).pipe(
map((event) => {
if (!(event instanceof HttpResponse)) {
return event;
}
const substitutedBody = JsonBareWordNumericSymbolTranslator.replaceBareWordSymbolsWithFlags(event.body);
const parsedJson = JsonBareWordNumericSymbolTranslator.translateJsonWithFlags(substitutedBody);
return event.clone({body: parsedJson});
})
);
}
}

How to update the html page view after a timeout in Angular

I am trying to display a routerlink name based on a condition. I want to display the div section routerLink name if condition is true.If i check {{isNameAvailable}}, first it displays false and after this.names got the values it shows true.Since in the component getDetails() method is asynchronous this.names getting the values after html template render.Therefore this routerLink does n't display.Therefore I want to display div section after some time. (That 's the solution i have) Don't know whether is there any other solution.
This is my html file code.
<main class="l-page-layout ps-l-page-layput custom-scroll bg-white">
{{isNameAvailable}}
<div class="ps-page-title-head" >
<a *ngIf ="isNameAvailable === true" [routerLink]="['/overview']">{{Name}}
</a>
{{Name}}
</div>
</main>
This is my component.ts file
names= [];
isNameAvailable = false;
ngOnInit() {
this.getDetails()
}
getDetails() {
this.route.params.subscribe(params => {
this.names.push(params.Names);
console.log(this.names);
this.getValues().then(() => {
this.isNameAvailable = this.checkNamesAvailability(this.names);
console.log(this.isNameAvailable);
});
});
}
resolveAfterSeconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 900);
});
}
checkNamesAvailability(names) {
console.log(names);
return names.includes('Sandy');
}
async getValues() {
await this.resolveAfterSeconds(900);
}
And console.log(this.isLevelAvailable); also true. What I can do for this?
1.You do not have anything to show in the HTML only the isNameAvailable, because you do not have any assignment in the Name variable.
2.It is better to use the angular build-in async pipe,
when you want to show the returned value from observables.
3.When you are using the *ngIf directive you can skip *ngIf ="isNameAvailable === true" check because the variable is boolean type, you gust write *ngIf ="isNameAvailable", it will check also for null but NOT for undefined
It is working because the *ngIf directive is responsible for checking and rendering the UI, you can see how many times the directive is checking by calling an function and print and answer in the console.
By any chance do you have changeDetection: ChangeDetectionStrategy.OnPush docs set in component annotation? That might explain this behaviour. With it Angular run change detection only on component #Input()'s changes and since in your case there were non it did not run change detection which is why template was not updated. You could comment that line to check if that was cause of the issue. You are always able to run change detection manually via ChangeDetectorRef.detectChange() docs which should solve you problem
constructor(private cd: ChangeDetectorRef) {}
...
getDetails() {
this.route.params.subscribe(params => {
...
this.getValues().then(() => {
this.isNameAvailable = this.checkNamesAvailability(this.names);
this.cd.detectChanges(); // solution
console.log(this.isNameAvailable);
});
});
}
This stackblitz show this bug and solution. You can read more about change detection here
You could use RxJS timer function with switchMap operator instead of a Promise to trigger something after a specific time.
Try the following
import { Subject, timer } from 'rxjs';
import { takeUntil, switchMap } from 'rxjs/operators';
names= [];
isNameAvailable = false;
closed$ = new Subject();
ngOnInit() {
this.getDetails()
}
getDetails() {
this.route.params.pipe(
switchMap((params: any) => {
this.names.push(params.Names);
return timer(900); // <-- emit once after 900ms and complete
}),
takeUntil(this.closed$) // <-- close subscription when `closed$` emits
).subscribe({
next: _ => {
this.isNameAvailable = this.checkNamesAvailability(this.names);
console.log(this.isNameAvailable);
}
});
}
checkNamesAvailability(names) {
console.log(names);
return names.includes('Sandy');
}
ngOnDestroy() {
this.closed$.next(); // <-- close open subscriptions when component is closed
}

Meteor EJSON support constructors

I would like to send constructors via EJSON through methods:
server.js
Meteor.methods({
'testConstructor' (doc) {
console.log(doc) // {}
}
})
client.js
Meteor.call({ type: String })
I thought of adding the types via EJSON.addType but it currently only supports instances and not constructors. I tried to wrap the String constructor inside a class like this:
Meteor.startup(function () {
class StrWrap {
constructor (val) {
this.val = val
}
toJSONValue () {
return {
value: this.val
}
}
typeName () {
return String.name
}
}
EJSON.addType(String, function (json) {
return String
})
const str = EJSON.stringify({ type: String})
console.log(str) // {}
})
Still no chance. In a related issue it has been mentioned, that the EJSON supports String, Number etc. but I think that was targeting the instances of these classes.
I currently work this around using native JSON replacer for JSON.stringify and resolver for JSON.parse but this adds a full conversion layer to every DDP protocol interaction and I'd like to support constructors out of the box, so I can send schemas around for service discovery.

JSON.parse() returns different values for local and global variable

I am working on an Angular application, that listens to the MQTT broker. I am trying to parse the response with JSON.parse() function. It looks simple, but I can't get the correct data to my global variable.
I am publishing this data (id of light and rgb value of that light) :
[{"light":2,"color":{"r":150,"g":24,"b":24}},{"light":3,"color":{"r":150,"g":24,"b":24}},{"light":4,"color":{"r":150,"g":24,"b":24}},{"light":6,"color":{"r":150,"g":24,"b":24}},{"light":7,"color":{"r":150,"g":24,"b":24}}]
The code looks like this.
export class LightsComponent implements OnDestroy {
lightsValues;
private subscription: Subscription;
constructor(private mqttService: MqttService) {
this.subscription = this.mqttService.observe(TOPIC).subscribe((message: IMqttMessage) => {
this.lightsValues = JSON.parse(message.payload.toString());
console.log(JSON.parse(message.payload.toString()));
console.log(this.lightsValues);
});
}
The result of console.log is the weird part, as I am only assigning the same JSON.parse() to the variable as into the console.log -
https://imgur.com/RdDai1B
As you can see, the light parameter is set to 0 for all objects, rgb is set correctly.
However, when I change the lightsValues variable to local like this, I get the correct data.
export class LightsComponent implements OnDestroy {
lightsValues;
private subscription: Subscription;
constructor(private mqttService: MqttService) {
this.subscription = this.mqttService.observe(TOPIC).subscribe((message: IMqttMessage) => {
let lightsValues = JSON.parse(message.payload.toString());
console.log(JSON.parse(message.payload.toString()));
console.log(lightsValues);
});
}
With this code, the data is correct in both console.logs - https://imgur.com/p2xnju9
I need to get this correct data to my global variable, as it serves as an #Input for my other component.
<app-lights-preview [lightsValues]="lightsValues"></app-lights-preview>
The child component :
export class LightsPreviewComponent {
#Input() lightsValues;
.
.
.

How to map a JSON string into a TypeScript (JavaScript) object in AngularJS 2?

Consider this simple snippet of an AngularJS 2 application:
TestObject
export class TestObject {
id: number;
name: string;
}
TestService
[...]
export class TestService {
constructor(private http: Http) {}
test(): Observable<TestObject> {
return this.http
.get("http://www.example.com")
.map(this.save)
.catch(this.fail);
}
private save(response: Response) {
let testObject: TestObject = <TestObject> response.json();
return testObject || {};
}
private fail(error: any) {
return Observable.throw("error!");
}
}
AppComponent
[...]
export class AppComponent implements OnInit {
testObject: TestObject;
constructor(private testService: testService) {}
ngOnInit() {
this.testService.test().subscribe(
data => {
this.testObject = new TestObject();
console.log(this.testObject); // prints (empty) TestObject
this.testObject = data;
console.log(this.testObject); // prints object, not TestObject?
},
error => { }
);
}
}
Here my questions:
1) Why does my application print out (using Chrome Inspector) object and not TestObject as type?
2) The property testObject of class AppComponent should be of type TestObject. Why does my application not fail?
3) How can I achieve that I really get TestObject? What would be the best way to do it? Of course I could just manually fill up my TestObject, but I hoped there is some way of automatically mapping the json to my object.
Here is an answer that I wrote to a question which explained the handling of observables in angular2.
Angular 2 http post is returning 200 but no response is returned
Here you can see how I am handling the Response object as returned by the service. It is very important that you return your response object from the map function in service.
Similarly you can convert your response object to typescript type by casting your response object. The example can be:
this._loginService.login(this.username, this.password)
.subscribe(
(response) => {
//Here you can map the response to a type.
this.apiResult = <IUser>response.json();
//You cannot log your object here. Here you can only map.
},
(err) => {
//Here you can catch the error
},
() => {
//this is fired after the api requeest is completed.
//here you can log your object.
console.log(this.apiResult);
//result will only be shown here.
}
);
Here, it can be clearly seen that I am casting the response object to IUser type.
Another thing is while handling apiresponse in your component it is to be noted that the subscribe function has three arguments and if you will like to log your object, you must do it in the last function of subscribe.
Hope this helps!
your call must be like
ngOnInit() {
this.testService.test().subscribe(
(data) => {
this.testObject = new TestObject();
console.log(this.testObject); // prints (empty) TestObject
//only mapping
this.testObject = data;
},
error => { },
() => {
console.log(this.testObject);
}
);
}