I have the following scenario.
I have a simple angular 2 app with a service which I add to providers in app.module. When I click on a button the app should load a javascript file and execute a function e.g function A defined in this javascript file. So the question is how can I access the service within this function A. My consideration is to append the service to the global window variable.
Are there better ways to achieve it?
Code:
export class MyService{
public editProjectDone = new Subject<string>();
}
app.module.ts
{
...providers: [MyService]
}
app.component.html
<button (click)="editProject()">Edit project</button>
app.component.ts
function editProject(){
... load Javascript file
call javascript file
js.editProject("projectId") // call function defined in javascript file
}
javascript file
{
function editProject(projectId)
{
//do calculation
// fire event that calculation is done
// the calcuation is not done in typescript, but here
MyService.editProjectDone.next()
// The question is here how to access the event and fire it
}
}
So you want to access angular service method in javascript function A().
For example:
Your service class:
export class SettingsService{
getLanguage() {
return 'en-GB';
}
}
Your javascript file
function A() {
settingsService.getLanguage();
}
Solution: Custom Event.
Basically you define a custom event handler in javascript file. And define the Custom Event and dispatchEvent the Custom Event in Angular click event function.
app.component.html:
<input type="button" value='log' (click)="logclick($event)">
app.component.ts
constructor(private settings: SettingsService){}
logclick(event){
// define custom event
var customevent = new CustomEvent(
"newMessage",
{
detail: {
message: "Hello World!",
time: new Date(),
myservice: this.settings //passing SettingsService reference
},
bubbles: true,
cancelable: true
}
);
event.target.dispatchEvent(customevent); //dispatch custom event
}
javascript file:
// event handler function
function newMessageHandler(e) {
console.log(
"Event subscriber on "+e.currentTarget.nodeName+", "
+e.detail.time.toLocaleString()+": "+e.detail.message
);
//calling SettingsService.getLanguage()
console.log(e.detail.myservice.getLanguage());
}
//adding listener to custom event.
document.addEventListener("newMessage", newMessageHandler, false);
Example output:
Event subscriber on #document, 9/11/2018, 11:31:36 AM: Hello World!
en-GB
Note: I have not added section for dynamically loading javascript file. I assume you are already able to do that from your comments.
Declare variable as public using window object but in proper way. export only some functions not whole service and in some standard way like below.
In Angular
export class AbcService {
constructor() {
const exportFunctions = {
xyzFunction: this.xyzFunction.bind(this),
pqrFunction: this.pqrFunction.bind(this)
}; // must use .bind(this)
window['ngLib']['abcService'] = exportFunctions;
}
xyzFunction(param1, param2) {
// code
}
pqrFunction() {
// code
}
private oneFunction() {
// code
}
private twoFunction() {
// code
}
}
In Javascript
ngLib.abcService.xyzFunction(value1, value2);
ngLib.abcService.pqrFunction();
Firstly you need to import the js file before calling the service and that can be done by:
TS
let js: any = require('JSFileNAME')['default']; //Something lik this in constructer
then once the file is imported you need to create a instantiate of the js in your Ts
something like
this.newObjectOfJs = new js(); // pass any paramater if its required.
hereafter you will be able to access the methods and service from the JSfile.
You have to use service variable to access functions and variable of particular services.
Here I demonstrate how you can do it.
export class AppComponent {
name = 'Angular';
constructor(private myServices: MyServices){
this.myServices.sayHello();
}
}
Whole code available here : https://stackblitz.com/edit/angular-services-example
Related
I am trying to create a custom event dispatcher. I have read this article and implemented the code
export default class Dispatcher{
constructor(){
}
addListener (event, callback) {
// Check if the callback is not a function
if (typeof callback !== 'function') {
console.error(`The listener callback must be a function, the given type is ${typeof callback}`);
return false;
}
// Check if the event is not a string
if (typeof event !== 'string') {
console.error(`The event name must be a string, the given type is ${typeof event}`);
return false;
}
// Create the event if not exists
if (this.events[event] === undefined) {
this.events[event] = {
listeners: []
}
}
this.events[event].listeners.push(callback);
}
removeListener(event, callback){
//check if this event not exists
if(this.events[event] === undefined){
console.error(`This event: ${event} does not exist`);
return false;
}
this.events[event].listeners = this.events[event].listeners.filter(listener => {
return listener.toString() !== callback.toString();
})
}
dispatch(event, details){
//check if this event not exists
if(this.events[event] === undefined){
console.error(`This event: ${event} does not exist`);
return false;
}
this.events[event].listeners.forEach((listener) => {
listener(details);
})
}
}
my goal is for my external classes that I import into my main JavaScript file to be able to dispatch events globaly. so i want my js imported like this
import {API2} from '/js/API2.js'
to be able to dispatch an event that can be captured from the main.js file that originally imports the API2 class.
one way that I have tried is to attach the imported dispatcher class to window but this is obviously wrong and produces no result.
How could one implement a dispatcher class that allows me to add events and dispatch events globally from anywhere in the code imported or not?
If I understand it correctly, you want 2 things:
Import the dispatcher into a file and use the class directly
Use a global object to interact with the class
1. import into a file and use it directly
In order to import it directly and use it, create a singleton (so 1 instance of the class) and export it directly:
class Dispatcher{
...
}
export default new Dispatcher() // this will initialise the singleton instantly
In order to use it, you can now import it in any file:
import dispatcher from './dispatcher.js';
It will be the same instance anywhere.
2. make it global
In order to make it global you could actually update the file with the following (either global for nodejs or window for web):
class Dispatcher{
...
}
const dispatcherSingleton = new Dispatcher();
window.dispatcherSingleton = dispatcherSingleton // web
global.dispatcherSingleton = dispatcherSingleton // nodejs
export default dispatcherSingleton // export the singleton
I am working on a task where I have an event listener window.addEventListener which is in javascript, based on event data I want to trigger a function of typescript and pass the data to that function. the point I am not getting is how to call typescript function in javascript. I have tried different things like a global variable, returning a value from js function but didn't work for me.
ngOnInit() {
(function ($) {
$(document).ready(function () {
window.addEventListener('message', function (event) {
console.log("Rece new Call event ibrahim " + JSON.stringify(event.data));
let obj: any = JSON.stringify(event.data)
obj = JSON.parse(obj);
console.log(obj)
if (obj.EventType === "handleContactIncoming") {
var num = obj.Number
// here i want to trigger Typescript function and pass this num to that function.
}
else if (event.data === "endCall") {
// return "No"
// var dbBtn = document.getElementById("db");
// dbBtn.click();
}
// $('.sidebar-menu').tree();
});
});
});
There is no difference when calling function from TS or JS. Finally there's always only JS in the browser, TS exists only in source code.
Also your code is a bit messy. There's no need to use jQuery inside angular (with an exception when you want to use some plugins or custom controls).
$(document).ready(function () {
is also redundant, if angular works the document is for sure ready.
Your code is quite messy and breaks separation of concerns. You should not use jQuery inside Angular. The injectable EventManager provides functionality for setting events on the window or document objects.
constructor(private readonly eventManager: EventManager) {}
ngOnInit(): void {
this.eventManager.addGlobalEventListener(target, event, () => {
this.hello();
});
}
hello(): void {
console.log('Hello World!');
}
I'm trying to create an instance of a class, this class only sets up listeners but there is no need to call any methods manually in the class.
Below is the module I load in my html file, I create a simple MVC-Pattern but I get an eslint error on line 4.
I need the constructor of the class GameController to run in order to setup the listeners but for that I need an instance of it.
I've tried making a dummy function in the GameController class and calling that. Gets rid of the error but obviously that's not the way to go, I'm looking for a clean alternative.
function init() {
let data = new GameData(),
view = new GameView(data),
controller = new GameController(data, view);
}
init();
class GameController {
constructor(data, view) {
this.data = data;
this.view = view;
this.letterGenerator = LetterGenerator();
this.view.addEventListener(Config.EVENT.CONSONANT, this.onConsonantClicked
.bind(this));
this.view.addEventListener(Config.EVENT.VOWEL, this.onVowelClicked.bind(
this));
this.view.addEventListener(Config.EVENT.STOP, this.onGameEnd.bind(this));
}
onVowelClicked() {
...
}
onConsonantClicked() {
...
}
onGameEnd(event) {
...
}
}
What I want is instantiate a GameController but not use it, eslint basically forces me to make a dummy function in the GameController class that I need to call to get rid of the error.
I'm trying to mock an ES6 class with a constructor that receives parameters, and then mock different class functions on the class to continue with testing, using Jest.
Problem is I can't find any documents on how to approach this problem. I've already seen this post, but it doesn't resolve my problem, because the OP in fact didn't even need to mock the class! The other answer in that post also doesn't elaborate at all, doesn't point to any documentation online and will not lead to reproduceable knowledge, since it's just a block of code.
So say I have the following class:
//socket.js;
module.exports = class Socket extends EventEmitter {
constructor(id, password) {
super();
this.id = id;
this.password = password;
this.state = constants.socket.INITIALIZING;
}
connect() {
// Well this connects and so on...
}
};
//__tests__/socket.js
jest.mock('./../socket');
const Socket = require('./../socket');
const socket = new Socket(1, 'password');
expect(Socket).toHaveBeenCalledTimes(1);
socket.connect()
expect(Socket.mock.calls[0][1]).toBe(1);
expect(Socket.mock.calls[0][2]).toBe('password');
As obvious, the way I'm trying to mock Socket and the class function connect on it is wrong, but I can't find the right way to do so.
Please explain, in your answer, the logical steps you make to mock this and why each of them is necessary + provide external links to Jest official docs if possible!
Thanks for the help!
Update:
All this info and more has now been added to the Jest docs in a new guide, "ES6 Class Mocks."
Full disclosure: I wrote it. :-)
The key to mocking ES6 classes is knowing that an ES6 class is a function. Therefore, the mock must also be a function.
Call jest.mock('./mocked-class.js');, and also import './mocked-class.js'.
For any class methods you want to track calls to, create a variable that points to a mock function, like this: const mockedMethod = jest.fn();. Use those in the next step.
Call MockedClass.mockImplementation(). Pass in an arrow function that returns an object containing any mocked methods, each set to its own mock function (created in step 2).
The same thing can be done using manual mocks (__mocks__ folder) to mock ES6 classes. In this case, the exported mock is created by calling jest.fn().mockImplementation(), with the same argument described in (3) above. This creates a mock function. In this case, you'll also need to export any mocked methods you want to spy on.
The same thing can be done by calling jest.mock('mocked-class.js', factoryFunction), where factoryFunction is again the same argument passed in 3 and 4 above.
An example is worth a thousand words, so here's the code.
Also, there's a repo demonstrating all of this, here:
https://github.com/jonathan-stone/jest-es6-classes-demo/tree/mocks-working
First, for your code
if you were to add the following setup code, your tests should pass:
const connectMock = jest.fn(); // Lets you check if `connect()` was called, if you want
Socket.mockImplementation(() => {
return {
connect: connectMock
};
});
(Note, in your code: Socket.mock.calls[0][1] should be [0][0], and [0][2] should be [0][1]. )
Next, a contrived example
with some explanation inline.
mocked-class.js. Note, this code is never called during the test.
export default class MockedClass {
constructor() {
console.log('Constructed');
}
mockedMethod() {
console.log('Called mockedMethod');
}
}
mocked-class-consumer.js. This class creates an object using the mocked class. We want it to create a mocked version instead of the real thing.
import MockedClass from './mocked-class';
export default class MockedClassConsumer {
constructor() {
this.mockedClassInstance = new MockedClass('yo');
this.mockedClassInstance.mockedMethod('bro');
}
}
mocked-class-consumer.test.js - the test:
import MockedClassConsumer from './mocked-class-consumer';
import MockedClass from './mocked-class';
jest.mock('./mocked-class'); // Mocks the function that creates the class; replaces it with a function that returns undefined.
// console.log(MockedClass()); // logs 'undefined'
let mockedClassConsumer;
const mockedMethodImpl = jest.fn();
beforeAll(() => {
MockedClass.mockImplementation(() => {
// Replace the class-creation method with this mock version.
return {
mockedMethod: mockedMethodImpl // Populate the method with a reference to a mock created with jest.fn().
};
});
});
beforeEach(() => {
MockedClass.mockClear();
mockedMethodImpl.mockClear();
});
it('The MockedClassConsumer instance can be created', () => {
const mockedClassConsumer = new MockedClassConsumer();
// console.log(MockedClass()); // logs a jest-created object with a mockedMethod: property, because the mockImplementation has been set now.
expect(mockedClassConsumer).toBeTruthy();
});
it('We can check if the consumer called the class constructor', () => {
expect(MockedClass).not.toHaveBeenCalled(); // Ensure our mockClear() is clearing out previous calls to the constructor
const mockedClassConsumer = new MockedClassConsumer();
expect(MockedClass).toHaveBeenCalled(); // Constructor has been called
expect(MockedClass.mock.calls[0][0]).toEqual('yo'); // ... with the string 'yo'
});
it('We can check if the consumer called a method on the class instance', () => {
const mockedClassConsumer = new MockedClassConsumer();
expect(mockedMethodImpl).toHaveBeenCalledWith('bro');
// Checking for method call using the stored reference to the mock function
// It would be nice if there were a way to do this directly from MockedClass.mock
});
For me this kind of Replacing Real Class with mocked one worked.
// Content of real.test.ts
jest.mock("../RealClass", () => {
const mockedModule = jest.requireActual(
"../test/__mocks__/RealClass"
);
return {
...mockedModule,
};
});
var codeTest = require("../real");
it("test-real", async () => {
let result = await codeTest.handler();
expect(result).toMatch(/mocked.thing/);
});
// Content of real.ts
import {RealClass} from "../RealClass";
export const handler = {
let rc = new RealClass({doing:'something'});
return rc.realMethod("myWord");
}
// Content of ../RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "The.real.deal "+input;
}
// Content of ../test/__mocks__/RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "mocked.thing "+input;
}
Sorry if I misspelled something, but I'm writing it on the fly.
I have a public method that I exposed to window. This method talks to a Component and modifies a variable I am watching in my template. But when I change the value, the *ngIf() does not get triggered.
app.component
constructor(private _public: PublicService,) {
window.angular = {methods: this._public};
}
PublicService
export class PublicService {
constructor(
private _viewManager: ViewManagerComponent,
) {}
CallMe(){
this._viewManager.renderView('page1')
}
}
LayoutManagerComponent
#Component({
selector: 'view-manager',
template: `<page *ngIf="view == 'page1'"></page>`
})
export class ViewManagerComponent {
//This is the variable being watched
view = "page";
renderView = function(type){
console.log(type)
this.view = type;
console.log(this.view)
};
}
So the idea is that when the view initially loads, the view is blank. Then when I type angular.methods.CallMe() it modifies the view variable to page1 which should then show the html for the Component. If I console renderView function it is successfully getting called, just the view does not change.
----Update - Still not working -------
export class ViewManagerComponent {
constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
}
view = "page";
#Output() renderView(type){
// type is 'page'
console.log(this.view)
this.zone.run(() => {
// type is 'page'
console.log(this.view)
this.view = type;
// type is 'page1'
console.log(this.view)
});
// type is 'page1'
console.log(this.view)
//cdRef errors:
//view-manager.component.ts:36 Uncaught TypeError: this.cdRef.detectChanges is not a function(…)
this.cdRef.detectChanges();
};
}
In this case Angular2 doesn't know that it needs to run change detection because the change is caused by code that runs outside Angulars zone.
Run change detection explicitely
contructor(private cdRef:ChangeDetectorRef) {}
someMethodCalledFromOutside() {
// code that changes properties in this component
this.cdRef.detectChanges();
}
Run the code that modifies the components properties inside Angulars zone explicitely
contructor(private zone:NgZone) {}
someMethodCalledFromOutside() {
this.zone.run(() => {
// code that changes properties in this component
});
}
The zone method is a better fit when // code that changes properties in this component not only changes properties of the current component, but also causes changes to other components (like this.router.navigate(), call method references of methods of other components) because zone.run() executes the code inside Angulars zone, and you don't need to explicitely take care of change detection in every component where a change might happen because of this call.
If you use function(...) instead of () => it's likely you'll get unexpected behavior with this in code inside the Angular component.
See also my answer to this similar question for more details Angular 2 - communication of typescript functions with external js libraries
update
export class ViewManagerComponent {
constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
self = this;
}
view = "page";
#Output() renderView(type){
// type is 'page'
console.log(self.view)
self.zone.run(() => {
// type is 'page'
console.log(self.view)
self.view = type;
// type is 'page1'
console.log(self.view)
});
// type is 'page1'
console.log(self.view)
self.cdRef.detectChanges();
};
}