delegateas / XrmDefinitelyTyped

Tool to generate TypeScript declaration files for Dynamics 365/CDS client-side coding.
http://delegateas.github.io/Delegate.XrmDefinitelyTyped/
MIT License
133 stars 53 forks source link

Object is possibly 'undefined' #81

Closed hansgfixed closed 5 years ago

hansgfixed commented 6 years ago

I came across an issue when not using the pre-provided arguments i.e. when getting an attribute and use a custom variable/property instead that a warning gets thrown saying the object is possibly undefined.

For example formContext.getAttribute(result.key) throws said warning.

Did anyone else already encounter this issue and is a workaround available? Using null assertion operator doesn't work in this case.

magesoe commented 6 years ago

To answer your question directly, null and undefined are two different things :).

However, your question relates to a situation that is far more general. Since XDT is an early-bound generator, you have to give it a specific logicalname to get the attribute.

formContext.getAttribute("name")

This is how you should approach your code, write client-side code for a specific form and attributes. But what if you have some common function that is used on several attributes?

let attrName: string = getAttrName();
usingAttr(attrName);

// ... some where else ... 
function usingAttr(attrName: string) {
  let attr = formContext.getAttribute(attrName);
  // use attr
}

My preferred way of dealing with this is to turn getAttrName into getAttr, such that you return a XrmAttribute<string> if the value of the attribute is a string, and XrmAttribute<any> if no common value exists between the attribute names it should return.

let attr: XrmAttribute<string> = getAttr();
usingAttr(attr);

// ... some where else ... 
function usingAttr(attr: XrmAttribute<string>) {
  // use attr
}

GetAttr would thus have to do a switch based upon the string value it used to return, and instead return the actual attribute. What you gain is that if someone removes the attribute your code uses, or changes the value type of the attribute, your code will no longer build and you catch your errors at compile time instead of runtime.

Note: I did not run any of this code, so please say if it has syntax errors.

hansgfixed commented 5 years ago

To answer your question directly, null and undefined are two different things :) You're right, my bad. ;)

Thanks for your thorough explanation but I still don't get why you would want to return an XrmAttribute when getAttribute expects just a string?

Anyway we managed to achieve dynamically loading attributes with let _attr = <Xrm.Attribute<any>>formContext.getAttribute(attr); _attr.addOnChange(xy); while also checking if the attribute is not null.

magesoe commented 5 years ago

XrmDefinitelyTyped generates early bound typings. So .getAttribute not only expects a string, but a specific string. It will only return the correct type when given a string that matches the logicalname of an attribute on the form you're working on.

This means formContext.getAttribute("name") will return Xrm.Attribute<string> on an account form, since the name attribute is on the form and the type of the attribute is string.

By writing <Xrm.Attribute<any>>formContext.getAttribute(attr) you circumvent the typings generated by XrmDefinitelyTyped and force the type to be what you want. Thus your code will not help you when the attribute is no longer on the form. That is why .getAttribute returns undefined when a generic string is passed to it.

In my example, the generic function usingAttr only expects to work with string attributes. Therefore I force the attribute to be of type string. Thus if the type is changed on an existing attribute, my code will not compile and i can fix it before it is deployed.

I would advice against type casting attributes, since you only make the code harder to maintain, in the long run.

hansgfixed commented 5 years ago

Eureka effect! Finally it sunk in and we've edited all our code to check for the specific type.

Thank you!