How to define a "global" variable in a component? (Angular4) - javascript

I have an app-editor-component which contains a lot of nested components inside it's structure. When the component is loaded, it creates an array: obj_list : MyObject[]
Many of the nested components will contain a <select> element, where one of the elements in obj_list has to be selected in each.
How is it possible to share this list with all the elements in the structure?

One way to share data between nested components is Input/Output+EventEmitter system.
Or, you can use shared service to transfer data between components.
Here are some links to official docs and good post from firebase about component interaction:
Docs
Post
In your case, for example, you can pass obj_list from app-editor-component down to child components through Inputs, then, in child components, observe <select>’s change event and emit changes back to app-editor-component.
But, if you have deep nesting, using service is better approach

The entity that is shared by several nesting components should be a service. This is naturally provided by Angular hierarchical injectors.
More importantly, if data is supposed to be changed asynchronously, components should be notified of this somehow. This is conveniently done with RxJS observables:
import { Subject } from 'rxjs/Subject';
#Injectable()
class Foo {
objListSubject = new Subject();
objList$ = this.objListSubject.asObservable();
}
#Component({
providers: [Foo] // belongs to NgModule providers if the service is global
...
})
class ParentComponent {
constructor(private foo: Foo) {
...
this.foo.next(['bar']);
}
}
#Component({
template: `
<select ...>
<option *ngFor="foo.objList$ | async">
</select>
`,
...
});
class ChildComponent {
constructor(public foo: Foo) {}
}

Related

Manipulating a reusable component based on input

I have created a reusable component and using it twice inside a component. But I need two buttons, that can manipulate the component individually.
In my case the button for component1 should not update both the instance of the component.
I think I'm doing something wrong by design, but any suggestion will help me.
Stackblitz
Reusable Component:-
import { Component, OnInit,Input } from '#angular/core';
import { AppService } from '../app.service';
#Component({
selector: 'app-reusable',
templateUrl: './reusable.component.html',
styleUrls: ['./reusable.component.css']
})
export class ReusableComponent implements OnInit {
#Input() items:any;
constructor(
private service:AppService
) { }
ngOnInit() {
this.service.addFruit.subscribe(()=>{
this.items.unshift({fruit:'Blackberry'});
});
}
}
Usage:-
<button type="button" (click)="Add()">Add Component1</button>
<app-reusable [items]="fruitList1"></app-reusable>
<hr/>
<button type="button" (click)="Add()">Add Component2</button>
<app-reusable [items]="fruitList2"></app-reusable>
I want to update only one instance of reusable component at once.
Either instance 1 or 2.
You have to let the service know which component you are calling from.
Try the changes I made in the demo.
app.component.html
<button type="button" (click)="Add(1)">Add Component1</button>
<app-reusable [items]="fruitList1" [componentNumber]="1"></app-reusable>
app.component.ts:
Add(componentNumber:number){
this.service.addFruit.next(componentNumber);
}
reusable.component.ts:
#Input() componentNumber: number;
ngOnInit() {
this.service.addFruit.subscribe((x) => {
if (x == this.componentNumber)
this.items.unshift({ fruit: 'Blackberry' });
});
}
Working Stackbiltz
More cleaner approch would be simply pass the component instance and call related method so create a method in your reusable component something like
addFruit(){
this.items.unshift({fruit:'Blackberry'});
}
Modify your add method to get component as instance and call this method
Add(instance:ReusableComponent){
instance.addFruit();
}
Then add hash to seprate each instance and pass instance in method
<button type="button" (click)="Add(instance1)">Add Component1</button>
<app-reusable [items]="fruitList1" #instance1></app-reusable>
Working demo
Each instance of ReusableComponent is subscribed to the Subject addFruit. Clicking on the button will update Subject value which will trigger all subscriptions.
In order to avoid this, you will need to add a filter in a subscription which ignores values from other components by adding some when doing this.service.addFruit.next();. You can do filtering with RXJS filter operator. https://rxjs-dev.firebaseapp.com/api/operators/filter
Another idea is to create a subscription for each component in service and save them in some map/object in service. When a component requests a subscription from service it would add an entry to map which would be subjectId: new Subject(). You would return that new subject for the component to subscribe. Instead of doing next() directly you would call service method addNewFruit(subjectId: string, newFruit: string): void.
Map would be:
{
'firstId': Subject,
'secondId': Subject,
}
The most simple idea for this case is to use ViewChild and call method addFruit from the parent component.
Instead of having subscription of APP service in the reusable component , on click of button you should modified the input provided to your components. If you update the fruitList1 or fruitList2 at a time then it would not update the another instance of the component.

Scope of an imported variable in ionic v3 [duplicate]

I have a constants file constants.ts:
export const C0NST = "constant";
I access it in a service some.service.ts like so:
import { C0NST } from './constants';
console.log(C0NST); // "constant"
However, when I access it in a component template:
some.component.ts:
import { C0NST } from './constants';
some.component.html:
{{ C0NST }} <!-- Outputs nothing -->
However defining a member in the component class works:
some.component.ts
public const constant = C0NST;
some.component.html
{{ constant }} <!-- constant -->
I don't understand why I was able to access the imported constant directly in the service class but not in the component template even though I imported it in the component class.
In Angular2, the template can only access fields and methods of the component class. Everything else is off-limits. This includes things which are visible to the component class.
The way to go around this is to have a field inside the component, which just references the constant, and use that instead.
It's one limitation of the design, but perhaps you should think a bit more about why you need a constant in the template in the first place. Usually these things are used by components themselves, or services, but not the template.
Since in the Component's template you can only use attributes of the Component's class, you can't directly use any external constants (or external variables).
The most elegant way that I've found so far is the following:
import { MY_CONSTANT } from '../constants';
#Component({
// ...
})
export class MyTestComponent implements OnInit {
readonly MY_CONSTANT = MY_CONSTANT;
// ...
}
which basically just creates a new attribute MY_CONSTANT inside the component class. Using readonly we make sure that the new attribute cannot be modified.
Doing so, in your template you can now use:
{{ MY_CONSTANT }}
The scope of Angular2 template bindings is the component instance. Only what's accessible there can be used in bindings.
You can make it available like
class MyComponent {
myConst = CONST;
}
{{myConst}}
There are two best directions in my opinion:
Wrapping constants as internal component property
enum.ts
export enum stateEnum {
'DOING' = 0,
'DONE',
'FAILED'
}
component.ts
import { stateEnum } from './enum'
export class EnumUserClass {
readonly stateEnum : typeof stateEnum = stateEnum ;
}
Example uses enum, but this can be any type of defined constant. typeof operator gives you all of benefits of TypeScript typing features. You can use then this variable directly in templates:
component.html
<p>{{stateEnum.DOING}}<p>
This solution is less efficient in memory usage context, because you are basically duplicating data (or references to constants) in each component you wish to use it. Beside that, syntax
readonly constData: typeof constData = constData
in my opinion introduce a lot of syntax noise and may be confusing to newcommers
Wrapping external constant in component function
Second option is to wrap your external variable/constant with component function and use that function on template:
enum.ts
export enum stateEnum {
'DOING' = 0,
'DONE',
'FAILED'
}
component.ts
import { stateEnum } from './enum'
export class EnumUserClass {
getEnumString(idx) {
return stateEnum[stateEnum[idx]];
}
}
component.html
<p>{{getEnumString(1)}}</p>
Good thing is that data is not duplicated in controller but other major downside occur. According to Angular team, usage of functions in templates is not recommended due to change detection mechanism, which works way less efficient in case of functions returning values to templates: change detection have no idea does value return by a function has changed, so it will be called way often than needed (and assuming you returning const from it, it's actually needed only once, when populating template view. It may be just a bit efficiency killing to your application (if you are lucky) or it may totally break it down if function resolves with Observable for instance, and you use async pipe to subscribe to results. You can refer to my short article on that HERE
You can create a BaseComponent , it is a place where you should create your constant instances and then you can create your FooComponent extends BaseComponent and you can use your constants.

draft state/publish changes on save in angular 2

I'm using angular 4 in my application and currently the user is able to make changes over multiple components(drag and drop, remove items, add items and etc...).
Now, for every user action there is a http requests via the relevant service that persist the changes on the DB.
There is a requirement that the user will be able to make this changes and only persist them once he done changing and pressed save. (it can be 10-50 actions from different types.)
How would you suggest refactoring the code in order to support that ? to make an array of user actions, and on save iterate over the array and make the relevant actions one by one, write some http middleware to hold all http calls until 'save' is pressed?
You should look into redux. This would allow a MVP programming model and works just fine with Angular. ng2-redux
Just so you know the problem you are facing has a name.
"Application State Management"
This can be solved via redux like libraries (redux/ rxjs-store rxjs-effects etc)..
Or you could just use plain rxjs BehaviourSubject or Subject as Observable.
here is a plunker example of using plain rxjs observables and angular services to achieve state management.
https://embed.plnkr.co/dEDJri4TziCS91oZiuHb/
TL;DR
This is the services
#Injectable()
export class AppStateService{
private _dataSaved = new Subject<string>();
public dataSaved$ = this._dataSaved.asObservable()
constructor() {}
dispatchSaveEvent(data: String){
this._dataSaved.next(data);
}
}
This is the component that will dispatch the save event
#Component({
selector: 'my-footer',
template: `
<button (click)="saveData($event)">Save</button>
`
})
export class Footer implements OnInit {
constructor(private appState: AppStateService) {}
ngOnInit() {}
saveData(e){
this.appState.dispatchSaveEvent("Some data to save here...");
}
}
This is how you consume the observable in every component that is interested that a saved has occurred
#Component({
selector: 'my-comp-1',
template: `
<h1>Component-1! {{savedDataRecived}}</h1>
`
})
export class Comp1 implements OnInit {
savedDataRecived = "";
constructor(private appState: AppStateService) {}
ngOnInit() {
this.appState.dataSaved$.subscribe(data=> this.handleSaveEvent(data))
}
handleSaveEvent(data: string){
this.savedDataRecived = data;
}
}

How to pass data from parrent constructor to child component in angular 2

I have a angular 2 page where i need to show 2 different components using same array of data from external API. Parent is regular component, and child is shared among several other components using same functionality.
In parent component class i have output property declared:
public weatherList: WeatherForecast[];
#Output() public weatherListData: any;
Inside constructor of parent component, i populate weatherListData property with data from an external API
http.get(url)
.subscribe(result => {
this.weatherList= result.json() as WeatherForecast[];
this.weatherListData = this.weatherList;
});
and i'm using it inside parent template with success, something like: {{ weatherList.someValue }}
Also, inside parent component template, i have a call to a child component
<daily-temperature-chart [weatherListData]='weatherListData'></daily-temperature-chart>
In child component class i have declared property
#Input() weatherListData: any;
but, when i try to access weatherListData property in constructor, or init of child component, i get undefined result.
EDIT: I have played with console.log() and noticed that child component Constructor and OnInit() methods return before http.get() from parent component. Maybe this is problem, but i'm still new to angular and can't tell.
Can someone point me how to solve this?
You've a service call so you can't go for constructor or OnInit because component initialization is not dependent on your service call for this situation angular provides OnChanges whenever your input value is updated OnChanges fired.
ngOnChanges(changes: any){
console.log(changes);
console.log(this.weatherListData);
}
OnChanges passes as well an argument which informs about the current state and pervious state now you are able to use input values. If your components are bases on input and input is based on any other operation you can handle it in this block.

Angular2: Creating child components programmatically

Question
How to create child components inside a parent component and display them in the view afterwards using Angular2? How to make sure the injectables are injected correctly into the child components?
Example
import {Component, View, bootstrap} from 'angular2/angular2';
import {ChildComponent} from './ChildComponent';
#Component({
selector: 'parent'
})
#View({
template: `
<div>
<h1>the children:</h1>
<!-- ??? three child views shall be inserted here ??? -->
</div>`,
directives: [ChildComponent]
})
class ParentComponent {
children: ChildComponent[];
constructor() {
// when creating the children, their constructors
// shall still be called with the injectables.
// E.g. constructor(childName:string, additionalInjectable:SomeInjectable)
children.push(new ChildComponent("Child A"));
children.push(new ChildComponent("Child B"));
children.push(new ChildComponent("Child C"));
// How to create the components correctly?
}
}
bootstrap(ParentComponent);
Edit
I found the DynamicComponentLoader in the API docs preview. But I get the following error when following the example: There is no dynamic component directive at element 0
This is generally not the approach I would take. Instead I would rely on databinding against an array that will render out more child components as objects are added to the backing array. Essentially child components wrapped in an ng-for
I have an example here that is similar in that it renders a dynamic list of children. Not 100% the same, but seems like the concept is still the same:
http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0
Warning: DynamicComponentLoader has been deprecated in RC.4
In Angular 2.0, loadIntoLocation method of DynamicComponentLoader serve this purpose of creating parent-child relationship. By using this approach you can dynamically create relationship between two components.
Here is the sample code in which paper is my parent and bulletin is my child component.
paper.component.ts
import {Component,DynamicComponentLoader,ElementRef,Inject,OnInit} from 'angular2/core';
import { BulletinComponent } from './bulletin.component';
#Component({
selector: 'paper',
templateUrl: 'app/views/paper.html'
}
})
export class PaperComponent {
constructor(private dynamicComponentLoader:DynamicComponentLoader, private elementRef: ElementRef) {
}
ngOnInit(){
this.dynamicComponentLoader.loadIntoLocation(BulletinComponent, this.elementRef,'child');
}
}
bulletin.component.ts
import {Component} from 'angular2/core';
#Component({
selector: 'bulletin',
template: '<div>Hi!</div>'
}
})
export class BulletinComponent {}
paper.html
<div>
<div #child></div>
</div>
Few things you needs to be take care of are mentioned in this answer
You should use ComponentFactoryResolver and ViewElementRef to add component at runtime.Let's have a look at below code.
let factory = this.componentFactoryResolver.resolveComponentFactory(SpreadSheetComponent);
let res = this.viewContainerRef.createComponent(factory);
Put the above code inside your ngOnInit function and replace "SpreadSheetComponent" by your component name.
Hope this will work.
Programmatically add components to DOM in Angular 2/4 app
We need to use ngAfterContentInit() lifecycle method from AfterContentInit. It is called after the directive content has been fully initialized.
In the parent-component.html, add the a div like this:
<div #container> </div>
The parent-component.ts file looks like this:
class ParentComponent implements AfterContentInit {
#ViewChild("container", { read: ViewContainerRef }) divContainer
constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
ngAfterContentInit() {
let childComponentFactory = this.componentFactoryResolver.resolveComponentFactory(childComponent);
this.divContainer.createComponent(childComponentFactory);
let childComponentRef = this.divContainer.createComponent(childComponentFactory);
childComponentRef.instance.someInputValue = "Assigned value";
}
}
Inside src\app\app.module.ts, add the following entry to the #NgModule() method parameters:
entryComponents:[
childComponent
],
Notice that we're not accessing the div#container using the #ViewChild("container") divContainer approach. We need it's reference instead of the nativeElement. We will access it as ViewContainerRef:
#ViewChild("container", {read: ViewContainerRef}) divContainer
The ViewContainerRef has a method called createComponent() which requires a component factory to be passed as a parameter. For the same, we need to inject a ComponentFactoryResolver. It has a method which basically loads a component.
The right approach depends on the situation you're trying to solve.
If the number of children is unknown then NgFor is the right approach.
If it is fixed, as you mentioned, 3 children, you can use the DynamicComponentLoader to load them manually.
The benefits of manual loading is better control over the elements and a reference to them within the Parent (which can also be gained using templating...)
If you need to populate the children with data, this can also be done via injection, the Parent is injected with a data object populating the children in place...
Again, a lot of options.
I have used 'DynamicComponentLoader' in my modal example, https://github.com/shlomiassaf/angular2-modal

Categories

Resources