Angular2 #Injectable not working - arrays

I have an #Injectable class with a get-function in Service.ts create. This function should return one Array and also get any array. But it returns null. If I write this directly in a #Component, then it does get the array. What am I doing wrong?
service.ts
#Injectable()
export class Arr_selected {
private arr = [];
get(arr_selected: any[]){
for(let key in arr_selected) {
if (arr_selected.hasOwnProperty(key)) {
this.arr.push([key]);
return this.arr;
}
}
console.log(this.arr);
}
}
component.ts
import {Arr_selected} from './service.ts';
export class Component implements DoCheck, OnChanges, OnInit {
....
constructor( public arr_selected: Arr_selected)
.....
ngOnInit{
let chk = this.ChkBoxValue;
let arr = [];
/// **this not working Array is null**/////
arr=(this.arr_selected.get(this.ChkBoxValue));
/// **this is working Array is not null**////
for (let key in this.ChkBoxValue) {
if (this.ChkBoxValue.hasOwnProperty(key)) {
arr.push(this.ChkBoxValue[key]);
}
}
console.log(arr);
}}

i thinks below should work
#Injectable()
export class Arr_selected {
get(arr_selected: any[]){
arr = [];
for(let key in arr_selected) {
if (arr_selected.hasOwnProperty(key)) {
arr.push([key]);
return this.arr;
}
}
console.log(this.arr);
}
I think the "this" operator is causing the problem for you . You have arr also defined in the component . this might be conflicting which arr to use.

Above code you have written your file name as service.ts
and importing from ./servece.ts
import {Arr_selected} from'./servece.ts;
May be its typing error here ,
also are you adding your service in provider of component ?.

Related

How to pass an array from one component to its sub component

After googling lots of related answers and tries, I seemly have to seek help here.
I have an angular application, one of its components named stock-subscribe, which is used to display the feeTypes that a customer subscribed. Within this component, I created another component, stock-addsubs, used to display the feeTypes that are not yet subscribed by this customer.
enter image description here
Obviously, the two feeType lists can compose one whole list. From stock-subscribe, I can get an array, subscribedFeeIds, which holds all the ids of those subscribed feeTypes.
My requirement is to pass this array, subscribedFeeIds, to the stock-addsubs component so that I can filter out those yet unsubscribed feeTypes based on those ids of the array.
To my best understanding, the data passing from one component to its sub component should be a simple process and neither of two component html templates should be involved for my case. Of the many googled solutions, using #Output and #Input seems the simpler than event emitting. However, none can successfully pass the elements of the array in the sub component.
I can get the expected id list (subscribedFeeIds[]) from the stock-subscribe component, and all the rest code work fine so long as the array passed to the sub component is not EMPTY.
1) stock-subscribe.component.ts
#Component({
selector: 'app-stock-subscribe',
templateUrl: './stock-subscribe.component.html',
styleUrls: ['./stock-subscribe.component.scss']
})
export class StockSubscribeComponent implements OnInit {
userSubscribe: ISubscribe;
#Output() subscribedFeeIds: any = [];
listData: MatTableDataSource<any>;
constructor(private accountService: AccountService,
private stockService: StockService) { }
ngOnInit(): void {
this.createSubsList();
}
createSubsList() {
this.accountService.getUserAccount()
.subscribe(account => {
let userId = account.id.toString();
this.stockService.getUserSubscribes(userId).subscribe((data: ISubscribe[]) => {
// get the id of subscribed fee type and thenpublish to other component
for (var i = 0; i < data.length; i++)
{
if (data[i].status)
this.subscribedFeeIds.push(data[i].fees[0].id);
}
console.log(this.subscribedFeeIds);
// prepare the list source for display
this.listData = new MatTableDataSource(data);
this.listData.sort = this.sort;
}, error => {
console.log(error);
});
}, error => {
console.log(error);
});
}
}
2) stock-addsubs.component.ts
#Component({
selector: 'app-stock-addsubs',
templateUrl: './stock-addsubs.component.html',
styleUrls: ['./stock-addsubs.component.scss']
})
export class StockAddsubsComponent implements OnInit {
listData: MatTableDataSource<any>;
#Input() subscribedFeeIds: any [];
constructor(private feesService: FeesService) { }
ngOnInit(): void {
this.createFeeList();
}
createFeeList() {
this.feesService.getFeeList().subscribe((data: IFee[]) => {
// filter those already subscribed
for (var i = 0; i < this.subscribedFeeIds.length; i++)
{
for (var j = 0; j < data.length; j++)
{
data = data.filter(f => f.id != this.subscribedFeeIds[i]);
}
}
// prepare data to display
this.listData = new MatTableDataSource(data);
}, error => {
console.log(error);
});
}
}
You can implement either one of these methods:
1.) Enhance your #Input and #Output based on your code above:
stock-subscribe.component.ts
#Component({...})
export class StockSubscribeComponent implements OnInit {
#Output() subscribedFeeIds: EventEmitter<any> = new EventEmitter<any>();
list: any[] = []; // To store your ids, do not use the #Output variable above to push things inside a loop, you may not want to continuously emit data for an nth number of the sequence.
...
createSubsList() {
...
for (var i = 0; i < data.length; i++) {
...
if (data[i].status)
this.list.push(data[i].fees[0].id); // Push IDs on this.list not on this.subscribedFeeIds
}
this.subscribedFeeIds.emit(this.list); // After the loop finishes, emit the final list value
// Do not use .push() on #Output variable, this is an emission variable so use .emit()
}
}
or you could also do any of these methods when handling your array list:
// METHOD #1
this.filteredIds = [];
for(const { status, fees } of FEES_DATA) {
if (status) filteredIds.push(fees[0].id);
}
OR
// METHOD #2
this.filteredIds = FEES_DATA
.filter(({ status }) => status)
.map(({ fees }) => fees[0].id);
// Then emit the filtered ids
this.list.emit(this.filteredIds);
stock-addsubs.component.ts
#Component({...})
export class StockAddsubsComponent implements OnInit {
// Since you are waiting for emission from StockSubscribeComponent which the
// emission also waits till the loop is finished based on the code above, better to
// implement the #Input like this so as to avoid processing a an empty list
#Input() set subscribedFeeIds(list: any[]) {
if (list && list.length) { // If the list now has items, call createFeeList() function
this.createFeeList();
}
}
...
}
____
or
#Component({...})
export class StockAddsubsComponent implements OnInit, OnChanges { // Add OnChanges
#Input() subscribedFeeIds: any [];
ngOnChanges({ subscribedFeeIds }: SimpleChanges) { // SimpleChanges from #angular/core
if (subscribedFeeIds && subscribedFeeIds.currentValue && subscribedFeeIds.currentValue.length) {
this.createFeeList();
}
// You can also console the subscribedFeeIds and check it's value or changes
console.log(subscribedFeeIds);
}
}
Have created a Stackblitz Demo for your reference
2.) Use RxJS Subject or BehaviorSubject to emit data
Use this Stackblitz Demo for your reference.

Why Can't Iterate over an array in my model using the map() function

i have angular 7 component which is tied to a model and there is an array inside that model, the array was populated from a service. and it's populated.
the problem is i can't map over the array although it has elements there.
when i console it it shows the array has element. then i tried to console typeOf(array) it always gives object although it is an array !!.
i tried using this soluation but it didn't help either.
any help please?
export class FooModel {
foo : Foo
bars: Bar[];
}
export class SomeComponent implements OnInit {
model: FooModel;
constructor(private service: ProjectService) {
this.model = new FooModel();
this.model.bars = [];
}
ngOnInit() {
this.service.getFoos().subscribe((result: any) => {
// data is populated fine
this.model= <FooModel>result.data;
});
Console.log(this.model); // the model has data at this point
const arr = this.model.bars.map(a=> {
// never comes here
return a;
});
console.log(arr); // nothing is displayed here
// this works why ??
const arr2 = [1,2,3].map(s=> {
return s;
}
console.log(arr2); // it displays [1,2,3]
}
}
As the request is asynchronous, you might need to place the logic within the subscribe,
this.service.getFoos().subscribe((result: any) => {
// data is populated fine
this.model= <FooModel>result.data;
const arr = this.model.bars.map(a=> {
// never comes here
return a;
});
console.log(arr);
});
subscription is asynchronous so while it is still working the next line operation in the execution stack will be performed in this case the map you have after the subscription meanwhile it is still being populated in the background. You can try mapping in another life cycle hook say viewChecked hopefully it works. #cheers
Please look at the comments
export class FooModel {
foo : Foo
bars: Bar[];
}
export class SomeComponent implements OnInit {
model: FooModel;
constructor(private service: ProjectService) {
this.model = new FooModel();
this.model.bars = [];
}
ngOnInit() {
this.service.getFoos().subscribe((result: any) => {
// data is populated fine
this.model= <FooModel>result.data;
});
// the following starts to execute even before the model is populated above.
const arr = this.model.bars.map(a=> {
// never comes here because this.model.bars is empty at here and the length is 0 and nothing inside map executes
return a;
});
console.log(arr); // nothing is displayed here because it has nothing inside
// this works why ?? because you are using on an array which has some items.
const arr2 = [1,2,3].map(s=> {
return s;
}
console.log(arr2); // it displays [1,2,3]
}
}
So as Sajeetharan suggested, you have keep it inside subscribe()

Angular 4 Observable on Array in Service

I have an interface that implements an array in a service:
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
interface Artwork {
artTitle: string;
slideUrl?: string;
}
let Artwks: Artwork [] = [];
Artwks.push({
artTitle: "'Surprising Breakfast Ideas'",
slideUrl: './assets/SBI_Slide_1.jpg',
});
Artwks.push({
artTitle: "'Eagle'",
slideUrl: './assets/Eagle_Slide_2.jpg',
});
Artwks.push({
artTitle: "'Knot'",
slideUrl: './assets/Knot_Slide_3.jpg',
});
#Injectable()
export class ImageServiceService {
Artwks = Artwks;
i: number = 0;
getIndex() {
return this.i ;
}
getSlide() {
return this.Artwks[this.i].slideUrl;
}
getPrev() {
this.i = this.i===0 ? 0 : this.i - 1;
this.getTitle();
console.log(this.getTitle());
this.getSlide();
}
getNext() {
this.i = this.i===this.Artwks.length ? this.i : this.i + 1;
this.getTitle();
console.log(this.getTitle());
this.getSlide();
}
getTitle(): Observable<Artwork.artTitle> {
return this.Artwks[this.i].artTitle;
}
}
I want a component that displays the title of the array when getNext() or getPrev() is called on the service, but I don't know how to set up my observable so it updates the value of artTitle every time it changes?
I get error "'Artwork' only refers to a type, but is being used as a namespace here"
You are returning a string from getTitle() so your type should be string.
getTitle(): string { //.etc
EDIT:
If you want to use an observable, you need to make an observable and return it somewhere. I'm not sure how to do this without knowing how your class is used, but the basic idea looks like this:
Create and subject observable:
import {Subject} from 'rxjs/Subject';
export class ImageServiceService {
public artworks:Subject<Artwork> = new Subject();
private index: number = 0
getArt(){
this.artworks.next(this.Artwks[this.index])
this.index++
}
// etc.
}
Then in your component you can subscribe to the observable:
constructor(private imageService: ImageServiceService){
this.artworks = imageService.artworks
this.artworks.subscribe(v => // do something with it)
// artworks should update with the subject calls next()
}

Angular 2 observable doesn't 'map' to model

As I'm learning Angular 2 I used an observable to fetch some data via an API. Like this:
getPosts() {
return this.http.get(this._postsUrl)
.map(res => <Post[]>res.json())
.catch(this.handleError);
}
My post model looks is this:
export class Post {
constructor(
public title: string,
public content: string,
public img: string = 'test') {
}
The problem I'm facing is that the map operator doesn't do anything with the Post model. For example, I tried setting a default value for the img value but in the view post.img displays nothing. I even changed Post[] with an other model (Message[]) and the behaviour doesn't change. Can anybody explain this behaviour?
I had a similar issue when I wanted to use a computed property in a template.
I found a good solution in this article:
http://chariotsolutions.com/blog/post/angular-2-beta-0-somnambulant-inauguration-lands-small-app-rxjs-typescript/
You create a static method on your model that takes an array of objects and then call that method from the mapping function. In the static method you can then either call the constructor you've already defined or use a copy constructor:
Mapping Method
getPosts() {
return this.http.get(this._postsUrl)
.map(res => Post.fromJSONArray(res.json()))
.catch(this.handleError);
}
Existing Constructor
export class Post {
// Existing constructor.
constructor(public title:string, public content:string, public img:string = 'test') {}
// New static method.
static fromJSONArray(array: Array<Object>): Post[] {
return array.map(obj => new Post(obj['title'], obj['content'], obj['img']));
}
}
Copy Constructor
export class Post {
title:string;
content:string;
img:string;
// Copy constructor.
constructor(obj: Object) {
this.title = obj['title'];
this.content = obj['content'];
this.img = obj['img'] || 'test';
}
// New static method.
static fromJSONArray(array: Array<Object>): Post[] {
return array.map(obj => new Post(obj);
}
}
If you're using an editor that supports code completion, you can change the type of the obj and array parameters to Post:
export class Post {
title:string;
content:string;
img:string;
// Copy constructor.
constructor(obj: Post) {
this.title = obj.title;
this.content = obj.content;
this.img = obj.img || 'test';
}
// New static method.
static fromJSONArray(array: Array<Post>): Post[] {
return array.map(obj => new Post(obj);
}
}
You can use the as keyword to de-serialize the JSON to your object.
The Angular2 docs have a tutorial that walks you through this. However in short...
Model:
export class Hero {
id: number;
name: string;
}
Service:
...
import { Hero } from './hero';
...
get(): Observable<Hero> {
return this.http
.get('/myhero.json')
.map((r: Response) => r.json() as Hero);
}
Component:
get(id: string) {
this.myService.get()
.subscribe(
hero => {
console.log(hero);
},
error => console.log(error)
);
}

Can I create a TypeScript class within a function and refer to its parameters?

E.g. in angularJS I may use the following construction:
myApp.factory('MyFactory', function(injectable) {
return function(param) {
this.saySomething = function() {
alert("Param=" + param + " injectable=" +injectable);
}
};
});
This can later be used like this:
function(MyFactory) {
new MyFactory().saySomething();
}
When the function passed to the method factory gets invoked, the param injectable is caged and will further be available to new instances of MyFactory without any need to specify that parameter again.
Now I want to use TypeScript and obviously I want to specify that my MyFactory is newable, and has a function saySomething. How could I do this elegantly?
I could write something like this:
class MyFactory {
constructor(private injectable, private param) {}
saySomething() {
alert(...);
}
}
myApp.factory('myFactory', function(injectable) {
return function(param) {
return new MyFactory(injectable, param);
}
});
But this changes the API:
function(myFactory) {
myFactory().saySomething();
}
I wonder if it could be more elegant, because I like how the "new" expresses quite clearly that a new unique object is created and this object creation is the whole purpose of the factory.
** Edit: TypeScript >= 1.6 supports class expressions and you can now write things like:
myApp.factory(injectable: SomeService) {
class TodoItem {
...
}
}
** Original answer:
I have the same problem: with AngularJS and ES5, I enjoy dependency injection not polluting constructors and be able to use the new keyword.
With ES6 you can wrap a class inside a function, this is not yet supported by TypeScript (see https://github.com/Microsoft/TypeScript/issues/307).
Here what I do (MyFactory is now class TodoItem from a todo app to be more relevant):
class TodoItem {
title: string;
completed: boolean;
date: Date;
constructor(private injectable: SomeService) { }
doSomething() {
alert(this.injectable);
}
}
class TodoItemFactory() {
constructor(private injectable: SomeService) { }
create(): TodoItem {
return new TodoItem(this.injectable);
}
// JSON from the server
createFromJson(data: any): TodoItem {
var todoItem = new TodoItem(this.injectable);
todoItem.title = data.title;
todoItem.completed = data.completed;
todoItem.date = data.date;
return todoItem;
}
}
// In ES5: myApp.factory('TodoItem', function(injectable) { ... });
myApp.service('TodoItemFactory', TodoItemFactory);
class TodosCtrl {
// In ES5: myApp.controller('TodosCtrl', function(TodoItem) { ... });
constructor(private todoItemFactory: TodoItemFactory) { }
doSomething() {
// In ES5: var todoItem1 = new TodoItem();
var todoItem1 = this.todoItemFactory.create();
// In ES5: var todoItem2 = TodoItem.createFromJson(...)
var todoItem2 = this.todoItemFactory.createFromJson(
{title: "Meet with Alex", completed: false}
);
}
}
This is less elegant than with ES5 and functions (and not using classes with TypeScript is a no go) :-/
What I would like to write instead:
#Factory
#InjectServices(injectable: SomeService, ...)
class TodoItem {
title: string;
completed: boolean;
date: Date;
// No DI pollution
constructor() { }
saySomething() {
alert(this.injectable);
}
static createFromJson(data: string): TodoItem {
...
}
}
#Controller
#InjectFactories(TodoItem: TodoItem, ...)
class TodosCtrl {
constructor() { }
doSomething() {
var todoItem1 = new TodoItem();
var todoItem2 = TodoItem.createFromJson({title: "Meet with Alex"});
}
}
Or with functions:
myApp.factory(injectable: SomeService) {
class TodoItem {
title: string;
completed: boolean;
date: Date;
// No constructor pollution
constructor() { }
saySomething() {
alert(injectable);
}
static createFromJson(data: string): TodoItem {
...
}
}
}
myApp.controller(TodoItem: TodoItem) {
class TodosCtrl {
constructor() { }
doSomething() {
var todoItem1 = new TodoItem();
var todoItem2 = TodoItem.createFromJson({title: "Meet with Alex"});
}
}
}
I could write something like this
This is what I do
Can I create a TypeScript class within a function
No it needs to be at the top level of the file or in a module. Just FYI if were able to create it inside a function the information would be locked inside that function and at least the type info would be useless.
What's the reason for instantiating multiple instances of MyFactory? Would you not want a single instance of your factory to be injected into your dependent code?
I think using the class declaration you provided will actually look like this once injected:
function(myFactory) {
myFactory.saySomething();
}
If you are really needing to pass a constructor function into your dependent code, then I think you will have to ditch TypeScript classes, since they can't be defined inside of a function which means you would have no way to create a closure on a variable injected into such function.
You do always have the option of just using a function in TypeScript instead of a class. Still get the strong typing benefits and can call 'new' on it since it is still a .js function at the end of the day. Here's a slightly more TypeScriptiffied version:
myApp.factory('MyFactory', (injectable: ng.SomeService) => {
return (param: string) => {
return {
saySomething: () {
alert("Param=" + param + " injectable=" +injectable);
}
};
};
});

Resources