typescriptprotected

Typescript protected object contains public members


Why there is no compiler error, when writing dataField of protected object within class Something that is not derived of class parent?

I expected that this.obj.data and all properties are read-only. But compiler only throws error for this.obj.data = new Data(), because it's protected property. It doesn't throw error, if writing to properties inside this object (this.obj.data.dataField = "something"), because they are public again.

In which use case does it make sense, where protected object contains public properties?

export class Data {
  public dataField: string = "empty";
}
export abstract class Parent {
  protected _dd: Data = new Data();
    
  public get data(): Data {
    return this._dd;
  }
}

export class Child extends Parent {
 constructor() {        
    super();
    this._dd.dataField = "hello";
  }
}

export class Something {
  obj: Child;  
  constructor() {       
    this.obj = new Child();
    this.obj.data.dataField = "hello world";
  }
}

Solution

  • I think there's a confusion: the field Parent._dd (and, subsequently, Child._dd) is protected, but because of the public getter, you can access it by using .data everywhere you have an instance of Parent (in this case, Child).

    I expected that this.obj.data and all properties are read-only. But compiler only throws error for this.obj.data = new Data(), because it's protected property.

    this.obj.data is read-only¹: you can't replace data (actually, _dd) with another object (not from inside Something class) because you don't have a public setter. But, since their properties are public (or have public setters), they aren't read-only.

    In which use case does it make sense, where protected object contains public properties?

    If you didn't have that public getter, you could have methods in Child/Parent classes that manipulated specific fields of Data class. This way, you could control which fields could be changed. For instance:

    export class Data {
      public dataField: string = "empty";
      public anotherField: string = "maybe empty";
    }
    export abstract class Parent {
      protected _dd: Data = new Data();
    
      public get dataField(): string {
        return this._dd.dataField;
      }
    
      public get anotherField(): string {
        return this._dd.anotherField;
      }
        
      public set anotherField(anotherField: string): void {
        this._dd.anotherField = anotherField;
      }
    }
    
    export class Child extends Parent {
     constructor() {        
        super();
        this._dd.dataField = "hello";
      }
    }
    
    export class Something {
      obj: Child;  
      constructor() {       
        this.obj = new Child();
    
        // ERROR because of lack of dataField setter - so, dataField is 'read-only',
        // as in, only Child/Parent and Data itself can manipulate it
        this.obj.dataField = "hello world"; 
    
        // OK because it's using anotherField setter, which access underlying _dd
        this.obj.anotherField = "hello world"; 
    
        // NOTICE that accessing this.obj._dd would error, so you can manipulate
        // fields of Data only using getter/setter specified in the containing class,
        // you have no way to manipulate Data itself from Something.
      }
    }
    

    Notice: I've implemented them as getter/setter, but I'm not sure this is good practice, to have getter/setters changing fields from nested objects. It's more of educational.

    But the point is: the use of having public fields in protected object is that if you don't expose the protected object (no public getter or public method returning it), you can control it's access.


    ¹ Notice: from my understanding, OP is using "read-only" as in "can't be changed from Something's pov". So, every time I mention read-only, it's in that same context. Not to be confused with readonly keyword.