Get access to Angular service instance from JavaScript code - javascript

What I'm trying to do is have some testing assertions based on the data in the Angular service, i.e. we're trying to create E2E tests and the tool we're using allows us to execute arbitrary JavaScript code for assertions, so for that I need to know if it's possible to get access to the Angular service instance.
How can I get access to an Angular service instance from plain JS code?
That is, if my Angular app is deployed, and I open the app in the browser, then open Chrome DevTools, can I get access to the service instance of the my Angular service that was provided to all components?
I know it's possible to get access to your component by through ng.probe($0) etc. but not sure about services.
From what I have searched so far, it seems like we have to do use the Injector class and then use it's .get() method to get access to one of the Angular service instances but I'm not sure how would I get access to the Injector class/instance itself?
Here's what I tried: ng.probe($0) ($0 being the <app-root> of my app) and then I see that the return value has an .injector property, I tried to call ng.probe($0).injector.get('MyServiceName') and got an error for: Uncaught Error: No provider for MyServiceName!.
(Even though I'm trying ng.probe above, I would love to know how to get access to the injector without ng.probe because during execution of the automated testing i.e. prod mode, I don't think I'll be able to do ng.probe($0))
So I'm not sure if I'm trying to access it the right way? Any ideas?
I'm using Angular 4.

This works for me in Angular 7 using ng.probe():
window.ng.probe(window.getAllAngularRootElements()[0])
.injector.view.root.ngModule._providers
.find(p => p && p.constructor && p.constructor.name === 'MyServiceName');
And I guess it is not possible to do it another way without ng.probe()

Related

How to ensure that data for JSONModel are loaded?

I maintain an SAP Fiori app that has been running for two years without any problems. After upgrading SAPUI5 from 1.56.x to 1.101.x, there are various errors that can be traced back to a place where I try to load data for a JSON model.
The error is:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'indexOf')
This is the code:
onAfterRendering: function (oEvent) {
var sButtonId = this.getModel("misc").getProperty("/ButtonId");
// ...
// Here the code breaks because the variable is undefined:
if (sButtonId.indexOf("Demand") > -1) {
//...
}
},
The error usually only appears when navigating to the app from the SAP Fiori launchpad. If you reload the app itself, 99.9% of the time no error occurs.
There is no call that describes or reads the variable beforehand. The error appears directly at the first use.
I was trying to debug the UI5 framework a bit and came across internal method JSONModel.prototype._getObject:
When I call the app directly, this method returns the appropriate data and the app works. When I call the app from the launchpad, the method returns null. The model has not been loaded yet (oNode === undefined).
I then looked at the network and found that when I call the app directly, the JSON file/model has already been loaded before the above function is called, whereas when I load the app from the launchpad, the JSON file has been requested but no content has been served yet.
So it seems to be an asynchronous problem. I cannot use async/await, because UI5 officially only supports ECMAScript 5 (ES5) and we cannot deploy otherwise.
The model is declared and preloaded via the app descriptor (manifest.json).
Same as a/61879725 or a/63892279, you'll need to refactor the code by using dataLoaded1 from the JSONModel to ensure that the data have been loaded:
// Given: JSONModel and data source declared in the application descriptor (manifest.json)
thatJSONModel.dataLoaded().then(function() { // Ensuring data availability instead of assuming it.
var sButtonId = thatJSONModel.getProperty("/ButtonId"); // sButtonId is now defined.
// ...
}.bind(this));
Btw. I'd strongly discourage applications from relying on onBeforeRendering/onAfterRendering since it's unpredictable for them when and how many times the framework or a control initiates rendering.2
1 API Reference: sap.ui.model.json.JSONModel#dataLoaded
2 For example, the recent commit 8e24738 reduces the number of render calls in applications.

Using SailsJS sails.io with Angular 2

Is there a way to instantiate sails.io inside the zone of a service provider in Angular2 so that websocket events trigger change detection?
A sub-question: how to RXJS to subscribe to sails.io data streams.
I'm using Angular2 RC4, and the latest version of SailsJS.
Related question: Angular2 View Not Changing After Data Is Updated
UPDATE
It annoyed me that I couldn't give a full example of how to use Sails with Angular 2 with just plunker, so I created a github repo, that provides the full picture on how this can work.
I can see how the related question you linked, would lead you down the zone path. However, you can plug all of this together without having to manually tinker with zones. Here is an example of how you could implement sails.io with Angular2 in a plunker. This leverages using RxJS to create a type of observable. You also have to implement a different version of Angular2 change detection(all of this is implemented in the plunker).
I go into a little more detail in my answer to your linked question.
As for your sub question I'm not sure if there is a way to integrate the stream, I believe one of the intentions of RxJS was to reduce the use of callbacks, which sails.io appears to still do.
Excerpt of implementing sails.io in a service from the plunker.
constructor() {
this._ioMessage$ = <Subject<{}>>new Subject();
//self is the window object in the browser, the 'io' object is actually on global scope
self.io.sails.connect('https://localhost:1337');//This fails as no sails server is listening and plunker requires https
this.listenForIOSubmission();
}
get ioMessage$(){
return this._ioMessage$.asObservable();
}
private listenForIOSubmission():void{
if(self.io.socket){//since the connect method failed in the constructor the socket object doesn't exist
//if there is a need to call emit or any other steps to prep Sails on node.js, do it here.
self.io.socket.on('success', (data) => {//guessing 'success' would be the eventIdentity
//note - you data object coming back from node.js, won't look like what I am using in this example, you should adjust your code to reflect that.
this._ioMessage$.next(data);//now IO is setup to submit data to the subscribbables of the observer
});
}
}

Is there an Angular 2 equivalent for 'angular.element($0).scope()'

So for my sins I'm on an Angular 2 project. I use angular.element($0).scope() all the time on my old Angular 1 work to inspect an element and see what's on the scope at that point in the dev tools. This is super useful, is there something similar in Angular 2?
Augury is a great suggestion. If you want direct access use
ng.probe($0)
See also
Get ComponentRef from DOM element
How to access the *angular 2* components' data in the browser's console?
how to access Angular2 component specific data in console?
Have you tried using Augury? Link: https://github.com/rangle/augury

angular.injector - can't find custom (app-related) services

I've got a angular application. I start it and I open the chrome dev tools console and start typing.
I want to do some $injector.get(...), to play with my services and factories in console. But I'm not in a DI context, so... $injector is the same as angular.injector(), right? So I type angular.injector().get('MyModel') and I get
Uncaught Error: [$injector:unpr] Unknown provider: MyModelProvider <- MyModel
Well... why does angular injector can't find the model that I have declared with:
MyModule.service('MyModel', MyModel);
and I'm sure it works, because when I click out some things in the app, the model gets used and calls the API correctly.
Moreover, if I execute angular.injector().get('$attrs') or angular.injector().get('$scope') or whatever, I get the same errors. So I can see that I can't use injector this way.
What am I doing wrong? Or is there something wrong with my app configuration?
More or less, what I want to do is to retrieve a service or a factory or whatever from angular in the console (when the app is already loaded) to play with those DI-components.
Try :
angular.element(document.body).injector().get('MyModel')
For any DOM element you can do this:
angular.element(domElement).scope() to get the current scope for the element
angular.element(domElement).injector() to get the current app injector
angular.element(domElement).controller() to get a hold of the ng-controller instance.

Why does a ChromeWorker throw "Components is not defined"?

The documentation on ChromeWorkers says that they have "chrome privileges", and chrome privileges are supposed to mean they can do anything, but when I create a ChromeWorker and try to use Components I get an error that 'Components' is not defined..
So my question is
why is Components not available?
what is available to a ChromeWorker?
You cannot access the Components object because the Components object isn't threadsafe. A ChromeWorker gives you access to everything a Worker has access to, and c-types.

Categories