【问题标题】:Pitfalls of the New AngularJS ng-ref Directive新 AngularJS ng-ref 指令的缺陷
【发布时间】:2019-01-13 21:12:02
【问题描述】:

AngularJS V1.7.1 的发布* 引入了新的ng-ref directive。虽然这个新指令使用户能够轻松地做某些事情,但我看到了滥用和问题的巨大潜力。

ng-ref 属性告诉 AngularJS 在当前范围内发布组件的控制器。这对于让诸如音频播放器之类的组件将其 API 暴露给同级组件很有用。可以轻松访问其播放和停止控件。

第一个问题是播放器控件是undefined在控制器的$onInit函数内。

Initial vm.pl = undefined  <<<< UNDEFINED
Sample = [true,false]

对于依赖于可用数据的代码,我们如何解决这个问题?

The DEMO

angular.module("app",[])
.controller("ctrl", class ctrl {
  constructor() {
    console.log("construct")
  }
  $onInit() {
    console.log("onInit", this.pl);
    this.initPL = this.pl || 'undefined';
    this.sample = this.pl || 'undefined';
    this.getSample = () => {
      this.sample = `[${this.pl.box1},${this.pl.box2}]`;
    }
  }
})
.component("player", {
  template: `
    <fieldset>
      $ctrl.box1={{$ctrl.box1}}<br>
      $ctrl.box2={{$ctrl.box2}}<br>
      <h3>Player</h3>
    </fieldset>
  `,
  controller: class player {
    constructor() {
      console.log("player",this);
    }
    $onInit() {
      console.log("pl.init", this)
      this.box1 = true;
      this.box2 = false;
    }
  },
})
<script src="//unpkg.com/angular@1.7.1/angular.js"></script>
<body ng-app="app" ng-controller="ctrl as vm">
    Initial vm.pl = {{vm.initPL}}<br>
    Sample = {{vm.sample}}<br>
    <button ng-click="vm.getSample()">Get Sample</button>
    <br>
    <input type="checkbox" ng-model="vm.pl.box1" />
      Box1 pl.box1={{vm.pl.box1}}<br>
    <input type="checkbox" ng-model="vm.pl.box2" />
      Box2 pl.box2={{vm.pl.box2}}<br>
    <br>
    <player ng-ref="vm.pl"></player>
</body>

【问题讨论】:

    标签: angularjs angularjs-components


    【解决方案1】:

    获取组件控制器的 ref 并不是什么新鲜事,过去的指令允许这样做,这根本不是问题,有必要拥有这样的功能,ng-ref 只是你的助手模板端(与 angular 2+ 相同)。

    不过,如果您需要准备好子组件,您应该使用$postLink() 而不是$onInit$postLink 在组件与他的子组件链接后被调用,这意味着 ng-ref 将在被调用时准备好。

    所以你所要做的就是像这样改变你的onInit

    ̶$̶o̶n̶I̶n̶i̶t̶(̶)̶ ̶{̶
    $postLink() {
        console.log("onInit", this.pl);
        this.initPL = this.pl || 'undefined';
        this.sample = this.pl || 'undefined';
        this.getSample = () => {
          this.sample = `[${this.pl.box1},${this.pl.box2}]`;
        }
    }
    

    $postLink() - 在此控制器的元素及其子元素被链接后调用。与 post-link 函数类似,此钩子可用于设置 DOM 事件处理程序并进行直接 DOM 操作。请注意,包含 templateUrl 指令的子元素将不会被编译和链接,因为它们正在等待其模板异步加载,并且它们自己的编译和链接已暂停,直到发生这种情况。这个钩子可以被认为类似于 Angular 中的 ngAfterViewInitngAfterContentInit 钩子。由于 AngularJS 中的编译过程有很大不同,因此没有直接映射,升级时应注意。

    参考:Understanding Components

    完整的工作 sn-p 可以在下面找到(我删除了所有 console.log 以使其更清晰):

    angular.module("app",[])
    .controller("ctrl", class ctrl {
      constructor() {
        //console.log("construct")
      }
      $postLink() {
        //console.log("onInit", this.pl);
        this.initPL = this.pl || 'undefined';
        this.sample = this.pl || 'undefined';
        this.getSample = () => {
          this.sample = `[${this.pl.box1},${this.pl.box2}]`;
        }
      }
    })
    .component("player", {
      template: `
        <fieldset>
          $ctrl.box1={{$ctrl.box1}}<br>
          $ctrl.box2={{$ctrl.box2}}<br>
        </fieldset>
      `,
      controller: class player {
        constructor() {
          //console.log("player",this);
        }
        $onInit() {
          //console.log("pl.init", this)
          this.box1 = true;
          this.box2 = false;
        }
      },
    })
    <script src="//unpkg.com/angular@1.7.1/angular.js"></script>
    <body ng-app="app" ng-controller="ctrl as vm">
        Initial vm.pl = {{vm.initPL}}<br>
        Sample = {{vm.sample}}<br>
        <button ng-click="vm.getSample()">Get Sample</button>
        <br>
        <input type="checkbox" ng-model="vm.pl.box1" />
          Box1 pl.box1={{vm.pl.box1}}<br>
        <input type="checkbox" ng-model="vm.pl.box2" />
          Box2 pl.box2={{vm.pl.box2}}<br>
        <player ng-ref="vm.pl"></player>
      </body>

    【讨论】:

      【解决方案2】:

      父控制器初始化发生在播放器控制器初始化之前,这就是为什么我们在第一个$onInit 中有initPL 作为undefined。 就个人而言,我更愿意在父控制器初始化时定义和加载应该传递给嵌套组件的数据,而不是从其子控制器设置父控制器的初始数据。但是,如果我们需要这个,我们可以使用绑定和回调在子组件初始化时进行。可能它看起来更像是一个肮脏的解决方法,但它可以在这种情况下工作,这里是代码:

      angular.module("app",[])
      .controller("ctrl", class ctrl {
        constructor() {
          console.log("construct")
        }
        $onInit() {
          console.log("onInit", this.pl);
          this.getSample = () => {
            this.sample = `[${this.pl.box1},${this.pl.box2}]`;
          }
          this.onPlayerInit = (pl) => {
            console.log("onPlayerInit", pl);
            this.initPL = pl || 'undefined';
            this.sample = `[${pl.box1},${pl.box2}]`;
          }
        }
      })
      .component("player", {
        bindings: {
            onInit: '&'
        },
        template: `
          <fieldset>
            $ctrl.box1={{$ctrl.box1}}<br>
            $ctrl.box2={{$ctrl.box2}}<br>
            <h3>Player</h3>
          </fieldset>
        `,
        controller: class player {
          constructor() {
            console.log("player",this);
          }
          $onInit() {
            console.log("pl.init", this)
            this.box1 = true;
            this.box2 = false;
            if (angular.isFunction( this.onInit() )) {
              this.onInit()(this);
            }
          }
        },
      })
      <script src="//unpkg.com/angular@1.7.1/angular.js"></script>
      <body ng-app="app" ng-controller="ctrl as vm">
          Initial vm.pl = {{vm.initPL}}<br>
          Sample = {{vm.sample}}<br>
          <button ng-click="vm.getSample()">Get Sample</button>
          <br>
          <input type="checkbox" ng-model="vm.pl.box1" />
            Box1 pl.box1={{vm.pl.box1}}<br>
          <input type="checkbox" ng-model="vm.pl.box2" />
            Box2 pl.box2={{vm.pl.box2}}<br>
          <br>
          <player ng-ref="vm.pl" on-init="vm.onPlayerInit"></player>
      </body>

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-05-04
        • 1970-01-01
        • 1970-01-01
        • 2015-06-16
        • 1970-01-01
        • 2015-11-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多