In Angular 9, can I store a property in a place where all components can reference? - service

I'm using Angular 9. I have this in most of my components' ...
import { DeviceDetectorService } from 'ngx-device-detector';
...
export class FirstComponent implements OnChanges, OnInit {
...
isMobile: boolean = false;
constructor(private deviceService: DeviceDetectorService) { }
ngOnInit(): void {
this.isMobile = this.deviceService.isMobile();
}
and then in the components themselves I have
<mat-table *ngIf="isMobile" #table [dataSource]="dataSource">
...
</mat-table>
<mat-table *ngIf="!isMobile" #table [dataSource]="dataSource">
...
</mat-table>
My question is, is there a more efficient way of doing this, in other words, can I place the "isMobile" property in one place and have all my components reference that place instead of defining it in each of my components?

Related

Change a ViewModel Property from a different class and update the View - MVVM

I need to change the Visibility of a Button on a View from method call from within a class.
I have tried accessing the VeiwModel by exposing it in the class, and then had success in changing the Property "ShowRedHat" from true to false, but this does not update the Visibility of the Button in the View. This also double loads the ViewModel, which is not acceptable in my solution.
Any help is appreciated.
The class:
public class HatEngine
{
public void SetShowRedHat()
{
????.ShowRedHat = false;
}
}
The Property in the ViewModel:
public class MyViewModel : ObservableObject
{
private bool _showRedHat;
public bool ShowRedHat
{
get { return _showRedHat; }
set
{
OnPropertyChanged(ref _showRedHat, value);
}
}
}
The Button in the View:
<Button Content="Red Hat"
Command="{Binding RedHatCommand}"
Visibility="{Binding ShowRedHat, Converter={StaticResource BoolToVis}}"/>
If the purpose of HatEngine is to be a service that is used by MyViewModel, then something like the following be the start of getting what you need.
This example uses dependency injection via the constructor; this is common in MVVM and if you're not familiar with it, I would highly recommend looking into it further.
// define delegate for event to be fired from HatEngine instances
public delegate void HatEngineNotifyEventHandler(object sender, bool shouldShow);
// interface declaration for HatEngine - this is important for injecting mocks for unit testing
public interface IHatEngine
{
event HatEngineNotifyEventHandler Notify;
void SetShowRedHat(bool show);
}
// simple IHatEngine implementation
public sealed class HatEngine : IHatEngine
{
public event HatEngineNotifyEventHandler Notify;
public void SetShowRedHat(bool show) => OnNotify(show);
private void OnNotify(bool shouldShow) =>
Notify?.Invoke(this, shouldShow);
}
public class MyViewModel : ObservableObject
{
private readonly IHatEngine _hatEngine;
private bool _showRedHat;
// MyViewModel consumes an IHatEngine instance and subscribes to its Notify event
public MyViewModel(IHatEngine hatEngine = null)
{
// many MVVM frameworks include a DI container that should be used here
// to resolve an IHatEngine instance; however, for simplicity for this
// example just create HatEngine() directly
_hatEngine = hatEngine ?? new HatEngine();
// when the event is received, update ShowRedHat accordingly
_hatEngine.Notify += (_, shouldShow) => ShowRedHat = shouldShow;
}
public bool ShowRedHat
{
get => _showRedHat;
set => OnPropertyChanged(ref _showRedHat, value);
}
}
You can just bind an integer since Visibility is an Enum, check documentation since in some versions Hidden option is not available and Collapsed becomes 1, however normally you can just use these below:
Visible [0] - Display the element.
Hidden [1] Do not display the element, but reserve space for the
element in layout.
Collapsed [2] Do not display the element, and do not reserve space for
it in layout.

How to dynamically change the children in a view in a vsc extension

The code examples for TreeDataProviders on github sometimes show a refresh method but I'm not sure how to use it. Do I just call refresh() and pass in the data to be used by getChildren() and just update the class property that getChildren uses?
The refresh() function usually triggers the onDidChangeTreeData even, to which the base class (TreeDataProvider) is listening. It will then call getChildren again to re-fill the tree. See also the description of that event:
/**
* An optional event to signal that an element or root has changed.
* This will trigger the view to update the changed element/root and its children recursively (if shown).
* To signal that root has changed, do not pass any argument or pass `undefined` or `null`.
*/
onDidChangeTreeData?: Event<T | undefined | null>;
You can design the refresh function as you like, e.g. passing in new data, or you keep a reference to an applicationm data provider in the tree provider (e.g. passed to it in the c-tor). Up to you.
There are a few methods on the TreeDataProvider that are important to know about...
getChildren - method to obtain the data for items that will be displayed in the tree. This should return an array -- don't worry about turning the data into a TreeItem yet, this is just raw data.
getTreeItem - called on each item in the array returned by getChildren. Should return a single TreeItem using the data provided..
onDidChangeTreeData - a vscode.Event that, when changed, will trigger getChildren to be re-evaluated. This can be done by creating a vscode.EventEmitter(let's call it eventEmitter) and calling the fire method on the eventEmitter. This will cause the eventEmitter.event to be updated/triggered.
Here is an example of how to set up a TreeDataProvider, that I hope will help illustrate how to create the EventEmitter, set the TreeDataProvider's onDidChangeTreeData property to the event of the EventEmitter, and create/export a refresh method that can be called to trigger an update of the data.
TreeDataProvider
import * as vscode from 'vscode';
export class AccountsProvider implements vscode.TreeDataProvider<Account> {
private accounts: Array<Account>;
constructor() {
this.accounts = getAccounts();
}
_onDidChangeTreeData: vscode.EventEmitter<undefined> =
new vscode.EventEmitter<undefined>();
onDidChangeTreeData: vscode.Event<undefined> =
this._onDidChangeTreeData.event;
refresh(): void {
this._onDidChangeTreeData.fire(undefined);
}
getTreeItem(a: Account): vscode.TreeItem {
return new AccountTreeItem(
a.name,
a.id,
a,
vscode.TreeItemCollapsibleState.None
);
}
getChildren(): Thenable<Account[] | undefined> {
this.accounts = getAccounts();
if (this.accounts) {
return Promise.resolve(this.accounts);
}
return Promise.resolve([]);
}
}
TreeItem
export class AccountTreeItem extends vscode.TreeItem {
constructor(
public readonly name: string,
public readonly id: string,
public readonly accountData: Account,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public readonly iconPath: string = new vscode.ThemeIcon('account'),
public readonly contextValue: string = 'accountTreeItem'
) {
super(name, collapsibleState);
this.tooltip = `Active Account: ${accountData.name}`;
}
}
Importing and triggering refresh method
import * as vscode from 'vscode';
import { AccountsProvider } from 'accountsProvider'; // The TreeDataProvider
const accountProvider = new AccountsProvider();
context.subscriptions.push(
vscode.commands.registerCommand('ext.accounts.refresh', () => {
accountProvider.refresh();
})
);
vscode.commands.executeCommand('ext.accounts.refresh');

Custom Struct Property Editor Add Setter and Listener

I have been struggling with this for a while and would really appreciate some guidance.
The goal is to create a dropdown list/combo box that contains strings that the user can select in the editor. This is to extend details customization.
I have created a custom structure like so.
USTRUCT()
struct FTaskComponentData
{
GENERATED_BODY()
UPROPERTY(VisibleAnywhere)
TArray<FString> Names;
};
Then I have created a class to implement the IPropertyTypeCustomization like so:
(Header file):
class FTaskComponentDataCustomization : public IPropertyTypeCustomization
{
public:
static TSharedRef<IPropertyTypeCustomization> MakeInstance();
/** IPropertyTypeCustomization interface */
virtual void CustomizeHeader(TSharedRef<class IPropertyHandle> StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override;
virtual void CustomizeChildren(TSharedRef<class IPropertyHandle> StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override;
private:
TSharedPtr<IPropertyHandle> UPropertyTaskNameHandle;
/** Visibility delegate for the various methods of calculating magnitude */
EVisibility GetPropertyVisibility(UProperty* InProperty) const;
};
(Source file):
TSharedRef<IPropertyTypeCustomization> FTaskComponentDataCustomization::MakeInstance()
{
return MakeShareable(new FTaskComponentDataCustomization());
}
void FTaskComponentDataCustomization::CustomizeHeader(TSharedRef<class IPropertyHandle> StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
uint32 NumChildren;
StructPropertyHandle->GetNumChildren(NumChildren);
for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex)
{
const TSharedRef< IPropertyHandle > ChildHandle = StructPropertyHandle->GetChildHandle(ChildIndex).ToSharedRef();
if (ChildHandle->GetProperty()->GetName() == TEXT("Names"))
{
UPropertyTaskNameHandle = ChildHandle;
}
}
check(UPropertyTaskNameHandle.IsValid());
static TArray< TSharedPtr<FString>> something;
something.Add(TSharedPtr<FString>(new FString("One")));
something.Add(TSharedPtr<FString>(new FString("Two")));
something.Add(TSharedPtr<FString>(new FString("Three")));
HeaderRow.NameContent()
[
StructPropertyHandle->CreatePropertyNameWidget(LOCTEXT("Task", "Task Names"), LOCTEXT("IDK","IDK"), false, true, false)
]
.ValueContent()
.MinDesiredWidth(250)
[
SNew(STextComboBox)
.OptionsSource(&something)
];
}
void FTaskComponentDataCustomization::CustomizeChildren(TSharedRef<class IPropertyHandle> StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
}
EVisibility FTaskComponentDataCustomization::GetPropertyVisibility(UProperty* InProperty) const {
return EVisibility::Visible;
}
I have then included this custom structure in my component subclass like this:
UPROPERTY(EditAnywhere, Category = "Planning | Task")
FTaskComponentData TaskData;
I then register with the FPropertyEditorModule like so:
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
//Custom properties
PropertyModule.RegisterCustomPropertyTypeLayout("TaskComponentData", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FTaskComponentDataCustomization::MakeInstance));
At this point, the desired combo box shows up.. I have 2 questions:
1) How can I set TArray so that I can update the data members with different strings?
2) How do I register the property change listener? Right now, none of the property change listeners are fired from my component when I try to select values from my combobox.
Or where do I got to dig up any of this functionality?

angular2 / typescript class inheritance with generic types

Hope one of you angular2 / typescript wizards can help out or at least provide a pointer in the right direction, before I got crazy :-)
Here is what I'd like to
have a parent class that implements it's own defined parent Interface, however using Generic Types so I can when creating a child class provide it with the child's specific and tailored class & data Interface.
the child class should be able to extend the parent data class by
being able to overwrite default/parent set variables
overwriting parent functions() and have the child's version called instead of the parent's default
In the below pseudo code example, I would like the call to the child's (inherited) someOtherfunction() to return "2"...
Am I asking for too much?
I can't seem to find any decent examples on the web...
How do I get this right?
Thank you -
Oliver
(CODE BELOW MAY BE BROKEN, IT'S JUST FOR ILLUSTRATION)
//
// Parent Class
//
export interface ICoreData <T> {
observeItems: Observable <T[]> ;
items: Array <T>;
}
#Injectable()
export class CoreData<T> implements ICoreData<T> {
public observeItems: Observable<T[]>;
private items: Array<T>;
constructor( 'Dependency Injection...' ) {}
coreFunction(): number {
return 1;
}
someOtherfunction(){
return this.coreFunction();
}
}
//
// Child class
//
export interface IMyDataStructure {
name: string;
age: string;
}
export interface ISpecificData extends ICoreData<IMyDataStructure> {
someExtraKey: number;
}
#Injectable()
export class SpecificData extends CoreData<IMyDataStructure> implements ISpecificData {
constructor() {
super();
}
coreFunction(): number{
//
// This function should "overwrite" the parent's original function
// and be called by the parent's someOtherfunction() function
//
return 2;
}
}
You're not asking too much. However you can't use interfaces to accomplish what you're trying to accomplish. You need to extend a class, which can be generic.
An interface is simply a contract, or a blueprint if you like, for a data type. There is no functionality associated with an interface. However in your case you wanted to be able to have methods on the base class; methods you could override in the derived.
The way I usually do this is to declare an abstract base class (so that the base class can't be instantiated itself), and then extend classes from that. Here's an example:
Note, I've removed all the Angular2 cruft in order to keep the example as simple as possible.
abstract class Base<T> {
constructor(public controlled: T) { }
doIt(): string {
return `Base.doIt: ${JSON.stringify(this.controlled)}`;
}
doSomethingElse(): string {
return `Base.doSomethingElse: ${JSON.stringify(this.controlled)}`;
}
};
interface Foo {
foo: string;
bar: string;
};
class Derived extends Base<Foo> {
constructor(foo: Foo) {
super(foo);
}
doSomethingElse(): string {
return `Derived.doSomethingElse: ${JSON.stringify(this.controlled)}`;
}
};
let d: Derived = new Derived({ foo: 'foo', bar: 'bar' });
console.log(`doIt ==> ${d.doIt()}`);
console.log(`doSomethingElse ==> ${d.doSomethingElse()}`);
Output:
doIt ==> Base.doIt: {"foo":"foo","bar":"bar"}
doSomethingElse ==> Derived.doSomethingElse: {"foo":"foo","bar":"bar"}
Playground link.

Exchange Data between multi step forms in Angular2: What is the proven way?

I can imagine following approaches to exchange Data between multi step forms:
1) Create a component for each form step and exchange data between components over #input, #output (e.g. you cannot change from step5 to 2)
2) Use the new property data in the new router (see here) (e.g. you cannot change from step5 to 2))
3) A shared Service (Dependency Injection) to store data (Component Interaction) (e.g. you can change from step5 to 2)
4) New rudiments with #ngrx/store (not really experienced yet)
Can you give some "gained experience values", what do you use and why?
See my edit below.
Using SessionStorage is not strictly the 'angular' way to approach this in my opinion—a shared service is the way to go. Implementing routing between steps would be even better (as each component can have its own form and different logic as you see fit:
const multistepRoutes: Routes = [
{
path: 'multistep',
component: MultistepComponent,
children: [
{
path: '',
component: MultistepBaseComponent,
},
{
path: 'step1',
component: MultistepStep1Component
},
{
path: 'step2',
component: MultistepStep2Component
}
]
}
];
The service multistep.service can hold the model and implement logic for components:
import { Injectable, Inject } from '#angular/core';
import { Router } from '#angular/router';
#Injectable()
export class MultistepService {
public model = {};
public baseRoute = '/multistep';
public steps = [
'step1',
'step2'
];
constructor (
#Inject(Router) public router: Router) { };
public getInitialStep() {
this.router.navigate([this.baseRoute + '/' + this.steps[0]]);
};
public goToNextStep (direction /* pass 'forward' or 'backward' to service from view */): any {
let stepIndex = this.steps.indexOf(this.router.url.split('/')[2]);
if (stepIndex === -1 || stepIndex === this.steps.length) return;
this.router.navigate([this.baseRoute + '/' + this.steps[stepIndex + (direction === 'forward' ? 1 : -1)]]);
};
};
Good luck.
EDIT 12/6/2016
Actually, now having worked with the form API for a while I don't believe my previous answer is the best way to achieve this.
A preferrable approach is to create a top level FormGroup which has each step in your multistep form as it's own FormControl (either a FormGroup or a FormArray) under it's controls property. The top level form in such a case would be the single-source of truth for the form's state, and each step on creation (ngOnInit / constructor) would be able to read data for its respective step from the top level FormGroup. See the pseudocode:
const topLevelFormGroup = new FormGroup({
step1: new FormGroup({fieldForStepOne: new FormControl('')}),
step2: new FormGroup({fieldForStepTwo}),
// ...
});
...
// Step1Component
class Step1Component {
private stepName: string = 'step1';
private formGroup: FormGroup;
constructor(private topLevelFormGroup: any /* DI */) {
this.formGroup = topLevelFormGroup.controls[this.stepName];
}
}
Therefore, the state of the form and each step is kept exactly where it should be—in the form itself!
Why not use session storage? For instance you can use this static helper class (TypeScript):
export class Session {
static set(key:string, value:any) {
window.sessionStorage.setItem(key, JSON.stringify(value));
}
static get(key:string) {
if(Session.has(key)) return JSON.parse(window.sessionStorage[key])
return null;
}
static has(key:string) {
if(window.sessionStorage[key]) return true;
return false;
}
static remove(key:string) {
Session.set(key,JSON.stringify(null)); // this line is only for IE11 (problems with sessionStorage.removeItem)
window.sessionStorage.removeItem(key);
}
}
And using above class, you can put your object with multi-steps-forms data and share it (idea is similar like for 'session helper' in many backend frameworks like e.g. php laravel).
The other approach is to create Singleton service. It can look like that (in very simple from for sake of clarity) (I not test below code, I do it from head):
import { Injectable } from '#angular/core';
#Injectable()
export class SessionService {
_session = {};
set(key:string, value:any) {
this._session[key]= value; // You can also json-ize 'value' here
}
get(key:string) {
return this._session[key]; // optionally de-json-ize here
}
has(key:string) {
if(this.get(key)) return true;
return false;
}
remove(key:string) {
this._session[key]=null;
}
}
And then in your main file where you bootstrap application:
...
return bootstrap(App, [
...
SessionService
])
...
And the last step - critical: When you want to use you singleton service in your component - don't put int in providers section (this is due to angular2 DI behavior - read above link about singleton services). Example below for go from form step 2 to step 3:
import {Component} from '#angular/core';
import {SessionService} from './sessionService.service';
...
#Component({
selector: 'my-form-step-2',
// NO 'providers: [ SessionService ]' due to Angular DI behavior for singletons
template: require('./my-form-step-2.html'),
})
export class MyFormStep2 {
_formData = null;
constructor(private _SessionService: SessionService) {
this._formData = this._SessionService.get('my-form-data')
}
...
submit() {
this._SessionService.set('my-form-data', this._formData)
}
}
It should looks like that.