I'm using angular2 with Typescript. I'm trying to create a base class that can be inherited by other classes and within the base class, a service is injected. So far I can not get the ajaxService injected correctly into the base class that is being inherited into the user class. Specifically when a user is instantiated, and then the save() method is called from the user instance, the following line in the base class: return _this._ajaxService.send(options); doesn't work since _ajaxService is undefined.
Here is a user class that extends the base class:
import {Base} from '../utils/base';
export class User extends Base {
// properties
id = null;
email = null;
password = null;
first_name = null;
last_name = null;
constructor(source) {
_super.CopyProperties(source, this);
}
}
Here is the base class:
import {Component} from 'angular2/core';
import {AjaxService} from './ajax.service';
#Component({
providers: [AjaxService]
})
export class Base {
constructor(private _ajaxService: AjaxService) { }
// methods
public static CopyProperties(source:any, target:any):void {
for(var prop in source){
if(target[prop] !== undefined){
target[prop] = source[prop];
}
else {
console.error("Cannot set undefined property: " + prop);
}
}
}
save(options) {
const _this = this;
return Promise.resolve()
.then(() => {
const className = _this.constructor.name
.toLowerCase() + 's';
const options = {
data: JSON.stringify(_this),
url: className,
action: _this.id ? 'PATCH' : 'POST';
};
debugger;
return _this._ajaxService.send(options);
});
}
}
This works fine except that AjaxService is not being injected into the base class. I guess this makes sense since user is being instantiated not base.
So how can I use AjaxService in the Base module when when `Base module is being extended on another class?
I guess when I instantiate user, the constructor in the user class is called but the constructor in the base class that injects the service is not being called.
Here's the AjaxService:
import {Injectable} from 'angular2/core';
#Injectable()
export class AjaxService {
// methods
send(options) {
const endpoint = options.url || "";
const action = options.action || "GET";
const data = options.data || {};
return new Promise((resolve,reject) => {
debugger;
$.ajax({
url: 'http://localhost:3000' + endpoint,
headers: {
Authentication: "",
Accept: "application/vnd.app.v1",
"Content-Type": "application/json"
},
data: data,
method: action
})
.done((response) => {
debugger;
return resolve(response);
})
.fail((err) => {
debugger;
return reject(err);
});
});
}
}
It's ok to inject services in the base, but you have to pass it in from the User class regardless. You can't inherit the actual instantiation of the service from the Base, so you have to pass it down to the Base from User. This is not a limitation of TypeScript, but rather a feature of how DI works in general.
Something like this:
class User extends Base
constructor(service: AjaxService) {
super(service);
}
If the Base instantiated the service for you, you would not be able to affect the instantiation from User. This would negate a lot of the benefits of DI overall since you would lose control by delegating dependency control to a different component.
I understand that you might be trying to reduce code duplication by specifying this in the Base, but this goes against the principle of DI.
Each class in Angular 2 that you want to inject you must annotate. If it is not component, you must annotate it with #Injectable() annotation. If you inject class that already inject other class, you must create provider for that.
import {Injectable} from 'angular2/core';
import {Base} from './base';
#Injectable()
export class User extends Base {
}
I created Plunker for you, i hope that it will solve your problem:
http://plnkr.co/edit/p4o6w9GjWZWGfzA6cv41?p=preview
( look at console output )
PS. Please use Observable instead of Promises
Related
I have some web components created using lit elements. And Those includes some api requests to send and fetch data from another site and I am hoping to send some data through api request header. So I am hoping to create one common function to contain those header details incase if i need to change them in the future, i won't need to edit those component one by one.
I need a solution like below :
common_function.js
function api_request(url) {
// content
}
my_component.js
import '/common_function.js';
...
constructor(){
api_request('http://apiRequestUrl');
}
Please let me know a way to achieve this using lit element.
Thanks in advance.
How to create a common function which you can import and reuse has nothing to do with lit.
So when you have a common function in a file named 'common.js':
export default txt => {
console.log('Hello from a common function!', txt);
}
You can use it in another javascript file, including lit components, like this:
import commonfunction from '/path/to/your/common.js';
commonfunction('Wow!');
What may be a problem (but which is not your question) is that your browser does not import lit with the lit import you may have specified... because it may be a npm package on your server. Even when you serve the node_modules folder and you specify the exact path and filename, your lit import may include other other imports that brake because of how they are specified as resolved by node.
Therefore you may have to use something like RollupJS which can distribute your app resources with proper imports. See https://rollupjs.org/guide/en/
Hope this helps.
Ok.. I found a solution. But I don't know whether this is the perfect answer for this.
We can use lit Reactive Controllers to do the job.
Here is the example How i did it.
common_function.js
import {initialState, Task} from '#lit-labs/task';
import * as SETTINGS from "../../bmw_settings";
export class ApiRequestController {
host;
url;
id;
task;
data = '';
_bmw_send_api_request() {
this.task = new Task(
this.host,
async ([data]) => {
const response = await fetch(
`${SETTINGS.BASE_URL + this.url}`,
{
headers: {
"Content-Type": "application/json",
'Access-Control-Request-Headers': 'Api-key, Content-Type',
'Api-key': '123'
}
}
);
const result = await response.json();
const error = result.error;
if (error !== undefined) {
throw new Error(error);
}
return result;
},
() => [this.data]
);
}
constructor(host, url, id) {
this.host = host;
this.url = url;
this.id = id;
this._bmw_send_api_request();
}
set data(value) {
this.data = value;
this.host.requestUpdate();
}
get data() {
return this.data;
}
render(renderFunctions) {
return this.task.render(renderFunctions);
}
}
my_component.js
import { LitElement, html} from 'lit';
import {ApiRequestController} from '../../common_functions';
class search_bar extends LitElement {
static properties = {
provider_type : String,
reasons : Array,
}
constructor() {
super();
this._getAllReasons();
}
async _getAllReasons(){
this.reasons = await new ApiRequestController(this, '/api/v1/get-reasons', 'search_bar');
}
render() {
return html `
${this.reasons.render({
complete: (data) => html `
<p>Reasons List</p>
<select>
<option>Select Reason</option>
${data.data.map((val) =>
html `<option value="${val.id}">${val.reason}</option>`
)}
</select>
`,
})}
`;
}
}
customElements.define('search-bar', search_bar)
use this documentation if you need more details.
Thank you
I'm learning Angular 2. And got confused over constructor.
Consider the below code :
import { Component, OnInit } from '#angular/core';
import { FormGroup,FormsModule,FormControl } from '#angular/forms';
import { WeatherService } from '../weather.service';
import { WeatherItem } from '../weather-item';
#Component({
selector: 'app-weather-search',
templateUrl: './weather-search.component.html',
styleUrls: ['../../assets/app.css'],
//providers: [WeatherService]
})
export class WeatherSearchComponent implements OnInit {
constructor(private _weatherService : WeatherService) { }
onSubmit(form : FormGroup){
//alert(form.value.location);
this._weatherService.searchWeatherData(form.value.location)
.subscribe(
data => {
const weatherItem = new WeatherItem(data.data.request["0"].query,data.data.weather["0"].maxtempC,data.data.weather["0"].maxtempC);
this._weatherService.addWeatherItems(weatherItem);
console.log(form);
})
}
ngOnInit() {
}
}
Here we are injecting 'WeatherService' in constructor. Can't we do the same outside constructor ? What constructor is doing here actually? Do we really need it here?
The constructor itself is not doing actual work.
Angular creates a new WeatherSearchComponent executing
new WeatherSearchComponent(weatherService);
and this causes the constructor in WeatherSearchComponent to receive the weatherService value.
The constructor
constructor(private _weatherService : WeatherService)
causes an instance field _weatherService to be created and initialized with the value passed from DI.
The constructor is the only place where it is easy to know when the injected service is available and when not.
If the service would passed to a field, setter or method, code in the constructor could not access it because the constructor is executed before outside code has a change to set a field or call a method.
Also for code outside the constructor it is not safe to assume the service is available because this code could be called from the constructor before a field could be set from the outside.
For dependency injection passing dependencies to the constructor is the only way to avoid a lot of complexity.
Dependency Injection in constructor is always better option and while the component is getting created it will get the weatherService as a parameter. To make it clear, below is the transpiled code for your snippet.
var WeatherSearchComponent = (function () {
function WeatherSearchComponent(_weatherService) {
this._weatherService = _weatherService;
}
WeatherSearchComponent.prototype.onSubmit = function (form) {
var _this = this;
//alert(form.value.location);
this._weatherService.searchWeatherData(form.value.location)
.subscribe(function (data) {
var weatherItem = new weather_item_1.WeatherItem(data.data.request["0"].query, data.data.weather["0"].maxtempC, data.data.weather["0"].maxtempC);
_this._weatherService.addWeatherItems(weatherItem);
console.log(form);
});
};
WeatherSearchComponent.prototype.ngOnInit = function () {
};
WeatherSearchComponent = __decorate([
core_1.Component({
selector: 'app-weather-search',
templateUrl: './weather-search.component.html',
styleUrls: ['../../assets/app.css'],
})
], WeatherSearchComponent);
return WeatherSearchComponent;
}());
exports.WeatherSearchComponent = WeatherSearchComponent;
As you can see in turn the javascript code has weatherService Instance being passed on to the function weatherSearchComponent.
I am trying to reuse some working code from AngularJS 1 services written in plain JavaScript in an Angular 2 environment.
The services look, for instance, like the following example:
(function () {
angular.module('myapp.mysubmodule').factory('myappMysubmoduleNormalService', ['someOtherService',
function (someOtherService) {
var internalState = {
someNumber: 0
};
var service = {};
service.someFunction = function () {
internalState.someNumber++;
};
someOtherService.getValues().forEach(function (v) {
service[v] = function () {
console.log(v + internalState.someNumber);
};
});
return service;
}]);
})();
I have found various examples of how to convert AngularJS 1 services to Angular 2 services (such as this one), all of which have in common that instead of the service factory, I have to export a class.
This should look roughly as follows:
import { Injectable } from '#angular/core';
#Injectable()
export class myappMysubmoduleNormalService {
someFunction: function () {
// ?
}
}
Now, the question is how to incorporate the internal state and the dynamically added properties.
Is it really the way to go to do all that in the constructor, i.e. fill each instance of the class upon initialization, like so:
import { Injectable } from '#angular/core';
#Injectable()
export class myappMysubmoduleNormalService {
constructor() {
var internalState = {
someNumber: 0
};
var service = {};
this.someFunction = function () {
internalState.someNumber++;
};
this.getValues().forEach(function (v) {
service[v] = function () {
console.log(v + internalState.someNumber);
};
});
}
}
Or is there any other way? The above probably works (save for the missing dependency injection, that I still have to find out about how to do in Angular 2). However, i am wondering whether it is a good way because I have not come across any samples that did much of a member initialization in their constructor.
You can use just the same approach in Angular with factory providers:
export function someServiceFactory(someOtherService) {
var internalState = {
someNumber: 0
};
var service = {};
service.someFunction = function () {
internalState.someNumber++;
};
someOtherService.getValues().forEach(function (v) {
service[v] = function () {
console.log(v + internalState.someNumber);
};
});
return service;
};
#NgModule({
providers: [
{
token: 'myappMysubmoduleNormalService',
useFactory: someServiceFactory,
deps: ['someOtherService']
}
]
})
Both in Angular and AngularJS the value returned by the factory function is cached.
A service is just a class that you can inject into components. It will create a singleton in the scope where it is named a provider.
import { Injectable. OnInit } from '#angular/core';
#Injectable()
export class myappMysubmoduleNormalService implements OnInit {
internalState: number;
constructor() {}
ngOnInit(){
this.internalState = 0;
}
incrementSomeNumber() {
this.internalState++;
console.log(this.internalState};
}
}
I realize this is not logging a distinct internal state for multiple functions but you get the idea.
Register this as a provider in the app.module (if you want a singleton for app scope)
When you import into a component and then inject in the constructor
constructor(private _myservice : myappMysubmoduleNormalService) {}
you can now use the _myservice methods
myNumber : number = 0 ;
componentFunction() {
_myservice.incrementSomeNumber();
this.myNumber = _myservice.internalState;
}
Of course you could have the service method return the incremented number (or data or a promise of data)
This is rough but gives you the idea. Very little code belongs in the constructor. A service should be injected. what is shown in component constructor is shorthand to a get private variable referencing the service. The service will be a singleton for the scope in which it is provided. (can be overridden within the scope but that seems a code smell to me)
To pass back a value :
In service
incrementSomeNumber(): number {
this._internalState++;
console.log(this._internalState};
return this._internalState;
}
In component:
mynumber: number;
componentFunction() {
this.mynumber = _myservice.incrementSomeNumber();
}
Not sure what you're trying to accomplish but just wanted to show example of getting information from services. Most common use of services for me is a dataservice, so the code would be a little more complex as it is asynch.
I'm trying to make a website where i use Aurelia and Javascript and ES6.
I have a simple class (Status) that needs to get some data on a interval from a server.
Update
I have added CalcData to the injector as sugessted by Fabio Luz, but i still get the same error. Good call btw ;).
The class looks like this:
import {inject} from "aurelia-framework"; // for the inject decorator
import { StatusData } from "./statusData"; // MovieData, the module that will be injected
import { CalcData } from "./Calc"
#inject(StatusData, CalcData) // Inject decorator injects MovieData
export class Status {
constructor(StatusData, CalcData) {
this.statusData2 = StatusData;
this.CalcData = CalcData;
}
activate() {
setInterval(this.updateCalc, 3000);
}
updateCalc() {
this.CalcData.hello()
.then(statusData => this.statusData2 = statusData);
}
updateStatus() {
return statusData2.getX()
.then(statusData => this.statusData2 = statusData);
}
update() {
return 1;
}
}
The updateCalc function is called but when this happens the browser says it that CalcData is undefined.
Uncaught TypeError: Cannot read property 'hello' of undefined
at updateCalc (status.js:17)
updateCalc # status.js:17
status.js:17 Uncaught TypeError: Cannot read property 'hello' of undefined
at updateCalc (status.js:17)
updateCalc # status.js:17
The CalcData class looks like this:
import { inject } from "aurelia-framework"; // for the inject decorator
import { HttpClient } from "aurelia-http-client"; // for the http client that will be injected
let baseUrl = "/movies.json";
#inject(HttpClient)
export class CalcData {
constructor(httpClient) {
this.http = httpClient;
}
hello() {
return this.http.get(baseUrl)
.then(response => {
return response.content;
});
}
}
I can't seem to find the problem, i have looked around but can't find a solution. I must say that i'm new to Aurelia.
Any help is much appreciated!
Your problem is down to capitalization, most likely.
Let's look at the beginning of your code:
import {inject} from "aurelia-framework"; // for the inject decorator
import { StatusData } from "./statusData"; // MovieData, the module that will be injected
import { CalcData } from "./Calc"
#inject(StatusData, CalcData) // Inject decorator injects MovieData
export class Status {
constructor(StatusData, CalcData) {
this.statusData2 = StatusData;
this.CalcData = CalcData;
}
Notice that your constructor is taking parameters whose names exactly match the names of the classes you want to inject. This is causing confusion for the runtime, as you are likely ending up setting this.Calcdata to the class CalcData (and the same for StatusData). The class does not have a function called hello(), only instances of the class have that function. If you change the parameter names to not exactly match, your issues should go away.
#inject(StatusData, CalcData) // Inject decorator injects MovieData
export class Status {
constructor(statusData, calcData) {
this.statusData = statusData;
this.calcData = calcData;
}
I've also lower-cased the property names to match JavaScript naming conventions.
Seems like i had to bind "this" to pass the object reference. When calling this in hello it how read gets the right object.
E.g.
import {inject} from "aurelia-framework";
import {StatusService} from "./statusService"
#inject(StatusService)
export class Status{
message = 'unknown yet';
statusService: StatusService;
constructor(statusService){
this.statusService = statusService;
}
activate(){
setInterval(this.updateStatus.bind(this), 3000);
}
updateStatus = function () {
this.message = this.statusService.getX();
}
}
I have TypeScript code with classes of components. And I want to use somehow remote js file to extend this classes remote. So I want when my app starts to get js file remote and use code this for extend of needed class.
How to extend class I know. For example:
Import { UsersBlocksMyOrders } from "../pages/users/blocks/myorders";
declare module "../pages/users/blocks/myorders" {
interface UsersBlocksMyOrders {
logit(): void;
}
}
UsersBlocksMyOrders.prototype.logit = function () { console.log(this); }
In component file the code is:
import { APP_CONFIG } from "../../../app/app.config";
#Component({
selector: 'menu-blocks-menupage',
templateUrl: APP_CONFIG.appDomain + '/mobilesiteapp/template/?path=pages/menu/blocks/menupage'
})
export class MenuBlocksMenuPage{
constructor(){
this.logit();
}
}
My problem is that I use the Webpack to compile code. Webpack create final file where name of function is different. That's why I can't access to class directly.
How to be in this situation?
Create service to get file and extend class. You need to have variable inside class which is would store object where have keys. Keys it is names of classes to extend. And values with imported classes. Inside init function we are loop extending with prototype method.
import { Http } from "#angular/http";
import { MenuBlocksMenuPage } from "../pages/menu/blocks/menupage";
export class ExtendService {
allModules = {
...
MenuBlocksMenuPage : MenuBlocksMenuPage,
...
};
constructor(public http: Http){ }
init()
{
//Load json map to extend class
this.http.get(APP_CONFIG.appDomain + '/modules/mobilesiteapp/view/js/extend.json').toPromise()
.then((res) => {
let json = res.json();
//Loop each class to extend
Object.keys(json).forEach((cl) => {
//Add new functions and methods
Object.keys(json[cl]).forEach((func) => {
this.allModules[cl].prototype[func] = eval(json[cl][func]);
});
});
}).catch((e) => {
console.error(e);
});
}
}
Request json file with functions to eval.
{
"MenuBlocksMenuPage": {
"logit": "(function (){console.log(this);})"
}
}