Angular 2 make a second emit, when first completes - angularjs

We've started migrating from ng1 to ng2, but I don't understand how to upgrade my button component. For example:
In angular 1, I can pass a promise inside of a component and wait response, like:
class ButtonController {
click: () => Promise<any>;
form: SomeForm;
onClick(): void {
this.click().then(() => {
this.form.doSomething();
});
}
}
export const ButtonComponent = {
templateUrl: 'button-component.template.html',
controller: ButtonController,
controllerAs: 'vm',
require: '^form',
bindings: {
click: '&'
}
});
But, how can I achive this in angular 2 with #Output and EventEmitter? I've achived this with #Input, callback from component and a service between ButtonComponent and FormComponent, but I'm sure it's totally wrong. Will be glad of any help. Thanks.
angular 2 code:
sign-up.template.html
// content
<ab-form [uFormGroup]="signUpForm">
//content
<ab-button [uClick]="onClick"></ab-button>
</ab-form>
sign-up.component.ts
export class SignUpComponent {
onClick = () => {
//do something async
}
}
form.component.ts
#Component({
selector: "ab-form",
template: require("./form.template.html"),
providers: [
FormService
]
})
export class FormComponent {
#Input() uFormGroup: FormGroup;
constructor(
private formService: FormService
) {
this.formService.buttonClicked$.subscribe((fn) => {
this.uFormGroup['submitted'] = true;
if (this.uFormGroup.invlaid) {
return this.formService.endSubmittingForm();
}
this.formService.startSubmittingForm();
// resolve if function is not a promise
Promise.resolve(fn()).then(() => {
this.formService.endSubmittingForm();
});
});
}
}
button.component.ts
export class ButtonComponent {
submitting = false;
constructor(
private formService: FormService
) {
this.formService.formSubmitting$.subscribe(() => {
this.submitting = true;
});
this.formService.formSubmitted$.subscribe(() => {
this.submitting = false;
});
}
onClick(): void {
this.formService.buttonClick(this.uClick);
}
}
form.service.ts
export class FormService {
private buttonClickedSource = new Subject<Function>();
private formSubmittingSource = new Subject<void>();
private formSubmitedSource = new Subject<void>();
buttonClicked$ = this.buttonClickedSource.asObservable();
formSubmitting$ = this.formSubmittingSource.asObservable();
formSubmited$ = this.formSubmitedSource.asObservable();
buttonClick(fn: Function): void {
this.buttonClickedSource.next(fn);
}
startSubmittingForm(): void {
this.formSubmittingSource.next();
}
endSubmittingForm(): void {
this.formSubmitedSource.next();
}
}

You can create simple Component Inputs and Outputs like this:
#Component({
selector: 'myButton',
template: '<button (click)="buttonAction()">{{label}}</button>',
inputs: ['label']
})
export class MyButton {
#Input()
label:string;
#Output()
myButtonClick = new EventEmitter<any>();
...
buttonAction() {
this.tableButtonClick.emit("some value")
}
}
and then use it in your templates like this:
<myButton [label]="sample"
(myButtonClick)="handleMyButtonClick($event)">
</myButton>
$event will be string "some value", but you can use any object you like

You can do the same as you done with Angular 1, using Observer :
onClick(): void {
this.formService.buttonClick().subscribe(() => {
// You reach this when buttonClick is async terminated
// like your then() in your Angular 1 code
this.uClick()
...
});
}
And in your FormService you return the Observable :
buttonClick(): Observable<any> {
return this.buttonClicked$;
}

Related

Trying to get a unique list of objects from an array in angular

Have an observable being returned from my service.ts as shown here:
import { Injectable, ErrorHandler } from '#angular/core';
import { HttpClient, HttpErrorResponse, HttpHeaders } from
'#angular/common/http'
import { Observable } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { PokeResponse } from '../PokeResponse';
#Injectable({
providedIn: 'root'
})
export class PokeApiService {
private url = "https://pokemon-go1.p.rapidapi.com/pokemon_stats.json";
constructor(private http:HttpClient) { }
getPokeData(): Observable<PokeResponse> {
return this.http.get<PokeResponse>(this.url,
{
headers : new HttpHeaders({
'x-rapidapi-key': 'a6cef4cbcamsh05b29346394d4a4p1bafacjsn2a92406ac103'
})
})
.pipe(
tap(data => console.log('Pokedata/Error' + JSON.stringify(data))
),
catchError(this.handleError)
);
}
private handleError(err:HttpErrorResponse) {
console.log('PokemonService: ' + err.message);
return Observable.throw(err.message);
}
}
This is my response:
export interface PokeResponse{
list:results[];
results:{
pokemon_id:number;
pokemon_name:string;
base_attack:number;
base_defense:number;
base_stamina:number;
}[];
}
export interface results{
pokemon_id:number;
pokemon_name:string;
base_attack:number;
base_defense:number;
base_stamina:number;
}
export class Pokemon implements results{
pokemon_id:number;
pokemon_name:string;
base_attack:number;
base_defense:number;
base_stamina:number;
constructor(_id:number, _name:string, _atk:number, _def:number, _stam:number) {
_id = this.pokemon_id;
_name = this.pokemon_name;
_atk = this.base_attack;
_def = this.base_defense;
_stam = this.base_stamina;
}
}
And this is my component.ts:
import { Component, OnInit } from '#angular/core';
import { PokeApiService } from 'src/app/services/poke-api.service';
import { PokeResponse, results, Pokemon } from 'src/app/PokeResponse';
import { Observable } from 'rxjs';
#Component({
selector: 'app-list-pokemon',
templateUrl: './list-pokemon.component.html',
styleUrls: ['./list-pokemon.component.css']
})
export class ListPokemonComponent implements OnInit {
constructor(private pokeService: PokeApiService) { }
ngOnInit(): void {
this.getPokeDetails();
}
pokeData:PokeResponse;
errorMessage:any;
pokeArray:results;
getPokeDetails() : boolean {
this.pokeService.getPokeData().subscribe(
pokeData => {
this.pokeData=pokeData;
console.table(pokeData);
},
error => this.errorMessage = <any>error
);
return false;
}
}
In my console I'm getting back a console.table of my observable like this
I'm trying to filter out the names of Pokemon which are the same as others, which could also be achieved by just filtering out any of the pokemon_ids as all the stats match regardless of the type.
So far I've tried:
console.log(this.pokeArray);,
using [...Set()], forEach(), and Array.from()
Any help or suggestions on how I can make this question any clearer would be greatly appreciated.
Try this, using filter:
// list-pokemon.component.ts
export class ListPokemonComponent implements OnInit {
uniqueListPoke = [];
flags = {};
constructor(private pokeService: PokeApiService) { }
ngOnInit(): void {
this.getPokeDetails();
}
pokeData:PokeResponse;
errorMessage:any;
pokeArray:results;
getPokeDetails() : boolean {
this.pokeService.getPokeData().subscribe(
pokeData => {
this.uniqueListPoke = pokeData.filter((entry) => {
if (this.flags[entry.pokemon_name]) {
// console.log('flags', false);
return false;
}
this.flags[entry.pokemon_name] = true;
return true;
});
console.log(JSON.stringify(this.uniqueListPoke));
},
error => this.errorMessage = <any>error
);
return false;
}
}
The working example:
https://stackblitz.com/edit/angular-distinct-poke?file=src/app/hello.component.ts

Update on array change

if I add an object to the objectListDrawArea array outside of the original class, it will be added, but my * ngFor cannot find the object. I have absolutely no idea how to solve it, should I use Observable, if so can you leave an example in the comments? Thank you
ao-bar.component.ts
import { AoBarService } from './ao-bar.service';
import { DrawAreaComponent } from '../../../draw-area/draw-area/draw-area.component';
#Component({
selector: 'app-ao-bar',
templateUrl: './ao-bar.component.html',
styleUrls: ['./ao-bar.component.sass']
})
export class AoBarComponent implements OnInit {
objectsList: object[] = new Array();
showObjectsList: boolean;
drawAreaComponent: DrawAreaComponent = new DrawAreaComponent();
constructor(private service: AoBarService) {
this.service.getObject(this.objectsList);
}
ngOnInit() {
console.log(this.objectsList, ' AoBarComponent');
}
private onShowObjectsList() {
this.showObjectsList = !this.showObjectsList;
}
public onDragEnd(event: DragEvent): void {
console.log('drag end', event);
if (this.drawAreaComponent.onDragEnter) {
for (let object of this.objectsList) {
if (object.name == event.path[0].nextElementSibling.innerText) {
object.settings.x = event.x;
object.settings.y = event.y;
this.drawAreaComponent.createObject(object);
}
}
}
}
}
draw-area.component.ts:
import { Component, OnInit } from '#angular/core';
import { CdkDragEnd } from '#angular/cdk/drag-drop';
#Component({
selector: 'app-draw-area',
templateUrl: './draw-area.component.html',
styleUrls: ['./draw-area.component.sass']
})
export class DrawAreaComponent implements OnInit {
objectsList: object[] = new Array();
constructor() {
}
ngOnInit() {
}
public onDragEnter(event: DragEvent): boolean {
console.log('drag enter', event);
return true;
}
public onDragEnd(event: CdkDragEnd): void {
console.log(event);
}
public createObject(objectToCreate: object): void {
this.objectsList.push(objectToCreate);
console.log(`Aktuelle Liste: ${this.objectsList}`);
}
}
draw-area.component.html :
<div id="DrawAreaComponent">
<div
class="example-boundary"
(dragenter)="onDragEnter($event)"
>
<div
id="ContainerObject"
*ngFor="let object of objectsList | async"
cdkDragBoundary=".example-boundary"
cdkDrag
(cdkDragEnded)="onDragEnd($event)"
>
<img id="ImgObject" [src]="object.imgUrl">
</div>
</div>
</div>
this is a change detection issue.
instead of this.objectsList.push(objectToCreate);
use this.objectsList = [...this.objectsList, objectToCreate]; it will work.
read about change detection.

AngularJS passing function to the modal, "this" is not referring to the controller I want

I'm having issue while trying to use function of the parent controller of my modal.
From the parent controller I open a modal, in the modal I do actions and when I click on a button I want to use the function parentOnChange of the parent controller.
To do this I pass the parentOnChange function of the parent controller through the resolve function of IModalService.
In parentOnChange I use other functions of the parent controller, the problem is : keyword "this" in parentOnChange is not referring the parent controller but to the modal controller, so it trigger an error :
this.oneFunction is not a function
I can't figure out how to solve this :)
Any help would be appreciated ;)
Code is written in angular#1.6.6 with typescript
Here is a simplified example :
The parent controller
export class Controller implements IComponentController {
public static $inject: string[] = [
'$uibModal'
];
constructor(
private readonly $uibModal: IModalService
) { }
private openContextMenu(object: MyInterface) {
const modal = this.$uibModal.open({
component: 'myContextMenuModal',
resolve: {
object: () => object,
onChange: () => this.parentOnChange
}
});
}
public parentOnChange(object: MyInterface) {
console.log(this);
this.oneFunction();
this.twoFunction(object);
}
public oneFunction() {
...
}
public twoFunction(object: MyInterface) {
...
}
}
The modal
export class Controller {
// Bindings
public readonly close: (data: any) => void;
public readonly dismiss: () => void;
public readonly resolve: {
object: MyInterface,
onChange(object: MyInterface) : void
};
public object: MyInterface;
public onChange: (object: MyInterface) => void;
public button_Click(): void {
...
this.Change(this.object);
...
}
}
export default {
template: htmlTemplate,
controller: Controller,
bindings: {
resolve: '<',
close: '&',
dismiss: '&'
}
};
Cannot figure out how to solve it, neither got time to create a plunker...
Anyway I did it a different way :
The parent controller
export class Controller implements IComponentController {
...
constructor(
private $scope: IScope,
private readonly $uibModal: IModalService
) {
this.$scope.$on('myEventName', this.parentOnChange.bind(this));
}
...
private openContextMenu(object: MyInterface) {
const modal = this.$uibModal.open({
component: 'myContextMenuModal',
scope: this.$scope, // Passing current scope to the modal
resolve: {
object: () => object,
onChange: () => this.parentOnChange
}
});
}
public parentOnChange(_event: IAngularEvent, args: MyInterface[]) {
console.log(args[0]); // this is my object send by the modal
this.oneFunction();
this.twoFunction(object);
}
public oneFunction() {
...
}
public twoFunction(object: MyInterface) {
...
}
}
The modal
export class Controller {
...
// Bindings
public readonly close: (data: any) => void;
public readonly dismiss: () => void;
public readonly resolve: {
object: MyInterface
};
public object: MyInterface;
public button_Click(): void {
...
this.$scope.$emit('myEventName', [this.object]);
...
}
}

why subscribe function is not called in angular 2?

I am using observable in angular .Actually my issue when I click button my subscribe function not called why ?
as per documentation subscribe function will call when we call next function
https://plnkr.co/edit/83NaHoVaxiXAeUFoaEmb?p=preview
constructor() {
this.data = new Observable(observer => this.dataObserver = observer);
this.data.subscribe(value => {
console.log('+++')
console.log(value)
})
}
hndle(){
this.name.push({name:"navee"});
this.dataObserver.next(this.name);
}
here is documentation
http://reactivex.io/rxjs/manual/tutorial.html
On basis of Volodymyr Bilyachat suggestion i have modified your code. its working now plz check. Problem was in your way of using dataObserver
//our root app component
import {Component, NgModule} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
import 'rxjs/Rx';
import {Observable} from 'rxjs/Observable';
#Component({
selector: 'my-app',
template: `
<div>
<ul>
<li *ngFor ="let n of name">{{n.name}}</li>
</ul>
<button (click)="hndle()">heelo</button>
</div>
`,
})
export class App {
private data:Observable;
private dataObserver:Observer;
name:string;
name[];
constructor() {
this.dataObserver = new Observable(observer => this.dataObserver = observer);
this.dataObserver.subscribe(value => {
console.log('+++')
console.log(value)
});
}
hndle(){
this.name.push({name:"navee"});
this.dataObserver.next(this.name);
}
}
#NgModule({
imports: [ BrowserModule ],
declarations: [ App ],
bootstrap: [ App ]
})
export class AppModule {}
link https://plnkr.co/edit/PO80y2udrOhsVq4QQXc5?p=preview
I believe you are subscribing to the observable 2 times. You should be able to fix it by adding .share()
constructor() {
this.data = new Observable(observer => this.dataObserver = observer).share();
this.data.subscribe(value => {
console.log('+++')
console.log(value)
})
}
hndle(){
this.name.push({name:"navee"});
this.dataObserver.next(this.name);
}
In your case, it's better to use this solution:
constructor() {
this.data = new Subject();
this.data.subscribe(value => {
console.log('+++');
console.log(value);
});
}
hndle() { // TYPO: Probably it was meant to be handle
this.name.push({
name: 'navee'
});
this.data.next(this.name);
}
Don't forget to add:
import { Subject } from 'rxjs/Subject'
Working example:
https://plnkr.co/edit/zB8FHTVEm2QUHiEAYuQB?p=preview

Detect if Observable not found using combineLatest

I need to modify my code where loading detail category will first look whether it is not already loaded in the statement, and if not then detail loads. Thanks for help!
Constructor of CategoryProvider:
private _obServers = {
'categoryList': undefined,
'category': undefined,
'idCategory': new Subject<Number>()
};
constructor(){
this.categories = new Observable(observer => this._obServers.categoryList = observer).share();
this._categoryObservable = this.categories
.combineLatest(this._obServers.idCategory, (categories, idCategory) => {
return categories.filter(category => category.id === idCategory)[0];
})
.distinctUntilChanged((oldCategory, newCategory) => {
return oldCategory.id === newCategory.id;
});
}
CategoryList:
loadCategories(search?:string):void{
this._http
.get('/services/category/list?search=' + search)
.map(res => res.json())
.subscribe(data => {
this._obServers.categoryList.next(this.createCategoryEntities(data));
});
}
CategoryDetail:
loadCategory(categoryId:number){
this._obServers.idCategory.next(categoryId);
//If category not loaded I need to load it
}
I have followed this way https://github.com/JonatanSCS/Angular-2-Tutorial/blob/master/node_modules/rxjs/src/add/observable/combineLatest.ts
import { Component, Injectable, Inject, provide } from '#angular/core';
import { HTTP_PROVIDERS } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { combineLatestStatic } from 'rxjs/operator/combineLatest.js';
import { MessageApi } from '../providers/lb-service/lb-services.provider'
import { EstimatesService } from './estimates.service';
#Component({
pipes: [TranslatePipe],
})
#Injectable()
export class InvoiceService {
constructor(private _message:MessageApi,
#Inject(EstimatesService) _estimates:EstimatesService) {
this._message = _message;
this._estimates = _estimates;
Observable.combineLatest = combineLatestStatic;
declare module 'rxjs/Observable' {
namespace Observable {
export let combineLatest: typeof combineLatestStatic;
}
}
Observable.combineLatest(
this._estimates.getEstimates(),
this._message.findOne({
where: {
moduleTag: 'monthlyStat',
'dynamic.date': "2016-07-01" //new Date
},
fields: {
dynamic: true
}
}),this._message.findOne({
where: {
moduleTag: 'areaPriceSE1',
'dynamic.date': ''
},
fields: {
dynamic: true
}
})
).subscribe(res => console.log("observable", res));

Resources