How to use zone.js in Angular? - javascript

I would like to use zone.js in my Angular project ( not just the runOutsideAngularZone function ).
I tried to include it like this:
import { zone } from 'zone.js';
Unfortunately I get this error:
error TS2306: File 'C:/projects/MyApp/node_modules/zone.js/dist/zone.js.d.ts' is not a module.
Then I removed the { zone } part:
import 'zone.js';
But now I get this error:
error TS2552: Cannot find name 'zone'. Did you mean 'Zone'?
My code is this:
let myZoneSpec = {
beforeTask: function () {
console.log('Before task');
},
afterTask: function () {
console.log('After task');
}
};
let myZone = zone.fork(myZoneSpec);
myZone.run(() => {console.log('Task');});
If I replace żone with Zone, I get this:
error TS2339: Property 'fork' does not exist on type 'ZoneType'.
How could I import and use zone.js from Angular?

Angular has a wrapper class for Zone.js called ngZone. You can inject it into your component like any service.
constructor(private zone: NgZone) {
this.zone.run(() => { console.log('This is zone'});
}
However, with this approach, we cannot use the full functionality of zone.js. For that, we have to declare:
declare let Zone: any;
public class MyComponent {
constructor() {
Zone.current.fork({
name: 'myZone'
}).run(() => {
console.log('in myzone? ', Zone.current.name);
});
}
}
Also, the APIs have changed since v 0.6.0. For running beforeTask and afterTask, you can look about it here, however, I looked into it and was unable to find anything related to beforeTask and afterTask.
Updated
For running beforeTask and afterTask, this is how it is possible in the new API.
constructor() {
const parentZone = new Zone();
const childZone = parentZone.fork({
name: 'child',
onInvoke: (...args) => {
console.log('invoked\n', args);
const valueToReturn = args[3](); // Run the function provided in the Zone.run
console.log('after callback is run');
return valueToReturn;
}
});
console.log(childZone.run(() => {console.log('from run'); return 'from child run';}));
}
NOTE:
If you want to create a scheduleMicroTask and want to have same functionality in it also, then you will need to implement onInvokeTask and/or onScheduleTask in the ZoneSpec (inside the parentZone.fork()).
constructor() {
const parentZone = new Zone();
const childZone = parentZone.fork({
name: 'child',
onScheduleTask: (...args) => {
console.log('task schedule...\n', args);
return args[3];
},
onInvokeTask: (...args) => {
console.log('task invoke...\n', args);
return args[3].callback();
}
});
const microTask = childZone
.scheduleMicroTask(
'myAwesomeMicroTask',
() => {
console.log('microTask is running');
return 'value returned from microTask';
} );
console.log(microTask.invoke());
}

I Highly recommend you to use NgZone with Angular.
Documentation here

Related

Memoize object lazily so that first attempt to access it would load it

Is there some npm package for memoizing object lazily, so that the first attempt to access it would load it?
The problem:
// service
class Service {
private readonly pathMap = {
user: process.env.USER_PATH,
post: process.env.POST_PATH,
page: process.env.PAGE_PATH,
}
getPath(entityType: EntityType) {
return this.pathMap[entityType];
}
}
export const service = new Service();
// service.spec.ts
import { service } from './service';
import { loadEnvVars } from '#app/loadEnvVars';
describe('service', () => {
beforeAll(loadEnvVars);
it('should return path', () => {
expect(service.getPath('user')).toBe(process.env.USER_PATH);
expect(service.getPath('post')).toBe(process.env.POST_PATH);
expect(service.getPath('page')).toBe(process.env.PAGE_PATH);
});
});
The tests will fail because the singleton service will load before the loadEnvVars due to the import of the service (before the beforeAll), which means the env vars will be set to undefined in the service pathMap.
The proposed solution:
I know there are several ways to fix it, but IMO the best solution would be to somehow lazy load the pathMap object so that the first attempt to get something from it will actually init the variable's value.
Here's a function I wrote to tackle this (Typescript):
export function lazy<T extends (...args: any[]) => any>(factory: T): ReturnType<T> {
let obj: ReturnType<T> | undefined;
const proxy = new Proxy(
{},
{
get(_, key) {
if (!obj) {
obj = factory();
}
return obj[key];
}
}
);
return proxy as ReturnType<T>;
}
Now the service will look like this instead:
class Service {
private readonly pathMap = lazy(() => ({
user: process.env.USER_PATH,
post: process.env.POST_PATH,
page: process.env.PAGE_PATH,
}));
getPath(entityType: EntityType) {
return this.pathMap[entityType];
}
}
export const service = new Service();
Now the tests will pass.
Note: In this solution lazy returns a read-only object. It can be changed of course.
The question:
Is there some NPM library out there that provides something like that already? Cause if not I think I might publish it myself.

Creating TypeScript class decorator for log correlation

I'm trying to create a TypeScript decorator that can be added to any class, that will create a request-scoped context that I can use to store a request ID. I've come across a couple of articles around decorators but none seem to fit my use-case.
Below is the code that I use to create the async hook, the decorator and a sample class that should be wrapped. When running the code, the actual response I receive is an empty object. The context is being lost but I'm not following why. I'm not receiving any errors or warnings.
Any suggestions would be highly appreciated.
This is the code I'm using to create the context. Calling the initContext() and getContext() functions.
import * as asyncHooks from 'async_hooks'
const contexts: any = {}
asyncHooks.createHook({
init: (asyncId: any, type: any, triggerAsyncId: any) => {
if (contexts[triggerAsyncId]) {
contexts[asyncId] = contexts[triggerAsyncId]
}
}
}).enable()
function initContext(fn: any) {
const asyncResource = new asyncHooks.AsyncResource('REQUEST_CONTEXT')
return asyncResource.runInAsyncScope(() => {
const asyncId = asyncHooks.executionAsyncId()
contexts[asyncId] = {}
return fn(contexts[asyncId])
})
}
function getContext() {
const asyncId = asyncHooks.executionAsyncId()
return contexts[asyncId] || {}
}
export { initContext, getContext }
This is the decorator that I'm using to wrap the class with. I'm trying to create the constructor within the context of the initContext function, set the context ID and then return the new constructor.
import { initContext } from './lib/context'
function AsyncHooksContext<T extends { new(...args: any[]): {} }>(target: T) {
return initContext((context: any) => {
context.id = 'some-uuid-goes-here'
return class extends target {
constructor(...args: any[]) {
super(...args)
}
}
})
}
export { AsyncHooksContext }
This is a sample class that should be able to produce the context ID
#AsyncHooksContext
class Foo {
public bar() {
const context = getContext()
console.log('This should be the context => ', { context })
}
}
new Foo().bar()

How can I evaluate Python code in the document context from JavaScript in JupyterLab?

With Jupyter Notebooks, I could have a cell
%%javascript IPython.notebook.kernel.execute('x = 42')
Then, elsewhere in the document a Python code cell with x would show it bound to 42 as expected.
I'm trying to produce something similar with JupyterLab. I understand I'm supposed to write a plugin rather than using ad-hoc JS, and that's fine, but I'm not finding an interface to the kernel similar to the global IPython from notebooks:
import { JupyerLab, JupyterLabPlugin } from '#jupyterlab/application';
const extension: JupyterLabPlugin<void> = {
// ...
requires: [],
activate: (app: JupyterLab) => {
// can I get to Python evaluation through app?
// by adding another class to `requires` above?
}
}
export default extension;
Here's a hacky attempt that "works". Could still use advice if anyone knows where there is a public promise for the kernel being ready, how to avoid the intermediate class, or any other general improvements:
import { JupyterLab, JupyterLabPlugin } from '#jupyterlab/application';
import { DocumentRegistry } from '#jupyterlab/docregistry';
import { INotebookModel, NotebookPanel } from '#jupyterlab/notebook';
import { IDisposable, DisposableDelegate } from '#phosphor/disposable';
declare global {
interface Window {
'execPython': {
'readyState': string,
'exec': (code: string) => any,
'ready': Promise<void>
} | null
}
}
class ExecWidgetExtension implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {
createNew(nb: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {
if (window.execPython) {
return;
}
window.execPython = {
'readyState': 'waiting',
'exec': null,
'ready': new Promise((resolve) => {
const wait = setInterval(() => {
if (!context.session.kernel || window.execPython.readyState === 'ready') {
return;
}
clearInterval(wait);
window.execPython.readyState = 'ready';
window.execPython.exec = (code: string) =>
context.session.kernel.requestExecute({ code }, true);
resolve();
}, 50);
})
};
// Usage elsewhere: execPython.ready.then(() => execPython.exec('x = 42').done.then(console.log, console.error))
return new DisposableDelegate(() => {});
}
}
const extension: JupyterLabPlugin<void> = {
'id': 'jupyterlab_foo',
'autoStart': true,
'activate': (app: JupyterLab) => {
app.docRegistry.addWidgetExtension('Notebook', new ExecWidgetExtension())
}
};
export default extension;

How to export an named instance in Vue.js, stop using: var app = this

I'm using .vue templates within a vue-cli created webpack project.
I typically use "export default" to export files, like this:
export default {
data () {
return {
message:''
};
},
...
Later in the script, when I try to access the instance, it seems within any function or third party library like axios, I have to write something like this at the top:
var app = this;
so I can access data properties...for example:
var app = this;
axios.post('https://test.com/api/getMessage',{}).then(res => {
app.message = res.data.message
})
.catch(error => {
console.log(error)
});
But calling var app = this; all the time gets really tiresome.
I would prefer to put the instance in a variable like:
let app = {
data () {
return {
message:''
};
},
methods :{
getMessage:function(){
axios.post('https://test.com/api/getMessage',{}).then(res => {
app.message = res.data.message
})
.catch(error => {
console.log(error)
});
}
}
}
But I don't understand how to export or import it properly. It seems every example just uses export default. So to restate my question, is there better /smarter way to export and import the script so I don't have to write:
var app = this;
axios.post('https://test.com/api/getMessage',{}).then(res => {
this.message = res.data.message
})
That should work - Arrow functions take place in the scope in which they are written, meaning this will refer to your Vue instance there. If you were not using arrow functions, you would have to specifically bind the scope:
axios.post('https://test.com/api/getMessage',{}).then(function(res){
this.message = res.data.message
}.bind(this))

What's an angular lifecycle-hook need when changing sets in componentInstance property by service?

I have a component that i send to MdDialog(Angular Material Dialog in my custom service.ts)
dialogRef = this.dialog.open(component, config);
And when I change a public property of this component by componentInstance like that:
dialogRef.componentInstance.task = task;
Angular shows me an error:
Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'dialog'. It seems like the view has been created after its parent and its children have been dirty checked. Has it been created in a change detection hook ?
Full code of open-modal.service.ts
#Injectable()
export class TasksPopupService {
constructor(
private dialog: MdDialog,
private router: Router,
private tasksService: TasksService
) { }
public open(component: any, id?: string) {
if (id) {
this.tasksService.find(id)
.subscribe(task => {
this.bindDialog(component, task);
});
} else {
this.bindDialog(component, new Task());
}
}
bindDialog(component, task: Task) {
let dialogRef;
let config = new MdDialogConfig();
config.height = '80%';
config.width = '70%';
dialogRef = this.dialog.open(component, config);
dialogRef.componentInstance.task = task;
dialogRef.afterClosed().subscribe(res => {
this.router.navigate([{ outlets: { popup: null } }], { replaceUrl: true });
});
return dialogRef;
}
}
But an error occured only if id is undefined (in ELSE block) I think it's because of this.tasksService.find return Observable (async), and block ELSE is not async. But I'm not sure.
I has some confuse becouse error eccured in MdContainer of Angular Material.
If i get data from server it's need some time, but when i pass a new object it's occur fast and change detection is not finished if i understend right.
Also, it's not parent/child component and lifecycle hooks maybe not works as we expect.
I found solution, but it's not right. Just fast solution.
if (id) {
this.tasksService.find(id)
.subscribe(task => {
this.bindDialog(component, task);
});
} else {
Observable.of(new Task()).delay(300).subscribe(task => {
this.bindDialog(component, task);
});
}
I use delay for change detection has finished and error will not throw.

Categories

Resources