Angular 4 Observable on Array in Service - arrays

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()
}

Related

What is wrong with the definition in this typescript file

I have an array of objects which I want to use when evaluating some input from a form. However I keep on getting a type error when evaluating the variable this.ptp. Can anyone point me in the right direction ?
export class AppCadComponent implements OnInit {
ptpForm: FormGroup;
ptp: number = 0;
ptp_list = [
{"leeftijd":"30-39", ptp:0, "geslacht":"man", "score":1},
{"leeftijd":"30-39", ptp:1, "geslacht":"vrouw", "score":2},
{"leeftijd":"40-49", ptp:2, "geslacht":"man", "score":3},
{"leeftijd":"40-49", ptp:3, "geslacht":"vrouw", "score":4},]
answer: {};
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.ptpForm = this.fb.group ({
ptp_age: [''],
ptp_geslacht: [''],
});
}
this.ptp = 1
var antwoord = this.ptp_list.filter(function (item) {
return item.leeftijd == formValue.ptp_age && item.geslacht ==formValue.ptp_geslacht && item.ptp == this.ptp
});
alert (answer.score);
}
You can assign this.ptp only inside a class method/constructor or during definition in general.
You can do this in constructor or ngOnInit
constructor(private fb: FormBuilder) {
this.ptp = 1;
}
or
ngOnInit() {
this.ptp = 1;
}
I don't know why the code to get the "answoord" variable is outside your component but anyway let's do some quick clean up in your code :
export interface PtpItem {
leeftijd: string;
ptp: number;
geslacht: string;
score: number;
}
export type PtpList = PtpItem[];
export class AppCadComponent implements OnInit {
ptpForm: FormGroup;
ptp: number = 0;
ptp_list: PtpList = [
{"leeftijd":"30-39", ptp:0, "geslacht":"man", "score":1},
{"leeftijd":"30-39", ptp:1, "geslacht":"vrouw", "score":2},
{"leeftijd":"40-49", ptp:2, "geslacht":"man", "score":3},
{"leeftijd":"40-49", ptp:3, "geslacht":"vrouw", "score":4}
]
answer: {};
constructor(private fb: FormBuilder) { }
ngOnInit(): void {
this.ptpForm = this.fb.group ({
ptp_age: new FormControl(''),
ptp_geslacht: new FormControl('')
});
// maybe this.ptp should be there ? To be confirmed
this.ptp = 1;
}
getAntWoord(formValue: any): PtpList {
if (!this.ptp || !formValue) {
return []; // We don't want to filter if this.ptp is not initialized or formGroup is null
}
return this.ptp_list.filter((item: PtpItem) => item.leeftijd === formValue.ptp_age && item.geslacht === formValue.ptp_geslacht && item.ptp === this.ptp);
}
}
I don't know how you define your answer member variable actually, but you can use the getAntWoord function by passing the formValue in param.

Angular2 #Injectable not working

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 ?.

Is it possible to run some code after an abstract extended method runs in typescript?

I have a typescript base class in my angular project called "Animal" that is extended for several types of animals. Here is some sample code to illustrate. Each extended class will implement it's own "getItems" method and then set "isItemsLoaded" after the promise resolves.
abstract class Animal {
items: Array<...> = [];
isItemsLoaded: boolean = false;
abstract getItems(): ng.IPromise<Array<...>>;
}
class Hippo extends Animal {
getItems() {
//return a promise to get a list of hippos
return this
.getHippos(...)
.then(list, => {
...
this.isItemsLoaded= true;
});
}
}
class Tiger extends Animal {
getItems() {
//return a promise to get a list of tigers
return this
.getTigers(...)
.then(list, => {
...
this.isItemsLoaded= true;
});
}
}
Can I somehow attach some code to run after the getItems resolves (like a finally method) in the Animal base class? I would like to be responsible for setting "isItemsLoaded" in the base. Is something like this possible?
abstract class Animal {
items: Array<...> = [];
isItemsLoaded: boolean = false;
abstract getItems(): ng.IPromise<Array<...>>.AfterResolved(this.isItemsLoaded = true;);
}
You can use template method pattern. Something like this:
// stripped down code for simplicity
abstract class Animal {
isItemsLoaded = false;
protected abstract getItemsForAnimal(): ng.IPromise<Array<...>>;
getItems() {
const items = this.getItemsForAnimal();
this.isItemsLoaded = true;
return items;
}
}
class Hippo extends Animal {
protected getItemsForAnimal() {
// return a promise to get a list of hippos
}
}

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