Angular 6 - Object Properties Updating without Setting them - html

I have an editor where the user can edit a product. I save an instance of the product in ngOnInit under initialProduct to make it possible to reset the edits.
Unfortunately, I have a weird issue: When adding a new tag the properties of the initialProduct changes without setting them.
Here is a stackblitz:
https://stackblitz.com/edit/angular-yxrh2d?file=src%2Fapp%2Fapp.component.ts

With this code
this.initialProduct = this.product;
you are assigning to this.initialProduct the same variable positioned at the memory index related by this.product. This because this.product points to a memory address and with the previous operation you are copying only the memory address. So this.product and this.initialProduct point to the same variable.
You need to create another array and to copy this.product values into this.initialProduct (new array).
You can do this by various ways. For example:
// this.initialProduct = this.product;
this.initialProduct = {
tags: Array.from(this.product.tags)
}
or
// this.initialProduct = this.product;
this.initialProduct = {
tags: this.product.tags.concat()
}
or
// this.initialProduct = this.product;
this.initialProduct = {
tags: this.product.tags.slice()
}

because of references
this.tags = this.product.tags;
You can do the following (ES6):
this.tags = [...this.product.tags];

Related

ngOnChanges only works when it's not the same value

So basically I have a modal component with an input field that tells it which modal should be opened (coz I didn't want to make a component for each modal):
#Input() type!:string
ngOnChanges(changes: SimpleChanges): void {
this.type = changes["type"].currentValue;
this.openModal();
}
that field is binded to one in the app component:
modalType = "auth";
HTML:
<app-modal [type] = modalType></app-modal>
In the beginning it's got the type "auth" (to login or register), but when I click on an icon I want to open a different modal, I do it like so:
<h1 id="options-route"
(click) ="modalType = 'settings'"
>⚙</h1>
but this only works the first time, when modalType already has the value "settings" the event doesn't trigger even though the value has technically changed
I think the problem is that it's the same value because i tried putting a button that does the exact same thing but with the value "auth" again and with that it was clear that the settings button only worked when tha last modal opened was auth and viceversa
any ideas? I want to be able to open the settings modal more than once consecutively possibly keeping onChange because ngDoCheck gets called a whole lot of times and it slows down the app
You need to include the changeDetectorRef, in order to continue in this way.
More about it https://angular.io/api/core/ChangeDetectorRef
Although, a better and a faster alternative is the use of a behavior Subject.
All you have to do is create a service that makes use of a behavior subject to cycle through each and every value exposed and then retrieve that value in as many components as you want. To do that just check for data changes in the ngOnInit of target component.
You may modify this for implementation,
private headerData = new BehaviorSubject(new HeaderData());
headerDataCurrent = this.headerData.asObservable();
changeHeaderData(headerDataNext : HeaderData) {
this.headerData.next(headerDataNext)
console.log("subscription - changeUserData - "+headerDataNext);
}
Explanation:
HeaderData is a class that includes the various values that can be shared with respective data types.
changeHeaderData({obj: value}), is used to update the subject with multiple values.
headerDataCurrent, an observable has to be subscribed to in the target component and data can be retrieved easily.
I mean i'm too l-a-z-y to use your slightly-not-so-much-tbh complicated answers so I just did this:
I added a counter that tops to 9 then gets resetted to 0 and I add it to the value
screwYouOnChangesImTheMasterAndYouShallDoMyBidding = 0;
//gets called onClick
openSettings(){
if(this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding === 9){
this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding = 0;
}
this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding = this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding + 1;
this.modalType = "settings"+this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding;
}
then in the child component I just cut that last character out:
ngOnChanges(changes: SimpleChanges): void {
let change = changes["type"].currentValue as string;
change = change.substring(0, change.length - 1);
this.type = change;
this.openModal();
}
works like a charm 😂

Appery.io navigation by passing variable parameter

I'm listing few user details id,name,place,phno using list control form my DB.
My model(userModel) has these 4 items and the array(userList) has some user elements.
Now i use
"ng-repeat user in userList" to populate the data in list.
On click i want to navigate to an update page containing some input fields where i can update that specific users details. For this i need the id. So i'm passing it to the next page.
I've used a scope function updatePage for navigation.
"ng-click updatePage(user.id)"
Inside updatePage function with argument userId:
Apperyio.navigateTo("UpdatePage", {id : userId});
In next page i've taken value as:
var $routeParams = Apperyio.get( "$routeParams" );
var id = $routeParams.id;
I know passing static value as parameters is easy but...
The problem is i'm always getting the same 'id' no matter which row i clicked.
I think the problem is in the function argument passing when clicked.
Please help. I'm a beginner.
Is there any other way to implement such a scenario.
You can share data and functions between pages in Appery Ionic projects using Angular factories.
Click Create New > JavaScript with Name = UserManager and Type = Angular factory.
Set factory code to
define( ['require'], function( require ){
function func( Apperyio ){
var manager = {
userId: null
};
return manager;
}
return [{
/* name for angular resource */
name: 'userManager',
/* type of angular resource */
type: 'factory',
/* angular dependency injection array */
deps: [ 'Apperyio', func ]
}];
});
Then on the 1st page you can set variable before navigating to 2nd page:
Apperyio.get('userManager').userId = user._id;
Apperyio.navigateTo("UpdatePage");
and on the 2nd page you can retrieve it:
var userId = Apperyio.get('userManager').userId;

Swift Class is not constructible

I'm following along with the iBook for swift programming, but I am getting an error when I try to contruct a class with var. Here is a stuct and a class:
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
I can create an instance of the Resolution struct just fine, but I can't seem to make one for VideoMode Class.
var r = Resolution()
println("Width:\(r.width) Height:\(r.height)")
r.height = 1234
r.width = 9877
println("Width:\(r.width) Height:\(r.height)")
var vm = VideoMode() //Says that 'VideoMode' is not constructible with ()
let vm = VideoMode() //Apparently this works though.... why?
vm.resolution.width = 22222
vm.resolution.height = 1234
vm.name = "Calimari"
print(vm)
I find this strange can anyone explain?
Update:
Apparently it works ok in playground. I am not running this in playground. I am running it using the master detail template using swift code. I added the "var vm = VideoMode()" in the viewDidLoad method and I get an error. But it seems to be ok if I change it to "let". No clue why that makes a difference.
If you don't define default values for all stored properties then you must define init().
var name: String? // There's no default value here. Either set name to `nil` or define init()
Exerpt from the Swift Documentation:
Classes and structures must set all of their stored properties to an appropriate initial value by the time an instance of that class or structure is created. Stored properties cannot be left in an indeterminate state.
You can set an initial value for a stored property within an initializer, or by assigning a default property value as part of the property’s definition. These actions are described in the following sections.
Addendum:
As stressed by user #valfer, I've found the following:
Optional Property Types
[...] Properties of optional type are automatically initialized with a value of nil, indicating that the property is deliberately intended to have “no value yet” during initialization.
I ignore if the above was present from the get-go or if it was added after the fact as the language is in beta at the time of this writing and is still in flux.
Apparently the Question mark at the end of the "name" variable declaration was preventing this from constructing.
//Implementation file of VideoMode
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String = "" //removed the question mark
}
//.....in another class
var vm = VideoMode(); //seems to work after making the above changes to the class declaration
I guess you forget to put : NSObject
like this:
class VideoMode: NSObject {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}

BackboneJS - Multiple Collections in one view

I have a leftMenu section where I want to display 4 menus which are categorized by each subject. Every menu is a list and I want them to be displayed in the same View. I created 4 JSON files and to each one of them a Collection and a Model.
Usually i do it like this, first i define in router
this.mainMenuCollection = new MainMenuCollection();
this.mainMenuView = new MainMenuView({el:'#nav', collection:this.mainMenuCollection});
So, now I have these 4 collections defined in the router which I want in one view:
this.allcategoryMenuCollection = new AllCategoryMenuCollection();
this.natcategoryMenuCollection = new NatCategoryMenuCollection();
this.intcategoryMenuCollection = new IntCategoryMenuCollection();
this.topcategoryMenuCollection = new TopCategoryMenuCollection();
usually i render the collection in the View like this:
$(this.el).html(this.template({mainmenu:this.collection.toJSON()}));
Please help
Send in the collections as an object when you create your view:
this.mainView = new MainView({
el:'#nav'
});
this.mainView.collections = {
allcategoryMenuCollection: this.allcategoryMenuCollection
natcategoryMenuCollection: this.natcategoryMenuCollection
intcategoryMenuCollection: this.intcategoryMenuCollection
topcategoryMenuCollection: this.topcategoryMenuCollection
}
Access your collections inside the view by calling this.collections.collectionName
This may not seem like a semantically accurate way to do it, but to take advantage of backbone automatically assigning the model and collection properties in options, I do this:
var view = new MyView({
collection: {
comments: new CommentsCollection(),
images: new ImagesCollection()
}
});
Works for me. Otherwise you have to put a line in the initialize function to extend this, or use a base class. Personally I find this more elegant, even if it's missing a couple of pluralised properties.
Then, later on, you can use this.collection.comments.toJSON() for your template properties.
There's nothing wrong with housing a number of sub-views within an outer view. Your outer view could be something as simple as this. If you need to wait for all of your collections to be fetched before you render you could try:
var MyView = Backbone.View.extend({
initialize : function (options) {
var done, collections = options.collections;
done = _.invoke(collections, 'fetch');
$.when.apply($, done).done(this.onDataReady);
},
onDataReady : function () {
// Make a second pass at render after setting the flag.
this.ready = true;
this.render();
},
render : function () {
if(this.ready === true) {
// Now render the whole lot.
} else {
// Any non-data depending rendering.
}
}
});
Then you can either initialize the outer view and pass an array of collections in:
var view = new MyView({
collections: [...]
});
If you don't need all the collections to have fetched their data before hand it's even simpler. Just pass in the collections array and set them up as you need:
var MyView = Backbone.View.extend({
initialize : function (options) {
this.collectionA = options.collections[0];
},
render : function () {
}
});

Accessing DOM object properties from Chrome's content script

I ran into a strange problem with a content script. The content script is defined as "run_at" : "document_end" in the manifest. After a page is loaded the script inserts an object tag into the page (if the tag with predefined id does not exist yet), and sets some properties in it, such as type, width, height, innerHTML, and title. All works fine here.
function checkForObject()
{
var obj = document.getElementById("unique_id");
if(obj == null)
{
var d = document.createElement("object");
d.id = "unique_id";
d.width = "1";
d.height = "1";
d.type = "application/x-y-z";
d.title = "1000";
d.style.position = "absolute";
d.style.left = "0px";
d.style.top = "0px";
d.style.zIndex = "1";
document.getElementsByTagName("body")[0].appendChild(d);
}
}
checkForObject();
I see the new object in the page html-code with proper values in its properties.
Some time later I need to read the title property of the object in the same content script. The code is simple:
function ReadTitle()
{
var obj = document.getElementById("unique_id");
var value = obj.title; // breakpoint
console.log(value);
// TODO: want to use proper title value here
}
The function is called from background.html page:
chrome.tabs.onActivated.addListener(
function(info)
{
chrome.tabs.executeScript(info.tabId, {code: 'setTimeout(ReadTitle, 250);'});
});
Unfortunately, in ReadTitle I'm getting not what I expect. Instead of current value of the title I see the logged value is:
function title() { [native code] }
If I set a breakpoint at the line marked by // breakpoint comment, I see in the watcher that all object properties including the title are correct. Nevertheless, the variable value gets the abovementioned descriptive string.
Apparently, I have missed something simple, but I can't figure it out.
The answer. It was a bug in the npapi plugin, which hosts the object of used type. My apologies for all who have read the question with intention to help.
The NPAPI plugin used in the object erroneously reported title as supported method.